feat: Add support for sandbox allow override (#165)

* feat: Add support for sandbox allow override

* fix: Main should fail on arg processing error

* fix: Remove redundant policy conflict check
This commit is contained in:
Abhisek Datta
2026-02-16 13:58:08 +05:30
committed by GitHub
parent d3cb15a3ea
commit 6074080219
9 changed files with 798 additions and 2 deletions
+54
View File
@@ -7,7 +7,9 @@ import (
"path/filepath"
"github.com/safedep/dry/log"
"github.com/safedep/dry/utils"
"github.com/safedep/pmg/config"
"github.com/safedep/pmg/internal/eventlog"
"github.com/safedep/pmg/sandbox"
"github.com/safedep/pmg/sandbox/platform"
"github.com/safedep/pmg/usefulerror"
@@ -114,6 +116,12 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...app
log.Debugf("Loaded sandbox policy %s", policy.Name)
// Apply runtime --sandbox-allow overrides to the policy before execution
if len(cfg.SandboxAllowOverrides) > 0 {
applyRuntimeOverrides(policy, cfg.SandboxAllowOverrides)
logSandboxOverridesToEventLog(policy.Name, cfg.SandboxAllowOverrides)
}
if !policy.AppliesToPackageManager(pmName) {
return nil, fmt.Errorf("sandbox policy %s does not apply to %s", policy.Name, pmName)
}
@@ -146,3 +154,49 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...app
return result, nil
}
// applyRuntimeOverrides applies --sandbox-allow overrides to the policy.
// Overrides are additive — they only append to allow lists, never modify deny lists.
// Warnings are logged for conflicts with deny rules and mandatory deny patterns.
func applyRuntimeOverrides(policy *sandbox.SandboxPolicy, overrides []config.SandboxAllowOverride) {
for _, override := range overrides {
switch override.Type {
case config.SandboxAllowRead:
log.Infof("Sandbox override: allowing read access to %s", override.Value)
policy.Filesystem.AllowRead = append(policy.Filesystem.AllowRead, override.Value)
case config.SandboxAllowWrite:
log.Infof("Sandbox override: allowing write access to %s", override.Value)
policy.Filesystem.AllowWrite = append(policy.Filesystem.AllowWrite, override.Value)
case config.SandboxAllowExec:
log.Infof("Sandbox override: allowing execution of %s", override.Value)
policy.Process.AllowExec = append(policy.Process.AllowExec, override.Value)
case config.SandboxAllowNetConnect:
log.Infof("Sandbox override: allowing outbound connection to %s", override.Value)
policy.Network.AllowOutbound = append(policy.Network.AllowOutbound, override.Value)
case config.SandboxAllowNetBind:
log.Infof("Sandbox override: allowing network bind on %s", override.Value)
policy.Network.AllowBind = append(policy.Network.AllowBind, override.Value)
// Enable AllowNetworkBind so the translator emits bind rules.
// Without this, AllowBind entries would be ignored on some platforms.
policy.AllowNetworkBind = utils.PtrTo(true)
}
}
}
// logSandboxOverridesToEventLog records sandbox allow overrides in the audit event log.
func logSandboxOverridesToEventLog(profileName string, overrides []config.SandboxAllowOverride) {
entries := make([]map[string]string, 0, len(overrides))
for _, o := range overrides {
entries = append(entries, map[string]string{
"type": string(o.Type),
"value": o.Value,
})
}
eventlog.LogSandboxOverrides(profileName, entries)
}
+165
View File
@@ -0,0 +1,165 @@
package executor
import (
"testing"
"github.com/safedep/dry/utils"
"github.com/safedep/pmg/config"
"github.com/safedep/pmg/sandbox"
"github.com/stretchr/testify/assert"
)
func TestApplyRuntimeOverrides_Read(t *testing.T) {
policy := &sandbox.SandboxPolicy{
Filesystem: sandbox.FilesystemPolicy{
AllowRead: []string{"/existing"},
},
}
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
{Type: config.SandboxAllowRead, Value: "/new/path", Raw: "read=/new/path"},
})
assert.Contains(t, policy.Filesystem.AllowRead, "/existing")
assert.Contains(t, policy.Filesystem.AllowRead, "/new/path")
}
func TestApplyRuntimeOverrides_Write(t *testing.T) {
policy := &sandbox.SandboxPolicy{
Filesystem: sandbox.FilesystemPolicy{
AllowWrite: []string{"/existing"},
},
}
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
{Type: config.SandboxAllowWrite, Value: "/new/file", Raw: "write=/new/file"},
})
assert.Contains(t, policy.Filesystem.AllowWrite, "/existing")
assert.Contains(t, policy.Filesystem.AllowWrite, "/new/file")
}
func TestApplyRuntimeOverrides_Exec(t *testing.T) {
policy := &sandbox.SandboxPolicy{
Process: sandbox.ProcessPolicy{
AllowExec: []string{"/usr/bin/node"},
},
}
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
{Type: config.SandboxAllowExec, Value: "/usr/bin/curl", Raw: "exec=/usr/bin/curl"},
})
assert.Contains(t, policy.Process.AllowExec, "/usr/bin/node")
assert.Contains(t, policy.Process.AllowExec, "/usr/bin/curl")
}
func TestApplyRuntimeOverrides_NetConnect(t *testing.T) {
policy := &sandbox.SandboxPolicy{
Network: sandbox.NetworkPolicy{
AllowOutbound: []string{"registry.npmjs.org:443"},
},
}
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
{Type: config.SandboxAllowNetConnect, Value: "example.com:443", Raw: "net-connect=example.com:443"},
})
assert.Contains(t, policy.Network.AllowOutbound, "registry.npmjs.org:443")
assert.Contains(t, policy.Network.AllowOutbound, "example.com:443")
}
func TestApplyRuntimeOverrides_NetBind(t *testing.T) {
policy := &sandbox.SandboxPolicy{
Network: sandbox.NetworkPolicy{
AllowBind: []string{},
},
}
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
{Type: config.SandboxAllowNetBind, Value: "127.0.0.1:3000", Raw: "net-bind=127.0.0.1:3000"},
})
assert.Contains(t, policy.Network.AllowBind, "127.0.0.1:3000")
assert.NotNil(t, policy.AllowNetworkBind)
assert.True(t, *policy.AllowNetworkBind)
}
func TestApplyRuntimeOverrides_NetBindPreservesExistingTrue(t *testing.T) {
policy := &sandbox.SandboxPolicy{
AllowNetworkBind: utils.PtrTo(true),
Network: sandbox.NetworkPolicy{
AllowBind: []string{"localhost:8080"},
},
}
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
{Type: config.SandboxAllowNetBind, Value: "127.0.0.1:3000", Raw: "net-bind=127.0.0.1:3000"},
})
assert.Contains(t, policy.Network.AllowBind, "localhost:8080")
assert.Contains(t, policy.Network.AllowBind, "127.0.0.1:3000")
assert.True(t, *policy.AllowNetworkBind)
}
func TestApplyRuntimeOverrides_MultipleOverrides(t *testing.T) {
policy := &sandbox.SandboxPolicy{
Filesystem: sandbox.FilesystemPolicy{},
Process: sandbox.ProcessPolicy{},
Network: sandbox.NetworkPolicy{},
}
overrides := []config.SandboxAllowOverride{
{Type: config.SandboxAllowWrite, Value: "/path/a", Raw: "write=/path/a"},
{Type: config.SandboxAllowWrite, Value: "/path/b", Raw: "write=/path/b"},
{Type: config.SandboxAllowExec, Value: "/usr/bin/curl", Raw: "exec=/usr/bin/curl"},
{Type: config.SandboxAllowNetConnect, Value: "example.com:443", Raw: "net-connect=example.com:443"},
}
applyRuntimeOverrides(policy, overrides)
assert.Len(t, policy.Filesystem.AllowWrite, 2)
assert.Len(t, policy.Process.AllowExec, 1)
assert.Len(t, policy.Network.AllowOutbound, 1)
}
func TestApplyRuntimeOverrides_EmptyOverrides(t *testing.T) {
policy := &sandbox.SandboxPolicy{
Filesystem: sandbox.FilesystemPolicy{
AllowWrite: []string{"/existing"},
},
}
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{})
// Policy should be unchanged
assert.Equal(t, []string{"/existing"}, policy.Filesystem.AllowWrite)
}
func TestApplyRuntimeOverrides_DenyListsUnmodified(t *testing.T) {
policy := &sandbox.SandboxPolicy{
Filesystem: sandbox.FilesystemPolicy{
DenyWrite: []string{"/protected"},
},
Process: sandbox.ProcessPolicy{
DenyExec: []string{"/usr/bin/curl"},
},
Network: sandbox.NetworkPolicy{
DenyOutbound: []string{"*:*"},
},
}
overrides := []config.SandboxAllowOverride{
{Type: config.SandboxAllowWrite, Value: "/something", Raw: "write=/something"},
{Type: config.SandboxAllowExec, Value: "/usr/bin/wget", Raw: "exec=/usr/bin/wget"},
{Type: config.SandboxAllowNetConnect, Value: "example.com:443", Raw: "net-connect=example.com:443"},
}
applyRuntimeOverrides(policy, overrides)
// Deny lists should never be modified by overrides
assert.Equal(t, []string{"/protected"}, policy.Filesystem.DenyWrite)
assert.Equal(t, []string{"/usr/bin/curl"}, policy.Process.DenyExec)
assert.Equal(t, []string{"*:*"}, policy.Network.DenyOutbound)
}