feat: deny-by-default filesystem isolation
Some checks failed
Build and test / Lint (push) Failing after 1m16s
Build and test / Build (push) Successful in 13s
Build and test / Test (Linux) (push) Failing after 41s
Build and test / Test (macOS) (push) Has been cancelled

- Deny-by-default filesystem isolation for Linux (Landlock) and macOS (Seatbelt)
- Prevent learning mode from collapsing read paths to $HOME
- Add Linux deny-by-default lessons to experience docs
This commit is contained in:
2026-02-13 11:39:18 -06:00
parent b55b3364af
commit c19370f8b3
11 changed files with 583 additions and 77 deletions

View File

@@ -1,6 +1,7 @@
package sandbox
import (
"fmt"
"strings"
"testing"
@@ -108,7 +109,8 @@ func buildMacOSParamsForTest(cfg *config.Config) MacOSSandboxParams {
AllowAllUnixSockets: cfg.Network.AllowAllUnixSockets,
AllowLocalBinding: allowLocalBinding,
AllowLocalOutbound: allowLocalOutbound,
DefaultDenyRead: cfg.Filesystem.DefaultDenyRead,
DefaultDenyRead: cfg.Filesystem.IsDefaultDenyRead(),
Cwd: "/tmp/test-project",
ReadAllowPaths: cfg.Filesystem.AllowRead,
ReadDenyPaths: cfg.Filesystem.DenyRead,
WriteAllowPaths: allowPaths,
@@ -175,38 +177,46 @@ func TestMacOS_DefaultDenyRead(t *testing.T) {
tests := []struct {
name string
defaultDenyRead bool
cwd string
allowRead []string
wantContainsBlanketAllow bool
wantContainsMetadataAllow bool
wantContainsSystemAllows bool
wantContainsUserAllowRead bool
wantContainsCwdAllow bool
}{
{
name: "default mode - blanket allow read",
name: "legacy mode - blanket allow read",
defaultDenyRead: false,
cwd: "/home/user/project",
allowRead: nil,
wantContainsBlanketAllow: true,
wantContainsMetadataAllow: false,
wantContainsSystemAllows: false,
wantContainsUserAllowRead: false,
wantContainsCwdAllow: false,
},
{
name: "defaultDenyRead enabled - metadata allow, system data allows",
name: "defaultDenyRead enabled - metadata allow, system data allows, CWD allow",
defaultDenyRead: true,
cwd: "/home/user/project",
allowRead: nil,
wantContainsBlanketAllow: false,
wantContainsMetadataAllow: true,
wantContainsSystemAllows: true,
wantContainsUserAllowRead: false,
wantContainsCwdAllow: true,
},
{
name: "defaultDenyRead with allowRead paths",
defaultDenyRead: true,
allowRead: []string{"/home/user/project"},
cwd: "/home/user/project",
allowRead: []string{"/home/user/other"},
wantContainsBlanketAllow: false,
wantContainsMetadataAllow: true,
wantContainsSystemAllows: true,
wantContainsUserAllowRead: true,
wantContainsCwdAllow: true,
},
}
@@ -215,6 +225,7 @@ func TestMacOS_DefaultDenyRead(t *testing.T) {
params := MacOSSandboxParams{
Command: "echo test",
DefaultDenyRead: tt.defaultDenyRead,
Cwd: tt.cwd,
ReadAllowPaths: tt.allowRead,
}
@@ -236,6 +247,13 @@ func TestMacOS_DefaultDenyRead(t *testing.T) {
t.Errorf("system path allows = %v, want %v\nProfile:\n%s", hasSystemAllows, tt.wantContainsSystemAllows, profile)
}
if tt.wantContainsCwdAllow && tt.cwd != "" {
hasCwdAllow := strings.Contains(profile, fmt.Sprintf(`(subpath %q)`, tt.cwd))
if !hasCwdAllow {
t.Errorf("CWD path %q not found in profile", tt.cwd)
}
}
if tt.wantContainsUserAllowRead && len(tt.allowRead) > 0 {
hasUserAllow := strings.Contains(profile, tt.allowRead[0])
if !hasUserAllow {