fix: handle macOS /tmp symlink in sandbox allowWrite paths (#23)
This commit is contained in:
@@ -84,6 +84,38 @@ func getAncestorDirectories(pathStr string) []string {
|
|||||||
return ancestors
|
return ancestors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expandMacOSTmpPaths mirrors /tmp paths to /private/tmp equivalents and vice versa.
|
||||||
|
// On macOS, /tmp is a symlink to /private/tmp, and symlink resolution can fail if paths
|
||||||
|
// don't exist yet. Adding both variants ensures sandbox rules match kernel-resolved paths.
|
||||||
|
func expandMacOSTmpPaths(paths []string) []string {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, p := range paths {
|
||||||
|
seen[p] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var additions []string
|
||||||
|
for _, p := range paths {
|
||||||
|
var mirror string
|
||||||
|
switch {
|
||||||
|
case p == "/tmp":
|
||||||
|
mirror = "/private/tmp"
|
||||||
|
case p == "/private/tmp":
|
||||||
|
mirror = "/tmp"
|
||||||
|
case strings.HasPrefix(p, "/tmp/"):
|
||||||
|
mirror = "/private" + p
|
||||||
|
case strings.HasPrefix(p, "/private/tmp/"):
|
||||||
|
mirror = strings.TrimPrefix(p, "/private")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mirror != "" && !seen[mirror] {
|
||||||
|
seen[mirror] = true
|
||||||
|
additions = append(additions, mirror)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(paths, additions...)
|
||||||
|
}
|
||||||
|
|
||||||
// getTmpdirParent gets the TMPDIR parent if it matches macOS pattern.
|
// getTmpdirParent gets the TMPDIR parent if it matches macOS pattern.
|
||||||
func getTmpdirParent() []string {
|
func getTmpdirParent() []string {
|
||||||
tmpdir := os.Getenv("TMPDIR")
|
tmpdir := os.Getenv("TMPDIR")
|
||||||
@@ -505,6 +537,9 @@ func WrapCommandMacOS(cfg *config.Config, command string, httpPort, socksPort in
|
|||||||
// Build allow paths: default + configured
|
// Build allow paths: default + configured
|
||||||
allowPaths := append(GetDefaultWritePaths(), cfg.Filesystem.AllowWrite...)
|
allowPaths := append(GetDefaultWritePaths(), cfg.Filesystem.AllowWrite...)
|
||||||
|
|
||||||
|
// Expand /tmp <-> /private/tmp for macOS symlink compatibility
|
||||||
|
allowPaths = expandMacOSTmpPaths(allowPaths)
|
||||||
|
|
||||||
// Enable local binding if ports are exposed or if explicitly configured
|
// Enable local binding if ports are exposed or if explicitly configured
|
||||||
allowLocalBinding := cfg.Network.AllowLocalBinding || len(exposedPorts) > 0
|
allowLocalBinding := cfg.Network.AllowLocalBinding || len(exposedPorts) > 0
|
||||||
|
|
||||||
|
|||||||
@@ -176,3 +176,70 @@ func TestMacOS_ProfileNetworkSection(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestExpandMacOSTmpPaths verifies that /tmp and /private/tmp paths are properly mirrored.
|
||||||
|
func TestExpandMacOSTmpPaths(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "mirrors /tmp to /private/tmp",
|
||||||
|
input: []string{".", "/tmp"},
|
||||||
|
want: []string{".", "/tmp", "/private/tmp"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mirrors /private/tmp to /tmp",
|
||||||
|
input: []string{".", "/private/tmp"},
|
||||||
|
want: []string{".", "/private/tmp", "/tmp"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no change when both present",
|
||||||
|
input: []string{".", "/tmp", "/private/tmp"},
|
||||||
|
want: []string{".", "/tmp", "/private/tmp"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no change when neither present",
|
||||||
|
input: []string{".", "~/.cache"},
|
||||||
|
want: []string{".", "~/.cache"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mirrors /tmp/fence to /private/tmp/fence",
|
||||||
|
input: []string{".", "/tmp/fence"},
|
||||||
|
want: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mirrors /private/tmp/fence to /tmp/fence",
|
||||||
|
input: []string{".", "/private/tmp/fence"},
|
||||||
|
want: []string{".", "/private/tmp/fence", "/tmp/fence"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mirrors nested subdirectory",
|
||||||
|
input: []string{".", "/tmp/foo/bar"},
|
||||||
|
want: []string{".", "/tmp/foo/bar", "/private/tmp/foo/bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no duplicate when mirror already present",
|
||||||
|
input: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
||||||
|
want: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := expandMacOSTmpPaths(tt.input)
|
||||||
|
|
||||||
|
if len(got) != len(tt.want) {
|
||||||
|
t.Errorf("expandMacOSTmpPaths() = %v, want %v", got, tt.want)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range got {
|
||||||
|
if v != tt.want[i] {
|
||||||
|
t.Errorf("expandMacOSTmpPaths()[%d] = %v, want %v", i, v, tt.want[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user