mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Experimental Sandbox Support (#101)
* feat: Sandbox implementation with seatbelt * refactor: Remove concept of PM_CACHE * fix: Misc fixes * refactor: Sandbox for separation of boundaries * fix: Apply API * fix: Add support for sandbox cleanup * test: Add variable interpolation test * fix: Misc cleanup fixes * chore: Cleanup sandbox registry * chore: Cleanup sandbox policy * chore: Cleanup sandbox * fix: Misc cleanup fixes * fix: Remove violation mode * fix: Update config template * chore: Go mod cleanup * fix: Handle the case when package manager policy is explicitly disabled * fix: Sandbox executor * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com> * test: Remove unused var * test: Add test for seatbelt sandbox driver * fix: Sandbox profile loader from file should use path for caching * test: Add policy test * feat: Add support for config templates * fix: Seatbelt translator handle glob * fix: Merge conflicts * fix: Fix sandbox policy generator for MacOS min permissions * fix: Sandbox path handling bugs * fix: Deny read to dangerous directories * fix: Deny read to dangerous directories * add sandbox e2e (#112) * fix: Sandbox E2E test * fix: Code review fixes * fix: Code review fixes * doc: Add sandbox debugging guide * doc: Update sandbox doc * docs: Add sandbox usage doc * fix: Use better error for sandbox without policy * fix: Add sandbox for npx * fix: Enable PTY for npm --------- Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
This commit is contained in:
co-authored by
Copilot
Sahil Bansal
parent
c0122898ca
commit
9693428171
@@ -0,0 +1,580 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/safedep/pmg/sandbox"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSeatbeltTranslatorDarwinCommonTranslation(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Name: "test",
|
||||
Description: "test",
|
||||
PackageManagers: []string{"npm"},
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowRead: []string{"/tmp"},
|
||||
AllowWrite: []string{"/tmp"},
|
||||
DenyRead: []string{"/private/var"},
|
||||
DenyWrite: []string{"/private/var"},
|
||||
},
|
||||
Network: sandbox.NetworkPolicy{
|
||||
AllowOutbound: []string{"*:*"},
|
||||
},
|
||||
Process: sandbox.ProcessPolicy{
|
||||
AllowExec: []string{"/bin/sh"},
|
||||
DenyExec: []string{"/bin/bash"},
|
||||
},
|
||||
}
|
||||
|
||||
translator := newSeatbeltPolicyTranslator()
|
||||
actual, err := translator.translate(policy)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test common translation
|
||||
assert.Contains(t, actual, "(version 1)")
|
||||
assert.Contains(t, actual, fmt.Sprintf(";; PMG Sandbox Policy: %s", policy.Name))
|
||||
assert.Contains(t, actual, fmt.Sprintf(";; %s", policy.Description))
|
||||
assert.Contains(t, actual, ";; Generated by PMG sandbox system")
|
||||
|
||||
// Test deny default - it should contain a message tag
|
||||
assert.Contains(t, actual, "(deny default (with message")
|
||||
|
||||
// Allow reading /dev and /etc
|
||||
assert.Contains(t, actual, "(allow file-read* (subpath \"/dev\"))")
|
||||
assert.Contains(t, actual, "(allow file-read* (subpath \"/etc\"))")
|
||||
|
||||
// Allow process fork
|
||||
assert.Contains(t, actual, "(allow process-fork)")
|
||||
}
|
||||
|
||||
func TestSeatbeltTranslatorDarwinFilesystemTranslation(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
policy *sandbox.SandboxPolicy
|
||||
assert func(t *testing.T, actual string, err error)
|
||||
}{
|
||||
{
|
||||
name: "simple path",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowRead: []string{"/tmp"},
|
||||
AllowWrite: []string{"/tmp"},
|
||||
DenyRead: []string{"/private/var"},
|
||||
DenyWrite: []string{"/private/var"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, actual, "(allow file-read* (subpath \"/tmp\"))")
|
||||
assert.Contains(t, actual, "(allow file-write* (subpath \"/tmp\"))")
|
||||
assert.Contains(t, actual, "(deny file-read* (subpath \"/private/var\") (with message")
|
||||
assert.Contains(t, actual, "(deny file-write* (subpath \"/private/var\") (with message")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob pattern with /**",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowRead: []string{"/path/to/dir/**"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should generate literal rule for parent directory
|
||||
assert.Contains(t, actual, `(allow file-read* (literal "/path/to/dir"))`)
|
||||
// Should use regex matching for glob patterns
|
||||
assert.Contains(t, actual, "(allow file-read* (regex")
|
||||
assert.Contains(t, actual, "^/path/to/dir/.*$")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob pattern with *.txt",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowRead: []string{"/path/to/*.txt"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should use regex matching for glob patterns
|
||||
assert.Contains(t, actual, "(allow file-read* (regex")
|
||||
assert.Contains(t, actual, `^/path/to/[^/]*\.txt$`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob pattern with ? wildcard",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowWrite: []string{"/path/to/file?.log"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should use regex matching for glob patterns
|
||||
assert.Contains(t, actual, "(allow file-write* (regex")
|
||||
assert.Contains(t, actual, `^/path/to/file[^/]\.log$`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob pattern in middle of path",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
DenyRead: []string{"/path/*/subdir"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should use regex matching for glob patterns
|
||||
assert.Contains(t, actual, "(deny file-read* (regex")
|
||||
assert.Contains(t, actual, `^/path/[^/]*/subdir$`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob pattern with bracket wildcard",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowRead: []string{"/tmp/test[123].txt"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should use regex matching for glob patterns
|
||||
assert.Contains(t, actual, "(allow file-read* (regex")
|
||||
assert.Contains(t, actual, `^/tmp/test[123]\.txt$`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "write glob pattern ending with /** auto-allows parent dir",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowWrite: []string{"/path/to/dir/**"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should generate literal rule for parent directory
|
||||
assert.Contains(t, actual, `(allow file-write* (literal "/path/to/dir"))`)
|
||||
// Should also generate regex rule for contents
|
||||
assert.Contains(t, actual, `(allow file-write* (regex "^/path/to/dir/.*$"))`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "write glob pattern with ** in middle not auto-allowed",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowWrite: []string{"/path/**/subdir"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should only generate regex rule, no literal parent
|
||||
assert.Contains(t, actual, `(allow file-write* (regex`)
|
||||
assert.NotContains(t, actual, `(allow file-write* (literal "/path")`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "write glob pattern with variable expansion and /**",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowWrite: []string{"${HOME}/.npm/**"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
homeDir := os.Getenv("HOME")
|
||||
expectedLiteral := fmt.Sprintf(`(allow file-write* (literal "%s/.npm"))`, homeDir)
|
||||
// Check for the literal parent directory rule
|
||||
assert.Contains(t, actual, expectedLiteral)
|
||||
// Check for the regex rule (escaped dot)
|
||||
assert.Contains(t, actual, `\.npm/.*$`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple /** patterns all auto-allow parents",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowWrite: []string{
|
||||
"/path/one/**",
|
||||
"/path/two/**",
|
||||
"/path/three/**",
|
||||
},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, actual, `(allow file-write* (literal "/path/one"))`)
|
||||
assert.Contains(t, actual, `(allow file-write* (literal "/path/two"))`)
|
||||
assert.Contains(t, actual, `(allow file-write* (literal "/path/three"))`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "edge case /** pattern at root",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowWrite: []string{"/**"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should allow writing to root (empty string after trimming becomes "/")
|
||||
assert.Contains(t, actual, `(allow file-write* (literal "/"))`)
|
||||
assert.Contains(t, actual, `(allow file-write* (regex "^/.*$"))`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dangerous file paths are blocked by default",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
DenyRead: []string{"/tmp/does-not-exist"},
|
||||
DenyWrite: []string{"/tmp/does-not-exist"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Contains(t, actual, fmt.Sprintf("(deny file-read* (subpath \"%s/.ssh\") (with message", homeDir))
|
||||
assert.Contains(t, actual, fmt.Sprintf("(deny file-write* (subpath \"%s/.ssh\") (with message", homeDir))
|
||||
assert.Contains(t, actual, fmt.Sprintf("(deny file-read* (subpath \"%s/.kube\") (with message", homeDir))
|
||||
assert.Contains(t, actual, fmt.Sprintf("(deny file-write* (subpath \"%s/.kube\") (with message", homeDir))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
translator := newSeatbeltPolicyTranslator()
|
||||
actual, err := translator.translate(tt.policy)
|
||||
tt.assert(t, actual, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeatbeltTranslatorDarwinProcessTranslation(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
policy *sandbox.SandboxPolicy
|
||||
assert func(t *testing.T, actual string, err error)
|
||||
}{
|
||||
{
|
||||
name: "literal exec path",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Process: sandbox.ProcessPolicy{
|
||||
AllowExec: []string{"/bin/sh"},
|
||||
DenyExec: []string{"/bin/bash"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, actual, "(allow process-exec* (literal \"/bin/sh\"))")
|
||||
assert.Contains(t, actual, "(deny process-exec* (literal \"/bin/bash\") (with message")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob pattern with /** for exec",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Process: sandbox.ProcessPolicy{
|
||||
AllowExec: []string{"/usr/local/bin/**"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should use regex matching for glob patterns
|
||||
assert.Contains(t, actual, "(allow process-exec* (regex")
|
||||
assert.Contains(t, actual, "^/usr/local/bin/.*$")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob pattern with * wildcard for exec",
|
||||
policy: &sandbox.SandboxPolicy{
|
||||
Process: sandbox.ProcessPolicy{
|
||||
AllowExec: []string{"/usr/bin/python*"},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, actual string, err error) {
|
||||
assert.NoError(t, err)
|
||||
// Should use regex matching for glob patterns
|
||||
assert.Contains(t, actual, "(allow process-exec* (regex")
|
||||
assert.Contains(t, actual, `^/usr/bin/python[^/]*$`)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
translator := newSeatbeltPolicyTranslator()
|
||||
actual, err := translator.translate(tt.policy)
|
||||
tt.assert(t, actual, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractBaseDir(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
pattern string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no glob pattern",
|
||||
pattern: "/path/to/file",
|
||||
expected: "/path/to/file",
|
||||
},
|
||||
{
|
||||
name: "glob pattern with /**",
|
||||
pattern: "/path/to/**",
|
||||
expected: "/path/to",
|
||||
},
|
||||
{
|
||||
name: "glob pattern with *.txt",
|
||||
pattern: "/path/to/*.txt",
|
||||
expected: "/path/to",
|
||||
},
|
||||
{
|
||||
name: "glob pattern with ? wildcard",
|
||||
pattern: "/path/to/file?.log",
|
||||
expected: "/path/to",
|
||||
},
|
||||
{
|
||||
name: "glob pattern in middle of path",
|
||||
pattern: "/path/*/subdir",
|
||||
expected: "/path",
|
||||
},
|
||||
{
|
||||
name: "glob pattern with bracket wildcard",
|
||||
pattern: "/tmp/test[123].txt",
|
||||
expected: "/tmp",
|
||||
},
|
||||
{
|
||||
name: "glob pattern at root",
|
||||
pattern: "/*.txt",
|
||||
expected: "/",
|
||||
},
|
||||
{
|
||||
name: "multiple glob patterns",
|
||||
pattern: "/path/*/sub/*.txt",
|
||||
expected: "/path",
|
||||
},
|
||||
{
|
||||
name: "complex glob with multiple wildcards",
|
||||
pattern: "/usr/bin/python*",
|
||||
expected: "/usr/bin",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := extractBaseDir(tt.pattern)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAncestorDirectories(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "deep path",
|
||||
path: "/private/tmp/test/file.txt",
|
||||
expected: []string{"/private/tmp/test", "/private/tmp", "/private"},
|
||||
},
|
||||
{
|
||||
name: "two level path",
|
||||
path: "/tmp/file.txt",
|
||||
expected: []string{"/tmp"},
|
||||
},
|
||||
{
|
||||
name: "root level path",
|
||||
path: "/file.txt",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "directory path",
|
||||
path: "/usr/local/bin",
|
||||
expected: []string{"/usr/local", "/usr"},
|
||||
},
|
||||
{
|
||||
name: "path with trailing slash",
|
||||
path: "/path/to/dir/",
|
||||
expected: []string{"/path/to/dir", "/path/to", "/path"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := getAncestorDirectories(tt.path)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateMoveBlockingRules(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
patterns []string
|
||||
logTag string
|
||||
assert func(t *testing.T, rules []string)
|
||||
}{
|
||||
{
|
||||
name: "single literal path",
|
||||
patterns: []string{"/sensitive/data"},
|
||||
logTag: "test",
|
||||
assert: func(t *testing.T, rules []string) {
|
||||
// Should block moving the path itself
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/sensitive/data\") (with message \"test\"))")
|
||||
// Should block moving the parent directory
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/sensitive\") (with message \"test\"))")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob pattern",
|
||||
patterns: []string{"/path/to/*.txt"},
|
||||
logTag: "test",
|
||||
assert: func(t *testing.T, rules []string) {
|
||||
// Should block moving the base directory
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/path/to\") (with message \"test\"))")
|
||||
// Should block moving ancestor directories
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/path\") (with message \"test\"))")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple paths",
|
||||
patterns: []string{"/tmp/test", "/var/log/app"},
|
||||
logTag: "test",
|
||||
assert: func(t *testing.T, rules []string) {
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/tmp/test\") (with message \"test\"))")
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/tmp\") (with message \"test\"))")
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/var/log/app\") (with message \"test\"))")
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/var/log\") (with message \"test\"))")
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/var\") (with message \"test\"))")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deep nested path",
|
||||
patterns: []string{"/a/b/c/d/e/file.txt"},
|
||||
logTag: "test",
|
||||
assert: func(t *testing.T, rules []string) {
|
||||
// Should have rules for all ancestors
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a/b/c/d/e\") (with message \"test\"))")
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a/b/c/d\") (with message \"test\"))")
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a/b/c\") (with message \"test\"))")
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a/b\") (with message \"test\"))")
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a\") (with message \"test\"))")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "root level path",
|
||||
patterns: []string{"/file"},
|
||||
logTag: "test",
|
||||
assert: func(t *testing.T, rules []string) {
|
||||
// Should only have the file itself, no ancestors
|
||||
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/file\") (with message \"test\"))")
|
||||
// Should not contain root as ancestor
|
||||
for _, rule := range rules {
|
||||
assert.NotContains(t, rule, "(deny file-write-unlink (literal \"/\"))")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rules := generateMoveBlockingRules(tt.patterns, tt.logTag)
|
||||
tt.assert(t, rules)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilesystemTranslationWithMoveProtection(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Name: "test",
|
||||
Description: "test with move protection",
|
||||
PackageManagers: []string{"npm"},
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
DenyRead: []string{"/private/sensitive"},
|
||||
DenyWrite: []string{"/usr/local/bin"},
|
||||
},
|
||||
}
|
||||
|
||||
translator := newSeatbeltPolicyTranslator()
|
||||
translator.enableMoveBlockingMitigation = true
|
||||
|
||||
actual, err := translator.translate(policy)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Should contain deny read rule
|
||||
assert.Contains(t, actual, "(deny file-read* (subpath \"/private/sensitive\") (with message")
|
||||
|
||||
// Should contain move protection for deny read
|
||||
assert.Contains(t, actual, ";; Prevent bypassing read restrictions via file movement")
|
||||
assert.Contains(t, actual, "(deny file-write-unlink (subpath \"/private/sensitive\") (with message")
|
||||
assert.Contains(t, actual, "(deny file-write-unlink (literal \"/private\") (with message")
|
||||
|
||||
// Should contain deny write rule
|
||||
assert.Contains(t, actual, "(deny file-write* (subpath \"/usr/local/bin\") (with message")
|
||||
|
||||
// Should contain move protection for deny write
|
||||
assert.Contains(t, actual, ";; Prevent bypassing write restrictions via file movement")
|
||||
assert.Contains(t, actual, "(deny file-write-unlink (subpath \"/usr/local/bin\") (with message")
|
||||
assert.Contains(t, actual, "(deny file-write-unlink (literal \"/usr/local\") (with message")
|
||||
assert.Contains(t, actual, "(deny file-write-unlink (literal \"/usr\") (with message")
|
||||
}
|
||||
|
||||
func TestPTYSupport(t *testing.T) {
|
||||
t.Run("PTY disabled by default", func(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Name: "test",
|
||||
Description: "test without PTY",
|
||||
PackageManagers: []string{"npm"},
|
||||
AllowPTY: false,
|
||||
}
|
||||
|
||||
translator := newSeatbeltPolicyTranslator()
|
||||
actual, err := translator.translate(policy)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Should NOT contain PTY rules
|
||||
assert.NotContains(t, actual, "(allow pseudo-tty)")
|
||||
assert.NotContains(t, actual, "/dev/ptmx")
|
||||
})
|
||||
|
||||
t.Run("PTY enabled when requested", func(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Name: "test",
|
||||
Description: "test with PTY",
|
||||
PackageManagers: []string{"npm"},
|
||||
AllowPTY: true,
|
||||
}
|
||||
|
||||
translator := newSeatbeltPolicyTranslator()
|
||||
actual, err := translator.translate(policy)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Should contain PTY rules
|
||||
assert.Contains(t, actual, ";; Pseudo-terminal (PTY) support")
|
||||
assert.Contains(t, actual, "(allow pseudo-tty)")
|
||||
assert.Contains(t, actual, "(allow file-ioctl")
|
||||
assert.Contains(t, actual, "(literal \"/dev/ptmx\")")
|
||||
assert.Contains(t, actual, "(regex #\"^/dev/ttys\")")
|
||||
assert.Contains(t, actual, "(allow file-read* file-write*")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSeatbeltTranslatorDarwinLogTag(t *testing.T) {
|
||||
translator := newSeatbeltPolicyTranslator()
|
||||
assert.NotEmpty(t, translator.LogTag())
|
||||
|
||||
translator = &seatbeltPolicyTranslator{logTag: "test"}
|
||||
assert.Equal(t, "test", translator.LogTag())
|
||||
}
|
||||
Reference in New Issue
Block a user