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,302 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGlobToRegex(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
pattern string
|
||||
shouldMatch []string
|
||||
shouldNotMatch []string
|
||||
}{
|
||||
{
|
||||
name: "simple asterisk wildcard",
|
||||
pattern: "/path/to/*.txt",
|
||||
shouldMatch: []string{
|
||||
"/path/to/file.txt",
|
||||
"/path/to/test.txt",
|
||||
"/path/to/a.txt",
|
||||
"/path/to/.txt", // * matches zero or more chars (standard glob behavior)
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/path/to/file.log",
|
||||
"/path/to/sub/file.txt",
|
||||
"/path/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "globstar pattern",
|
||||
pattern: "/path/**/*.txt",
|
||||
shouldMatch: []string{
|
||||
"/path/file.txt",
|
||||
"/path/to/file.txt",
|
||||
"/path/to/sub/file.txt",
|
||||
"/path/a/b/c/file.txt",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/path/file.log",
|
||||
"/other/path/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "globstar with trailing slash",
|
||||
pattern: "/src/**/",
|
||||
shouldMatch: []string{
|
||||
"/src/",
|
||||
"/src/a/",
|
||||
"/src/a/b/",
|
||||
"/src/deep/nested/path/",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/src",
|
||||
"/other/",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "question mark wildcard",
|
||||
pattern: "/tmp/file?.log",
|
||||
shouldMatch: []string{
|
||||
"/tmp/file1.log",
|
||||
"/tmp/file2.log",
|
||||
"/tmp/filea.log",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/tmp/file.log",
|
||||
"/tmp/file12.log",
|
||||
"/tmp/file/.log",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bracket wildcard",
|
||||
pattern: "/tmp/test[123].txt",
|
||||
shouldMatch: []string{
|
||||
"/tmp/test1.txt",
|
||||
"/tmp/test2.txt",
|
||||
"/tmp/test3.txt",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/tmp/test4.txt",
|
||||
"/tmp/testa.txt",
|
||||
"/tmp/test.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bracket range",
|
||||
pattern: "/tmp/file[0-9].log",
|
||||
shouldMatch: []string{
|
||||
"/tmp/file0.log",
|
||||
"/tmp/file5.log",
|
||||
"/tmp/file9.log",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/tmp/filea.log",
|
||||
"/tmp/file10.log",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regex special characters escaped",
|
||||
pattern: "/path/to/file.txt",
|
||||
shouldMatch: []string{
|
||||
"/path/to/file.txt",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/path/to/fileXtxt",
|
||||
"/path/to/file_txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple wildcards",
|
||||
pattern: "/path/*/sub/*.txt",
|
||||
shouldMatch: []string{
|
||||
"/path/a/sub/file.txt",
|
||||
"/path/b/sub/test.txt",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/path/sub/file.txt",
|
||||
"/path/a/sub/deep/file.txt",
|
||||
"/path/a/b/sub/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "globstar in middle",
|
||||
pattern: "/usr/**/bin/node",
|
||||
shouldMatch: []string{
|
||||
"/usr/bin/node",
|
||||
"/usr/local/bin/node",
|
||||
"/usr/a/b/c/bin/node",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/usr/node",
|
||||
"/usr/bin/npm",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exact path (no wildcards)",
|
||||
pattern: "/etc/passwd",
|
||||
shouldMatch: []string{
|
||||
"/etc/passwd",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/etc/passwd.bak",
|
||||
"/etc/shadow",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wildcard at beginning",
|
||||
pattern: "*.txt",
|
||||
shouldMatch: []string{
|
||||
"file.txt",
|
||||
"test.txt",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"file.log",
|
||||
"dir/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complex pattern with parens and dots",
|
||||
pattern: "/path/to/file(1).txt",
|
||||
shouldMatch: []string{
|
||||
"/path/to/file(1).txt",
|
||||
},
|
||||
shouldNotMatch: []string{
|
||||
"/path/to/file1.txt",
|
||||
"/path/to/file(1)Xtxt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
regexPattern := GlobToRegex(tt.pattern)
|
||||
re, err := regexp.Compile(regexPattern)
|
||||
assert.NoError(t, err, "Generated regex should be valid")
|
||||
|
||||
for _, path := range tt.shouldMatch {
|
||||
assert.True(t, re.MatchString(path), "Pattern %s should match %s (regex: %s)", tt.pattern, path, regexPattern)
|
||||
}
|
||||
|
||||
for _, path := range tt.shouldNotMatch {
|
||||
assert.False(t, re.MatchString(path), "Pattern %s should not match %s (regex: %s)", tt.pattern, path, regexPattern)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobToRegexPatterns(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
pattern string
|
||||
expectedRegex string
|
||||
}{
|
||||
{
|
||||
name: "simple asterisk",
|
||||
pattern: "*.txt",
|
||||
expectedRegex: `^[^/]*\.txt$`,
|
||||
},
|
||||
{
|
||||
name: "globstar",
|
||||
pattern: "**/*.txt",
|
||||
expectedRegex: `^(.*/)?[^/]*\.txt$`,
|
||||
},
|
||||
{
|
||||
name: "question mark",
|
||||
pattern: "file?.txt",
|
||||
expectedRegex: `^file[^/]\.txt$`,
|
||||
},
|
||||
{
|
||||
name: "absolute path with wildcard",
|
||||
pattern: "/path/to/*.txt",
|
||||
expectedRegex: `^/path/to/[^/]*\.txt$`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := GlobToRegex(tt.pattern)
|
||||
assert.Equal(t, tt.expectedRegex, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeRegexChars(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "dot escaping",
|
||||
input: "file.txt",
|
||||
expected: `file\.txt`,
|
||||
},
|
||||
{
|
||||
name: "multiple special chars",
|
||||
input: "file(1).txt",
|
||||
expected: `file\(1\)\.txt`,
|
||||
},
|
||||
{
|
||||
name: "glob chars not escaped but dots are",
|
||||
input: "*.txt",
|
||||
expected: `*\.txt`,
|
||||
},
|
||||
{
|
||||
name: "brackets not escaped",
|
||||
input: "[0-9]",
|
||||
expected: "[0-9]",
|
||||
},
|
||||
{
|
||||
name: "question mark not escaped but dots are",
|
||||
input: "file?.txt",
|
||||
expected: `file?\.txt`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := escapeRegexChars(tt.input)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeUnclosedBrackets(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "unclosed bracket at end",
|
||||
input: "test[abc",
|
||||
expected: `test\[abc`,
|
||||
},
|
||||
{
|
||||
name: "closed bracket",
|
||||
input: "test[abc]",
|
||||
expected: "test[abc]",
|
||||
},
|
||||
{
|
||||
name: "no brackets",
|
||||
input: "test",
|
||||
expected: "test",
|
||||
},
|
||||
{
|
||||
name: "multiple closed brackets",
|
||||
input: "[a-z][0-9]",
|
||||
expected: "[a-z][0-9]",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := escapeUnclosedBrackets(tt.input)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user