mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* update the go.sum Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> * migrate most of the files to dry errors Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> * update the rest of the files Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> * fixs the review comments Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> * chores Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> --------- Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
229 lines
7.2 KiB
Go
229 lines
7.2 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
|
"github.com/safedep/dry/usefulerror"
|
|
"github.com/safedep/pmg/errcodes"
|
|
)
|
|
|
|
func sampleReport() *pmgsandbox.ViolationReport {
|
|
return &pmgsandbox.ViolationReport{
|
|
SandboxName: pmgsandbox.DriverSeatbelt,
|
|
PolicyName: "npm-restrictive",
|
|
CorrelationID: "corr-abc",
|
|
Violations: []pmgsandbox.Violation{
|
|
{
|
|
Kind: pmgsandbox.ViolationKindFSWrite,
|
|
Target: "/Users/dev/project/.env",
|
|
RuleLabel: "file-write* on sensitive project file",
|
|
Process: "npm",
|
|
RawLog: "deny file-write* /Users/dev/project/.env",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func writeFixtureCache(t *testing.T, dir string) *pmgsandbox.ViolationCache {
|
|
t.Helper()
|
|
cache := pmgsandbox.NewViolationCache(dir, pmgsandbox.WithClock(func() time.Time {
|
|
return time.Date(2026, 5, 14, 10, 30, 0, 0, time.UTC)
|
|
}))
|
|
_, err := cache.Write(sampleReport())
|
|
require.NoError(t, err)
|
|
return cache
|
|
}
|
|
|
|
func runExplainCmd(t *testing.T, factory cacheFactory, args []string, stdin string) (string, string, error) {
|
|
t.Helper()
|
|
cmd := newExplainCommand(factory)
|
|
var out, errOut bytes.Buffer
|
|
cmd.SetOut(&out)
|
|
cmd.SetErr(&errOut)
|
|
cmd.SetIn(strings.NewReader(stdin))
|
|
cmd.SetArgs(args)
|
|
err := cmd.Execute()
|
|
return out.String(), errOut.String(), err
|
|
}
|
|
|
|
func TestExplainLastEmptyCache(t *testing.T) {
|
|
dir := t.TempDir()
|
|
factory := func() *pmgsandbox.ViolationCache { return pmgsandbox.NewViolationCache(dir) }
|
|
|
|
stdout, _, err := runExplainCmd(t, factory, []string{"--last"}, "")
|
|
require.Error(t, err)
|
|
|
|
fe, ok := err.(*explainFailError)
|
|
require.True(t, ok)
|
|
assert.Equal(t, ExitCodeExplainFail, fe.ExitCode())
|
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errcodes.NotFound, usefulErr.Code())
|
|
assert.Empty(t, stdout)
|
|
assert.Contains(t, err.Error(), "no violations cached")
|
|
}
|
|
|
|
func TestExplainLastRendersHuman(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFixtureCache(t, dir)
|
|
factory := func() *pmgsandbox.ViolationCache { return pmgsandbox.NewViolationCache(dir) }
|
|
|
|
stdout, _, err := runExplainCmd(t, factory, []string{"--last"}, "")
|
|
require.NoError(t, err)
|
|
|
|
assert.Contains(t, stdout, "seatbelt")
|
|
assert.Contains(t, stdout, "npm-restrictive")
|
|
assert.Contains(t, stdout, "Reason:")
|
|
assert.Contains(t, stdout, "Details:")
|
|
assert.Contains(t, stdout, "Primary violation:")
|
|
assert.Contains(t, stdout, "/Users/dev/project/.env")
|
|
}
|
|
|
|
func TestExplainLastJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFixtureCache(t, dir)
|
|
factory := func() *pmgsandbox.ViolationCache { return pmgsandbox.NewViolationCache(dir) }
|
|
|
|
stdout, _, err := runExplainCmd(t, factory, []string{"--last", "--json"}, "")
|
|
require.NoError(t, err)
|
|
|
|
var payload struct {
|
|
Explanation struct {
|
|
Hint string `json:"hint"`
|
|
Details string `json:"details"`
|
|
SuggestedOverride string `json:"suggested_override"`
|
|
Primary struct {
|
|
Kind string `json:"kind"`
|
|
Target string `json:"target"`
|
|
} `json:"primary"`
|
|
} `json:"explanation"`
|
|
Report *pmgsandbox.ViolationReport `json:"report"`
|
|
RecordedAt string `json:"recorded_at"`
|
|
}
|
|
require.NoError(t, json.Unmarshal([]byte(stdout), &payload))
|
|
|
|
assert.NotEmpty(t, payload.Explanation.Hint)
|
|
assert.Equal(t, "fs_write", payload.Explanation.Primary.Kind)
|
|
assert.Equal(t, "/Users/dev/project/.env", payload.Explanation.Primary.Target)
|
|
require.NotNil(t, payload.Report)
|
|
assert.Equal(t, pmgsandbox.DriverSeatbelt, payload.Report.SandboxName)
|
|
assert.NotEmpty(t, payload.RecordedAt)
|
|
}
|
|
|
|
func TestExplainLastRejectsCacheRecordWithNilReport(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "violation-99999999T999999.999999999Z-bad.json")
|
|
body := []byte(`{"schema_version":1,"recorded_at":"2026-05-14T10:30:00Z","report":null}`)
|
|
require.NoError(t, os.WriteFile(path, body, 0o644))
|
|
factory := func() *pmgsandbox.ViolationCache { return pmgsandbox.NewViolationCache(dir) }
|
|
|
|
stdout, _, err := runExplainCmd(t, factory, []string{"--last"}, "")
|
|
require.Error(t, err)
|
|
|
|
fe, ok := err.(*explainFailError)
|
|
require.True(t, ok)
|
|
assert.Equal(t, ExitCodeExplainFail, fe.ExitCode())
|
|
assert.Empty(t, stdout)
|
|
assert.Contains(t, err.Error(), "cache JSON is missing report")
|
|
}
|
|
|
|
func TestExplainStdinValid(t *testing.T) {
|
|
rec := pmgsandbox.ViolationCacheRecord{
|
|
SchemaVersion: pmgsandbox.ViolationCacheSchemaVersion,
|
|
RecordedAt: time.Date(2026, 5, 14, 10, 30, 0, 0, time.UTC),
|
|
Report: sampleReport(),
|
|
}
|
|
data, err := json.Marshal(rec)
|
|
require.NoError(t, err)
|
|
|
|
factory := func() *pmgsandbox.ViolationCache {
|
|
t.Fatal("cache should not be consulted in stdin mode")
|
|
return nil
|
|
}
|
|
|
|
stdout, _, err := runExplainCmd(t, factory, []string{"-"}, string(data))
|
|
require.NoError(t, err)
|
|
assert.Contains(t, stdout, "Primary violation:")
|
|
assert.Contains(t, stdout, "/Users/dev/project/.env")
|
|
}
|
|
|
|
func TestExplainStdinInvalidJSON(t *testing.T) {
|
|
factory := func() *pmgsandbox.ViolationCache { return nil }
|
|
_, _, err := runExplainCmd(t, factory, []string{"-"}, "not json {{")
|
|
require.Error(t, err)
|
|
fe, ok := err.(*explainFailError)
|
|
require.True(t, ok)
|
|
assert.Equal(t, ExitCodeExplainFail, fe.ExitCode())
|
|
assert.Contains(t, err.Error(), "parse stdin JSON")
|
|
}
|
|
|
|
func TestExplainStdinSchemaVersion(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
body string
|
|
want string
|
|
}{
|
|
{
|
|
name: "missing schema_version",
|
|
body: `{"report":{"SandboxName":"seatbelt","PolicyName":"p","Violations":[{"Kind":"fs_write","Target":"/x","RuleLabel":"r"}]}}`,
|
|
want: "missing schema_version",
|
|
},
|
|
{
|
|
name: "unknown schema_version",
|
|
body: `{"schema_version":999,"report":{"SandboxName":"seatbelt","PolicyName":"p"}}`,
|
|
want: "unknown schema_version",
|
|
},
|
|
}
|
|
|
|
factory := func() *pmgsandbox.ViolationCache { return nil }
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, err := runExplainCmd(t, factory, []string{"-"}, tc.body)
|
|
require.Error(t, err)
|
|
fe, ok := err.(*explainFailError)
|
|
require.True(t, ok)
|
|
assert.Equal(t, ExitCodeExplainFail, fe.ExitCode())
|
|
assert.Contains(t, err.Error(), tc.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExplainMutualExclusion(t *testing.T) {
|
|
factory := func() *pmgsandbox.ViolationCache { return nil }
|
|
_, _, err := runExplainCmd(t, factory, []string{"--last", "-"}, "")
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "mutually exclusive")
|
|
}
|
|
|
|
func TestExplainRejectsUnexpectedArgsWithUsage(t *testing.T) {
|
|
factory := func() *pmgsandbox.ViolationCache { return nil }
|
|
stdout, stderr, err := runExplainCmd(t, factory, []string{"one", "two"}, "")
|
|
require.Error(t, err)
|
|
assert.Contains(t, stderr, "accepts at most 1 arg(s), received 2")
|
|
assert.Contains(t, stdout, "Usage:")
|
|
assert.Contains(t, stdout, "explain [--last | -] [flags]")
|
|
assert.Contains(t, stdout, "pmg sandbox explain --last")
|
|
}
|
|
|
|
func TestExplainNoMode(t *testing.T) {
|
|
factory := func() *pmgsandbox.ViolationCache { return nil }
|
|
_, _, err := runExplainCmd(t, factory, []string{}, "")
|
|
require.Error(t, err)
|
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
|
assert.Contains(t, err.Error(), "no input")
|
|
assert.Contains(t, err.Error(), "--last")
|
|
}
|