package sandbox import ( "errors" "os" "testing" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" ) func TestNewDefaultProfileRegistry(t *testing.T) { registry, err := newDefaultProfileRegistry() assert.NoError(t, err) assert.NotNil(t, registry) assert.Greater(t, len(registry.profiles), 0) npmRestrictive, err := registry.GetProfile("npm-restrictive") assert.NoError(t, err) assert.NotNil(t, npmRestrictive) pypiRestrictive, err := registry.GetProfile("pypi-restrictive") assert.NoError(t, err) assert.NotNil(t, pypiRestrictive) } func TestGetProfileUnknownWrapsErrProfileNotFound(t *testing.T) { registry, err := newDefaultProfileRegistry() assert.NoError(t, err) _, err = registry.GetProfile("definitely-not-a-real-profile-name") assert.Error(t, err) assert.True(t, errors.Is(err, ErrProfileNotFound), "unknown profile should wrap ErrProfileNotFound, got %T: %v", err, err) } func TestLoadCustomProfileInvalidYAMLWrapsErrProfileInvalid(t *testing.T) { registry, err := newDefaultProfileRegistry() assert.NoError(t, err) tempFile, err := os.CreateTemp(t.TempDir(), "bad-policy-*.yml") assert.NoError(t, err) defer func() { _ = tempFile.Close() }() _, err = tempFile.WriteString("name: bad\npackage_managers:\n - npm\n invalid: : :\n") assert.NoError(t, err) _, err = registry.LoadCustomProfile(tempFile.Name()) assert.Error(t, err) assert.True(t, errors.Is(err, ErrProfileInvalid), "YAML parse failure should wrap ErrProfileInvalid, got %T: %v", err, err) } func TestLoadCustomProfile(t *testing.T) { cases := []struct { name string policy *SandboxPolicy assert func(t *testing.T, policy *SandboxPolicy, err error) }{ { name: "valid policy", policy: &SandboxPolicy{ Name: "test", Description: "test", PackageManagers: []string{"npm"}, // At least some rules are required for a policy to be valid Filesystem: FilesystemPolicy{ AllowRead: []string{"/tmp"}, AllowWrite: []string{"/tmp"}, DenyRead: []string{"/private/var"}, DenyWrite: []string{"/private/var"}, }, }, assert: func(t *testing.T, policy *SandboxPolicy, err error) { assert.NoError(t, err) assert.NotNil(t, policy) assert.Equal(t, "test", policy.Name) assert.Equal(t, "test", policy.Description) assert.Equal(t, []string{"npm"}, policy.PackageManagers) assert.Equal(t, FilesystemPolicy{ AllowRead: []string{"/tmp"}, AllowWrite: []string{"/tmp"}, DenyRead: []string{"/private/var"}, DenyWrite: []string{"/private/var"}, }, policy.Filesystem) }, }, { name: "invalid policy without any rules", policy: &SandboxPolicy{ Name: "test", Description: "test", PackageManagers: []string{"npm"}, }, assert: func(t *testing.T, policy *SandboxPolicy, err error) { assert.Error(t, err) assert.Nil(t, policy) }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { registry, err := newDefaultProfileRegistry() assert.NoError(t, err) assert.NotNil(t, registry) tempFile, err := os.CreateTemp(t.TempDir(), "sandbox-policy-*.yml") assert.NoError(t, err) defer func() { _ = tempFile.Close() }() err = yaml.NewEncoder(tempFile).Encode(c.policy) assert.NoError(t, err) policy, err := registry.LoadCustomProfile(tempFile.Name()) c.assert(t, policy, err) }) } } func TestBuiltinProfileInheritance(t *testing.T) { registry, err := newDefaultProfileRegistry() assert.NoError(t, err) assert.NotNil(t, registry) // Get the npx profile which inherits from npm-restrictive npxProfile, err := registry.GetProfile("npx") assert.NoError(t, err) assert.NotNil(t, npxProfile) // Verify the inherits field is cleared after resolution assert.Empty(t, npxProfile.Inherits) // Verify package managers are from child (not parent) assert.ElementsMatch(t, []string{"npx", "pnpx"}, npxProfile.PackageManagers) // Get the parent profile for comparison npmRestrictive, err := registry.GetProfile("npm-restrictive") assert.NoError(t, err) assert.NotNil(t, npmRestrictive) // Verify that child has parent's rules // Check that some parent rules are present assert.Contains(t, npxProfile.Filesystem.AllowRead, "/") assert.Contains(t, npxProfile.Filesystem.AllowRead, "/usr/**") assert.Contains(t, npxProfile.Filesystem.AllowWrite, "/tmp/**") // Verify that child has its own rules assert.Contains(t, npxProfile.Filesystem.AllowWrite, "${CWD}/**") assert.Contains(t, npxProfile.Filesystem.DenyWrite, "${CWD}/.env") } func TestLoadCustomProfileWithInheritance(t *testing.T) { tests := []struct { name string policy *SandboxPolicy assert func(t *testing.T, policy *SandboxPolicy, err error) }{ { name: "custom profile inherits from npm-restrictive", policy: &SandboxPolicy{ Name: "custom-npm", Description: "Custom npm policy", Inherits: "npm-restrictive", PackageManagers: []string{"npm"}, Filesystem: FilesystemPolicy{ AllowWrite: []string{"/custom/path/**"}, }, }, assert: func(t *testing.T, policy *SandboxPolicy, err error) { assert.NoError(t, err) assert.NotNil(t, policy) // Verify inheritance was resolved assert.Empty(t, policy.Inherits) // Verify parent rules are present assert.Contains(t, policy.Filesystem.AllowRead, "/") assert.Contains(t, policy.Filesystem.AllowRead, "/usr/**") // Verify child rules are added assert.Contains(t, policy.Filesystem.AllowWrite, "/custom/path/**") assert.Contains(t, policy.Filesystem.AllowWrite, "/tmp/**") }, }, { name: "custom profile inherits from already-resolved profile (npx)", policy: &SandboxPolicy{ Name: "custom-chain", Description: "Inherits from npx which already resolved its inheritance", Inherits: "npx", PackageManagers: []string{"npm"}, Filesystem: FilesystemPolicy{ AllowWrite: []string{"/custom/**"}, }, }, assert: func(t *testing.T, policy *SandboxPolicy, err error) { // This should succeed because npx has already resolved its inheritance // and no longer has an Inherits field set assert.NoError(t, err) assert.NotNil(t, policy) assert.Empty(t, policy.Inherits) }, }, { name: "custom profile inherits from non-existent profile", policy: &SandboxPolicy{ Name: "custom-bad", Description: "Should fail", Inherits: "does-not-exist", PackageManagers: []string{"npm"}, }, assert: func(t *testing.T, policy *SandboxPolicy, err error) { // This should fail because parent doesn't exist assert.Error(t, err) assert.Nil(t, policy) assert.Contains(t, err.Error(), "inherits from unknown profile") assert.True(t, errors.Is(err, ErrProfileNotFound), "missing parent profile should wrap ErrProfileNotFound") }, }, { name: "custom profile with multiple inherited and own rules", policy: &SandboxPolicy{ Name: "custom-extended", Description: "Extended npm profile", Inherits: "npm-restrictive", PackageManagers: []string{"npm", "yarn"}, Filesystem: FilesystemPolicy{ AllowRead: []string{"/opt/**"}, AllowWrite: []string{"/data/**", "/cache/**"}, DenyWrite: []string{"/data/secrets/**"}, }, Network: NetworkPolicy{ AllowOutbound: []string{"custom.registry.io:443"}, }, }, assert: func(t *testing.T, policy *SandboxPolicy, err error) { assert.NoError(t, err) assert.NotNil(t, policy) // Verify inheritance resolved assert.Empty(t, policy.Inherits) // Verify parent and child filesystem rules are merged assert.Contains(t, policy.Filesystem.AllowRead, "/") assert.Contains(t, policy.Filesystem.AllowRead, "/usr/**") assert.Contains(t, policy.Filesystem.AllowRead, "/opt/**") assert.Contains(t, policy.Filesystem.AllowWrite, "/tmp/**") assert.Contains(t, policy.Filesystem.AllowWrite, "/data/**") assert.Contains(t, policy.Filesystem.AllowWrite, "/cache/**") assert.Contains(t, policy.Filesystem.DenyWrite, "/etc/**") assert.Contains(t, policy.Filesystem.DenyWrite, "/data/secrets/**") // Verify network rules are merged assert.Contains(t, policy.Network.AllowOutbound, "registry.npmjs.org:443") assert.Contains(t, policy.Network.AllowOutbound, "custom.registry.io:443") }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { registry, err := newDefaultProfileRegistry() assert.NoError(t, err) assert.NotNil(t, registry) tempFile, err := os.CreateTemp(t.TempDir(), "custom-policy-*.yml") assert.NoError(t, err) defer func() { assert.NoError(t, tempFile.Close()) }() err = yaml.NewEncoder(tempFile).Encode(tt.policy) assert.NoError(t, err) loadedPolicy, err := registry.LoadCustomProfile(tempFile.Name()) tt.assert(t, loadedPolicy, err) }) } } func TestResolveInheritance(t *testing.T) { registry, err := newDefaultProfileRegistry() assert.NoError(t, err) assert.NotNil(t, registry) parent := &SandboxPolicy{ Name: "test-parent", PackageManagers: []string{"npm"}, Filesystem: FilesystemPolicy{ AllowRead: []string{"/parent/**"}, AllowWrite: []string{"/parent/write/**"}, }, } child := &SandboxPolicy{ Name: "test-child", Inherits: "test-parent", PackageManagers: []string{"npx"}, Filesystem: FilesystemPolicy{ AllowRead: []string{"/child/**"}, AllowWrite: []string{"/child/write/**"}, }, } // Add parent to registry registry.mu.Lock() registry.profiles["test-parent"] = parent registry.mu.Unlock() // Resolve inheritance registry.mu.Lock() err = registry.resolveInheritance(child) registry.mu.Unlock() assert.NoError(t, err) assert.Empty(t, child.Inherits) // Verify lists are merged assert.ElementsMatch(t, []string{"/parent/**", "/child/**"}, child.Filesystem.AllowRead) assert.ElementsMatch(t, []string{"/parent/write/**", "/child/write/**"}, child.Filesystem.AllowWrite) // Verify package managers are from child assert.Equal(t, []string{"npx"}, child.PackageManagers) }