Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions h --force-with-lease
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
df5d68b7e919c7b1ab5ff4fcf6514cec7c1c590c fix: update e2e tests to expect exit code 130 on user decline
Signed-off-by: Ishwar <ishwarcm@iitbhilai.ac.in>

cbf7940a73f0e0ee8a52d3d30a13054eee46aae4 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 <ishwarcm@iitbhilai.ac.in>

61e910218b2b2f0d519058a81d56a307ea2afc73 publish: return ErrPublishAborted when user declines interactive prompts
Signed-off-by: Ishwar <ishwarcm@iitbhilai.ac.in>

36 changes: 35 additions & 1 deletion pkg/compose/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,10 +407,23 @@ func (s *composeService) checkOnlyBuildSection(project *types.Project) (bool, er
func (s *composeService) checkForBindMount(project *types.Project) map[string][]types.ServiceVolumeConfig {
allFindings := map[string][]types.ServiceVolumeConfig{}
for serviceName, config := range project.Services {
bindMounts := []types.ServiceVolumeConfig{}
var bindMounts []types.ServiceVolumeConfig
for _, volume := range config.Volumes {
if volume.Type == types.VolumeTypeBind {
bindMounts = append(bindMounts, volume)
continue
}
if volume.Type == types.VolumeTypeVolume && volume.Source != "" {
if topLevel, ok := project.Volumes[volume.Source]; ok {
if isDriverOptsBind(topLevel) {
device := topLevel.DriverOpts["device"]
bindMounts = append(bindMounts, types.ServiceVolumeConfig{
Type: types.VolumeTypeBind,
Source: device,
Target: volume.Target,
})
}
}
}
}
if len(bindMounts) > 0 {
Expand All @@ -420,6 +433,27 @@ func (s *composeService) checkForBindMount(project *types.Project) map[string][]
return allFindings
}

func isDriverOptsBind(v types.VolumeConfig) bool {
if v.Driver != "" && v.Driver != "local" {
return false
}
opts := v.DriverOpts
if len(opts) == 0 {
return false
}
_, hasDevice := opts["device"]
if !hasDevice {
return false
}
for _, opt := range strings.Split(opts["o"], ",") {
switch strings.TrimSpace(opt) {
case "bind", "rbind":
return true
}
}
return false
}

func (s *composeService) checkForSensitiveData(ctx context.Context, project *types.Project) ([]secrets.DetectedSecret, error) {
var allFindings []secrets.DetectedSecret
scan := scanner.NewDefaultScanner()
Expand Down
113 changes: 113 additions & 0 deletions pkg/compose/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,116 @@ func Test_publish_decline_returns_ErrCanceled(t *testing.T) {
assert.Assert(t, errors.Is(err, api.ErrCanceled),
"expected api.ErrCanceled when user declines, got: %v", err)
}

func Test_checkForBindMount_namedVolume_driverOptsBind(t *testing.T) {
project := &types.Project{
Services: types.Services{
"web": {
Name: "web",
Image: "nginx",
Volumes: []types.ServiceVolumeConfig{
{
Type: types.VolumeTypeVolume,
Source: "secret_host_data",
Target: "/mnt/data",
},
{
Type: types.VolumeTypeVolume,
Source: "normal_vol",
Target: "/data",
},
},
},
},
Volumes: types.Volumes{
"secret_host_data": {
Driver: "local",
DriverOpts: map[string]string{
"type": "none",
"o": "bind",
"device": "/Users/admin/.ssh",
},
},
"normal_vol": {
Driver: "local",
},
},
}

svc := &composeService{}
findings := svc.checkForBindMount(project)

assert.Equal(t, 1, len(findings["web"]), "expected exactly one bind mount finding for web")
assert.Equal(t, "/Users/admin/.ssh", findings["web"][0].Source)
assert.Equal(t, "/mnt/data", findings["web"][0].Target)
}

func Test_isDriverOptsBind(t *testing.T) {
tests := []struct {
name string
volume types.VolumeConfig
expected bool
}{
{
name: "plain bind",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "bind", "device": "/host/path"},
},
expected: true,
},
{
name: "rbind",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "rbind", "device": "/host/path"},
},
expected: true,
},
{
name: "comma-separated ro,bind — validates split logic",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "ro,bind", "device": "/host/path"},
},
expected: true,
},
{
name: "nobind must not match — guards against substring false positive",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "nobind", "device": "/host/path"},
},
expected: false,
},
{
name: "no device key",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "bind"},
},
expected: false,
},
{
name: "empty driver_opts",
volume: types.VolumeConfig{
Driver: "local",
},
expected: false,
},
{
name: "non-local driver",
volume: types.VolumeConfig{
Driver: "nfs",
DriverOpts: map[string]string{"o": "bind", "device": "/host/path"},
},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, isDriverOptsBind(tt.volume))
})
}
}
Loading