From 61e910218b2b2f0d519058a81d56a307ea2afc73 Mon Sep 17 00:00:00 2001 From: Ishwar Date: Thu, 26 Mar 2026 15:46:19 +0530 Subject: [PATCH 1/3] publish: return ErrPublishAborted when user declines interactive prompts Signed-off-by: Ishwar --- pkg/compose/publish.go | 2 +- pkg/compose/publish_test.go | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index e15eaec89d6..30b6a6a49b7 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -59,7 +59,7 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re return err } if !accept { - return nil + return api.ErrCanceled } err = s.Push(ctx, project, api.PushOptions{IgnoreFailures: true, ImageMandatory: true}) if err != nil { diff --git a/pkg/compose/publish_test.go b/pkg/compose/publish_test.go index 8f91f663e69..c0028c74031 100644 --- a/pkg/compose/publish_test.go +++ b/pkg/compose/publish_test.go @@ -17,6 +17,7 @@ package compose import ( + "errors" "slices" "testing" @@ -100,3 +101,62 @@ services: return !slices.Contains([]string{".Data", ".Digest", ".Size"}, path.String()) }, cmp.Ignore())) } + +func Test_preChecks_decline_returns_ErrPublishAborted(t *testing.T) { + project := &types.Project{ + Services: types.Services{ + "web": { + Name: "web", + Image: "nginx", + Volumes: []types.ServiceVolumeConfig{ + { + Type: types.VolumeTypeBind, + Source: "/host/path", + Target: "/container/path", + }, + }, + }, + }, + } + + declined := func(message string, defaultValue bool) (bool, error) { + return false, nil + } + svc := &composeService{ + prompt: declined, + } + + accept, err := svc.preChecks(t.Context(), project, api.PublishOptions{}) + assert.NilError(t, err) + assert.Equal(t, accept, false) +} + +func Test_publish_decline_returns_ErrCanceled(t *testing.T) { + project := &types.Project{ + Services: types.Services{ + "web": { + Name: "web", + Image: "nginx", + Volumes: []types.ServiceVolumeConfig{ + { + Type: types.VolumeTypeBind, + Source: "/host/path", + Target: "/container/path", + }, + }, + }, + }, + } + + declined := func(message string, defaultValue bool) (bool, error) { + return false, nil + } + svc := &composeService{ + prompt: declined, + events: &ignore{}, + } + + err := svc.publish(t.Context(), project, "docker.io/myorg/myapp:latest", api.PublishOptions{}) + assert.Assert(t, errors.Is(err, api.ErrCanceled), + "expected api.ErrCanceled when user declines, got: %v", err) +} From cbf7940a73f0e0ee8a52d3d30a13054eee46aae4 Mon Sep 17 00:00:00 2001 From: Ishwar Date: Tue, 31 Mar 2026 14:50:59 +0530 Subject: [PATCH 2/3] test: repurpose decline test to cover sensitive data detection path Renames test to Test_preChecks_sensitive_data_detected_decline. Uses a temporary .env file with an AWS token to reliably trigger the DefangLabs secret detector, and confirms that preChecks correctly aborts early on user decline. Signed-off-by: Ishwar --- pkg/compose/publish_test.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pkg/compose/publish_test.go b/pkg/compose/publish_test.go index c0028c74031..31932632ff2 100644 --- a/pkg/compose/publish_test.go +++ b/pkg/compose/publish_test.go @@ -18,6 +18,7 @@ package compose import ( "errors" + "os" "slices" "testing" @@ -102,18 +103,22 @@ services: }, cmp.Ignore())) } -func Test_preChecks_decline_returns_ErrPublishAborted(t *testing.T) { +func Test_preChecks_sensitive_data_detected_decline(t *testing.T) { + + dir := t.TempDir() + envPath := dir + "/secrets.env" + secretData := `AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"` + err := os.WriteFile(envPath, []byte(secretData), 0o600) + assert.NilError(t, err) + project := &types.Project{ + Services: types.Services{ "web": { Name: "web", Image: "nginx", - Volumes: []types.ServiceVolumeConfig{ - { - Type: types.VolumeTypeBind, - Source: "/host/path", - Target: "/container/path", - }, + EnvFiles: []types.EnvFile{ + {Path: envPath, Required: true}, }, }, }, From df5d68b7e919c7b1ab5ff4fcf6514cec7c1c590c Mon Sep 17 00:00:00 2001 From: Ishwar Date: Tue, 31 Mar 2026 21:54:17 +0530 Subject: [PATCH 3/3] fix: update e2e tests to expect exit code 130 on user decline Signed-off-by: Ishwar --- pkg/compose/publish_test.go | 2 -- pkg/e2e/publish_test.go | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/compose/publish_test.go b/pkg/compose/publish_test.go index 31932632ff2..3b0e5a4389c 100644 --- a/pkg/compose/publish_test.go +++ b/pkg/compose/publish_test.go @@ -104,7 +104,6 @@ services: } func Test_preChecks_sensitive_data_detected_decline(t *testing.T) { - dir := t.TempDir() envPath := dir + "/secrets.env" secretData := `AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"` @@ -112,7 +111,6 @@ func Test_preChecks_sensitive_data_detected_decline(t *testing.T) { assert.NilError(t, err) project := &types.Project{ - Services: types.Services{ "web": { Name: "web", diff --git a/pkg/e2e/publish_test.go b/pkg/e2e/publish_test.go index b5488df601c..1dd83d031e4 100644 --- a/pkg/e2e/publish_test.go +++ b/pkg/e2e/publish_test.go @@ -71,7 +71,7 @@ or remove sensitive data from your Compose configuration "-p", projectName, "publish", "test/test", "--dry-run") cmd.Stdin = strings.NewReader("n\n") res := icmd.RunCmd(cmd) - res.Assert(t, icmd.Expected{ExitCode: 0}) + res.Assert(t, icmd.Expected{ExitCode: 130}) out := res.Combined() assert.Assert(t, strings.Contains(out, "you are about to publish bind mounts declaration within your OCI artifact."), out) assert.Assert(t, strings.Contains(out, "e2e/fixtures/publish:/user-data"), out) @@ -111,7 +111,7 @@ or remove sensitive data from your Compose configuration "-p", projectName, "publish", "test/test", "--with-env", "--dry-run") cmd.Stdin = strings.NewReader("n\n") res := icmd.RunCmd(cmd) - res.Assert(t, icmd.Expected{ExitCode: 0}) + res.Assert(t, icmd.Expected{ExitCode: 130}) output := res.Combined() assert.Assert(t, strings.Contains(output, "you are about to publish sensitive data within your OCI artifact.\n"), output)