mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Add support for per-project sandbox overlays (#294)
* feat: Add support for per-project sandbox overlays * chore: Maintain consistency with TUI experience * fix: Code review fixes
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/safedep/pmg/config"
|
||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type allowDeps struct {
|
||||
overlayDir string
|
||||
repoRoot string
|
||||
cache *pmgsandbox.ViolationCache
|
||||
}
|
||||
|
||||
func newAllowDeps(t *testing.T) *allowDeps {
|
||||
t.Helper()
|
||||
return &allowDeps{
|
||||
overlayDir: t.TempDir(),
|
||||
repoRoot: filepath.Clean(t.TempDir()),
|
||||
cache: pmgsandbox.NewViolationCache(t.TempDir()),
|
||||
}
|
||||
}
|
||||
|
||||
func runAllowCmd(t *testing.T, deps *allowDeps, args ...string) (string, string, error) {
|
||||
t.Helper()
|
||||
cmd := newAllowCommand(allowFactory{
|
||||
overlayDir: func() string { return deps.overlayDir },
|
||||
repoRoot: func() (string, error) { return deps.repoRoot, nil },
|
||||
cache: func() *pmgsandbox.ViolationCache { return deps.cache },
|
||||
})
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
return stdout.String(), stderr.String(), err
|
||||
}
|
||||
|
||||
func TestAllow_PositionalSaved(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
stdout, stderr, err := runAllowCmd(t, deps, "net-bind=localhost:4321")
|
||||
require.NoError(t, err, "stderr: %s", stderr)
|
||||
assert.Contains(t, stdout, "Saved 1 allowance")
|
||||
|
||||
overlay, _, err := pmgsandbox.LoadOverlayForRepo(deps.overlayDir, deps.repoRoot)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, overlay)
|
||||
require.Len(t, overlay.Allow, 1)
|
||||
assert.Equal(t, config.SandboxAllowNetBind, overlay.Allow[0].Type)
|
||||
assert.Equal(t, "localhost:4321", overlay.Allow[0].Value)
|
||||
}
|
||||
|
||||
func TestAllow_LastAllPromotesFromCache(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
writeTarget := filepath.Join(deps.repoRoot, ".astro")
|
||||
execTarget := "/usr/bin/sh"
|
||||
report := &pmgsandbox.ViolationReport{
|
||||
SandboxName: pmgsandbox.DriverSeatbelt,
|
||||
Violations: []pmgsandbox.Violation{
|
||||
{Kind: pmgsandbox.ViolationKindFSWrite, Target: writeTarget},
|
||||
{Kind: pmgsandbox.ViolationKindExec, Target: execTarget},
|
||||
},
|
||||
}
|
||||
_, err := deps.cache.Write(report)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, stderr, err := runAllowCmd(t, deps, "--last", "--all")
|
||||
require.NoError(t, err, "stderr: %s", stderr)
|
||||
|
||||
overlay, _, err := pmgsandbox.LoadOverlayForRepo(deps.overlayDir, deps.repoRoot)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, overlay)
|
||||
assert.ElementsMatch(t, []pmgsandbox.OverlayAllow{
|
||||
{Type: config.SandboxAllowWrite, Value: writeTarget},
|
||||
{Type: config.SandboxAllowExec, Value: execTarget},
|
||||
}, overlay.Allow)
|
||||
}
|
||||
|
||||
func TestAllow_LastPromotesPrimaryOnly(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
writeTarget := filepath.Join(deps.repoRoot, ".astro")
|
||||
execTarget := "/usr/bin/sh"
|
||||
report := &pmgsandbox.ViolationReport{
|
||||
SandboxName: pmgsandbox.DriverSeatbelt,
|
||||
Violations: []pmgsandbox.Violation{
|
||||
{Kind: pmgsandbox.ViolationKindFSWrite, Target: writeTarget},
|
||||
{Kind: pmgsandbox.ViolationKindExec, Target: execTarget},
|
||||
},
|
||||
}
|
||||
_, err := deps.cache.Write(report)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = runAllowCmd(t, deps, "--last")
|
||||
require.NoError(t, err)
|
||||
|
||||
overlay, _, err := pmgsandbox.LoadOverlayForRepo(deps.overlayDir, deps.repoRoot)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, overlay)
|
||||
require.Len(t, overlay.Allow, 1)
|
||||
}
|
||||
|
||||
func TestAllow_LastNoCacheErrors(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
_, _, err := runAllowCmd(t, deps, "--last")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAllow_RefusesSensitiveTargetWithoutForce(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
secret := filepath.Join(deps.repoRoot, ".env")
|
||||
_, _, err := runAllowCmd(t, deps, "read="+secret)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "sensitive target")
|
||||
|
||||
overlay, _, err := pmgsandbox.LoadOverlayForRepo(deps.overlayDir, deps.repoRoot)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, overlay)
|
||||
}
|
||||
|
||||
func TestAllow_SensitiveTargetWithForceSaves(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
secret := filepath.Join(deps.repoRoot, ".env")
|
||||
_, _, err := runAllowCmd(t, deps, "--force", "read="+secret)
|
||||
require.NoError(t, err)
|
||||
overlay, _, err := pmgsandbox.LoadOverlayForRepo(deps.overlayDir, deps.repoRoot)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, overlay)
|
||||
assert.Len(t, overlay.Allow, 1)
|
||||
}
|
||||
|
||||
func TestAllow_Dedupes(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
_, _, err := runAllowCmd(t, deps, "exec=/usr/bin/sh")
|
||||
require.NoError(t, err)
|
||||
stdout, _, err := runAllowCmd(t, deps, "exec=/usr/bin/sh")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, stdout, "No new allowances")
|
||||
|
||||
overlay, _, err := pmgsandbox.LoadOverlayForRepo(deps.overlayDir, deps.repoRoot)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, overlay)
|
||||
assert.Len(t, overlay.Allow, 1)
|
||||
}
|
||||
|
||||
func TestAllow_NothingToSaveErrors(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
_, _, err := runAllowCmd(t, deps)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAllow_AllWithoutLastErrors(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
_, _, err := runAllowCmd(t, deps, "--all", "exec=/usr/bin/sh")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAllow_InvalidPositionalRejected(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
_, _, err := runAllowCmd(t, deps, "garbage")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAllow_RefusedUnderLockdown(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
cmd := newAllowCommand(allowFactory{
|
||||
overlayDir: func() string { return deps.overlayDir },
|
||||
repoRoot: func() (string, error) { return deps.repoRoot, nil },
|
||||
cache: func() *pmgsandbox.ViolationCache { return deps.cache },
|
||||
locked: func() bool { return true },
|
||||
})
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stderr)
|
||||
cmd.SetArgs([]string{"exec=/usr/bin/sh"})
|
||||
err := cmd.Execute()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "global_lockdown")
|
||||
|
||||
overlay, _, err := pmgsandbox.LoadOverlayForRepo(deps.overlayDir, deps.repoRoot)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, overlay)
|
||||
}
|
||||
|
||||
func TestAllow_LastNormalizesRelativeTargets(t *testing.T) {
|
||||
deps := newAllowDeps(t)
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
relTarget := "./.astro"
|
||||
wantAbs := filepath.Join(cwd, ".astro")
|
||||
|
||||
report := &pmgsandbox.ViolationReport{
|
||||
SandboxName: pmgsandbox.DriverSeatbelt,
|
||||
Violations: []pmgsandbox.Violation{
|
||||
{Kind: pmgsandbox.ViolationKindFSWrite, Target: relTarget},
|
||||
},
|
||||
}
|
||||
_, err = deps.cache.Write(report)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = runAllowCmd(t, deps, "--last")
|
||||
require.NoError(t, err)
|
||||
|
||||
overlay, _, err := pmgsandbox.LoadOverlayForRepo(deps.overlayDir, deps.repoRoot)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, overlay)
|
||||
require.Len(t, overlay.Allow, 1)
|
||||
assert.Equal(t, wantAbs, overlay.Allow[0].Value, "cache-derived target should be normalized to absolute")
|
||||
}
|
||||
Reference in New Issue
Block a user