Commit Graph
15 Commits
Author SHA1 Message Date
ee684a29a9 feat(sandbox): presets — additive workload allowance bundles (#387)
* feat(sandbox): introduce presets - additive workload allowance bundles

Presets are named, additive-only bundles of sandbox allowances for a
specific workload (git hooks tooling, Astro/Vite/Next.js dev servers).
They solve the per-workload tuning friction from #384 without weakening
the default posture: no built-in profile references a preset, presets
cannot carry deny rules or profile booleans (strict YAML decoding), and
mandatory denies still win everywhere except the existing exact-match
suppression.

- Preset schema with metadata (author, labels) and schema_version gating
- Registry over ordered sources (embedded builtin, user dir); builtin
  wins name collisions; source abstraction is the extension point for a
  future hosted registry and SafeDep cloud sync
- Official presets: git, astro, vite, nextjs (with threat notes)
- Overlay/runtime integration: pmg sandbox allow preset=<name> and
  --sandbox-allow preset=<name>, stored by reference, resolved at apply
  time, missing presets warn (fail closed) instead of aborting
- Profile integration: presets: [...] list resolved after inherits
- CLI: pmg sandbox preset list (metadata filters, --json), show (prints
  YAML with threat notes), lint
- Docs: user guide (docs/sandbox-presets.md) and design spec

Closes #384

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* fix(sandbox): address review findings on presets

- Presets never modify deny lists: a profile authored deny now survives a
  preset allowing the same path (deny-beats-allow keeps it enforced).
  Regression test added.
- Profile inspection commands (show, diff, lint) construct the profile
  registry with the user-aware preset registry so they agree with runtime
  resolution of custom profiles referencing user presets.
- Handle stderr write error when warning about unresolvable presets.
- Compute preset show underline from the uncolored header.
- Use path.Join for embed.FS reads (slash-separated on all platforms).
- Clarify in docs that lint-staged/astro are examples of preset workloads.
- Drop the design spec from the PR per review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* fix(sandbox): harden preset precedence against authored denies

Addresses external security review findings on the preset mechanism:

- Bubblewrap: a mandatory write-denied path listed in allow_read lost its
  protection when a later writable parent bind covered it (bwrap last
  mount wins) - exactly the git preset shape (allow_read .git/config +
  allow_write .git/**). The mandatory deny now re-binds the path
  read-only after all writable mounts instead of being skipped.
  Regression test asserts mount ordering. Landlock and Seatbelt were
  unaffected (tests added for the same policy shape on Landlock).
- Environment: ScrubEnv is allow-wins, so a preset environment allowance
  could override a profile-authored deny. Preset env allowances
  overlapping an authored deny pattern are now dropped at application
  time (conservative bidirectional glob overlap, fail closed). Surviving
  entries still opt out of built-in credential scrubbing as intended.
- Network: removed allow_outbound from the preset schema. Both platform
  translators are all-or-nothing for outbound (one allow rule means
  blanket network access), so a preset outbound entry would silently
  change network posture far beyond what its YAML conveys. Strict
  decoding rejects the key.
- Added a dual-path expansion equivalence test (profile presets: field
  vs overlay/--sandbox-allow) and documented the precedence guarantees
  in docs/sandbox-presets.md.

Explicit --sandbox-allow and pmg sandbox allow overrides keep their
existing semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* docs(sandbox): document env and preset allowances in allow command and overlay docs

pmg sandbox allow help, the --sandbox-allow flag usage, and the project
overlay docs enumerated only read/write/exec/net types. Add env and
preset to all of them, with an overlay example for persisting an env
allowance and a note on why env entries are not auto-promoted by --last.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* chore(sandbox): trim preset code comments to corner cases and minimal godocs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* fix(sandbox): exact glob intersection for preset env deny overlap

The bidirectional literal-text heuristic missed overlapping globs with
different literal structure: preset allow AWS_*_KEY and authored deny
AWS_SECRET_* both match AWS_SECRET_ACCESS_KEY but neither pattern
matches the other's text, so the allowance merged and allow-wins
scrubbing exposed the variable. EnvPatternsOverlap now computes exact
intersection non-emptiness for the name glob dialect (case-insensitive,
'*' any sequence, '?' single char) via memoized DP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* fix(sandbox): preset env allowances are exact names, not globs

Glob-vs-glob intersection is a losing game: every dialect extension
(character classes today) silently reopens the deny-bypass hole.
Restricting preset environment allowances to literal variable names
makes the authored-deny precedence check exact by construction: each
deny pattern is evaluated against the concrete name with the same
matcher ScrubEnv uses at runtime, so the decision cannot diverge from
enforcement regardless of deny dialect. Removes the glob intersection
machinery. Profile and --sandbox-allow env globs are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* fix(sandbox): reject mandatory-deny targets in preset paths

Preset validation relied on IsSensitiveProjectTarget, which covers fewer
files than util.DANGEROUS_FILES. A preset naming .git-credentials,
.pgpass, .docker/config.json or .config/gh exactly would exact-match
suppress the mandatory deny; .git/config in allow_write would suppress
the write protection. Preset paths are now checked against
DANGEROUS_FILES (single source of truth), .git/hooks is rejected in any
direction, and .git/config is rejected for write/exec while read stays
allowed for git repo discovery. Docs state the two deliberate opt-outs
precisely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* feat(sandbox): preset init and edit commands for community authoring

pmg sandbox preset init scaffolds a valid user preset (metadata flags,
threat-note template, starter rule) and refuses built-in names since
builtins win resolution. pmg sandbox preset edit opens the file via the
shared editor package and validates the result, warning when a user
preset is shadowed by a built-in. Docs lead with the scaffolded flow and
spell out builtin-vs-community provenance in preset list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

* refactor(sandbox): move mandatory-target matching into util

Preset path validation re-encoded knowledge util already owns: the
dangerous-files comparison and hardcoded .git/config and .git/hooks
strings. util now exports GitConfigPath, GitHooksPath (also used by
GetMandatoryDenyPatterns), PathCoveredBy and DangerousFileMatch, and
preset validation consumes them so the mandatory deny policy has a
single definition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PETCfE4crLcmodosz12qRn

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-21 15:14:07 +05:30
695a1d739d feat: Add support for sandbox edit command (#385)
* feat: Add support for sandbox edit command

* docs: Update sandbox docs

* fix(editor): address review — neutral failure wording, skip sh-script tests on Windows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): avoid pnpm init in pm-e2e — devEngines.packageManager crashes subsequent pnpm add

Same workaround and rationale as the PNPM job in pmg-e2e.yml. Latest pnpm
(unpinned pnpm/action-setup) writes devEngines.packageManager with
onFail:download on init, and the next add fails with "Cannot use 'in'
operator to search for 'integrity' in undefined".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 22:53:39 +05:30
Abhisek DattaandGitHub d3e656edcd feat(sandbox): enable network_via_proxy_only for the Go ecosystem profile (#375) 2026-07-10 17:40:35 +00:00
648adcbda4 feat: add uvx (uv tool run) package executor (#357)
* feat(uvx): add uvx (uv tool run) package executor

Adds support for `uvx`, implemented as a PyPI Executor alongside pipx.
uvx is an alias for `uv tool run`: it installs a tool into an ephemeral
environment and runs it, so it has no install/list subcommand and the
first positional argument (or --from) is the package to audit.

Parsing highlights:
- --from overrides the positional command as the package to audit
- --with packages are audited as additional environment dependencies
- name@version shorthand (ruff@0.3.0, ruff@latest) is normalized
- flag parsing stops at the tool name so the tool's own flags are not
  misread as uvx options; uvx's value/boolean flags are registered so
  none greedily consume the package positional
- VCS/URL/local-path specs are skipped for registry auditing

Wires up command registration, analytics, shell alias/shim, cloud audit
mapping, a dedicated `uvx` sandbox profile (UV_*/PIP_* env, uv cache and
tool dirs), config policy, docs, unit tests and an E2E workflow step.

Closes #326

https://claude.ai/code/session_011hyLxq7oWJX5Dp4tCEfG19

* chore(uvx): align docs and base profile with uvx support

Incorporates the low-risk, non-parser improvements from the community
PR #345 (author non-responsive) into our implementation:

- list uvx (and the previously-missing pipx) as PyPI managers in the
  pypi-restrictive base profile package_managers and its README, so the
  base profile applies directly when selected via --sandbox-profile
- document uvx in docs/github-action.md and docs/proxy-mode.md
- add version / IsExplicitVersion assertions to the uvx parser tests

Our pflag-based parser is kept as-is: unlike #345 it audits --with
packages and handles all uvx short flags (e.g. -w), both of which the
community PR misses.

* fix(uvx): skip interpreter requests; use require in tests

Addresses review feedback on PR #357:

- uvx interpreter requests (`uvx python`, `uvx python@3.12`, `uvx pypy`,
  ...) launch an isolated interpreter rather than installing a PyPI tool.
  Treating the positional as a package made the guard flow resolve/analyze
  pkg:pypi/python (and python==3.12), which could wrongly block or fail a
  valid invocation. Skip these for the positional; --with packages on the
  same command are still audited.
- Use require.NoError / require.Len for fatal assertions in the uvx tests,
  matching the repo's testing convention, so a failure stops the subtest
  before a nil dereference instead of panicking.

* docs(uvx): document fail-open and --with-requirements trade-offs

Record the two deliberate parsing decisions raised in review as in-code
trade-off comments (no behavior change):

- unknown flags are tolerated (fail open), consistent with the other
  executors; the residual gap only affects non-proxy guard mode since the
  default proxy flow intercepts every registry download.
- --with-requirements / --with-editable values are consumed but not
  expanded into audit targets; expanding them needs manifest-extractor and
  guard changes, tracked as follow-up. Proxy mode still covers them.

* docs(uvx): drop --with-requirements limitation note

Per maintainer review: guard mode is being deprecated and auditing the
contents of an existing requirements file is a scanner's responsibility,
not PMG's. Remove the "known limitation / follow-up" note; the flags stay
registered only so their values are not mistaken for the tool positional.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-02 18:49:31 +05:30
Abhisek DattaandGitHub c7244f921a feat: Add support for environment protection (scrubbing) (#327)
* feat: Add support for environment variable protection for sandbox

* chore: Update dangerous env var list

* fix: Split profiles for improved environment protection

* fix: pipx sandbox profile separation

* chore: Show sandbox scrub info on error exit

* fix: Code review fixes

* test: Add e2e for sandbox environment scrubbing
2026-06-11 11:40:33 +05:30
Abhisek DattaandGitHub b03e82e3f2 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
2026-05-26 21:06:57 +05:30
Abhisek DattaandGitHub b8588e3df4 feat: Add Sandbox Inspection and Debugging Commands (#261)
* feat: add sandbox DX commands

* fix: Linter errors

* fix: Sandbox deny log parsing

* fix: Sandbox docs

* refactor: Maintain SSOT across pkg dependencies

* fix: Linter errors
2026-05-19 14:40:54 +05:30
Abhisek DattaandGitHub 4c42ceca0e feat: Add support for Landlock based Sandbox for Linux (#238)
* feat: Initial implementation of landlock based sandbox driver

* fix: Handle seccom probe failure

* fix: Remove unnecessary seccomp probe

* fix: Use file based policy load

* fix: Keep bpf filter in memory

* fix: Use TSYNC for seccom filter

* fix: Use TSYNC for seccom filter

* fix: Update landlock translator

* fix: Landlock sandbox implementation

* fix: Landlock + seccomp based sandboxing on Linux

* fix: Misc fixes

* fix: Cleanup sandbox files

* fix: Handle mandatory deny API change post merge

* fix: Landlock write access translation

* chore: Fix linter issues

* ci: Use /tmp for npm cache for landlock
2026-05-07 12:42:28 +05:30
d6755d3f44 feat/sandbox allow explicit dangerous pattern override (#239)
* feat(sandbox): allow opt-out of mandatory deny via explicit allow rules

Mandatory deny patterns (.env, .aws, .ssh, .gcloud, .kube, .gnupg,
.docker/config.json, .git/config) can now be opted out by listing the
exact literal post-expansion path in policy filesystem.allow_read /
allow_write, OR via --sandbox-allow read=... / write=... at runtime.
Both channels are treated at par.

Suppression is exact-match. Listing the CWD-absolute or HOME-absolute
form of a dangerous file additionally suppresses its **/<file> glob
sibling on the same direction so a single opt-out is sufficient.
Broad globs (${CWD}/**) and relative paths in user allow lists do not
suppress. The unnamed absolute form remains denied. .git/hooks is
unconditional and never suppressible (arbitrary code execution risk).

GetMandatoryDenyPatterns now returns split DenyRead / DenyWrite
slices and reports SuppressedRead / SuppressedWrite for audit. Both
translators emit per-direction deny rules and log.Warnf each
suppression. On Linux/bubblewrap, the tmpfs hide is restricted to the
intersection of DenyRead and DenyWrite; one-sided suppression falls
back to /dev/null (write) or the user's allow_read --ro-bind (read).
bwrap has no primitive that allows writes while denying reads, so
write-only opt-outs warn that the read-side mandatory deny is
unenforceable.

Updates docs/sandbox.md to document the opt-out, exact-match
semantics, and the Linux platform limitation. Updates pmg-e2e.yml to
create ./.env so the sandbox e2e test exercises the BLOCK case.

Closes #232

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: Code review fixes

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:45:36 +05:30
Abhisek DattaandGitHub 2bb5dd3778 fix: Allow explicit override over deny patterns (#177)
* fix: Allow explicit override over deny patterns

* test: Non-glob expansion for overrides is expected
2026-03-06 10:10:15 +05:30
Abhisek DattaandGitHub 6074080219 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
2026-02-16 13:58:08 +05:30
Abhisek DattaandGitHub 80a1747e3e feat: Add support for Linux Sandbox using Bubblewrap (#120)
* feat: Add support for bubblewrap sandbox

* fix: Glob pattern expansion limit for linux

* fix: Bug in glob pattern expansion for bwrap

* fix: README on trust

* fix: Multiple bubblewrap translator fix

* test: Add E2E for linux sandbox

* fix: Refactor bwrap sandbox to use common dangerous files

* fix: Path test case

* fix: Non-existent path handling bug

* refactor: Misc cleanup

* fix: Avoid bind mount for non-existentent deny protection

* fix: Off by one bug in path depth handling

* ci: Disable AppArmor on GHA runner

* fix: Disable apparmor userns restrictions
2026-01-15 20:12:12 +05:30
b97a4c2ee5 docs: Add trust doc (#118)
* docs: Add trust doc

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-14 15:02:08 +05:30
2e1f5b1a36 feat: Add support for policy inheritence (#113)
* feat: Add support for policy inheritence

* fix: Linter fixes

* Update docs/sandbox.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* fix: Handle boolean inheritence

* ci: Add linter

* Update sandbox/policy_test.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* fix: Linter fixes

* fix: Linter fixes

* fix: Sandbox rule regex format

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-14 10:50:39 +05:30
9693428171 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>
2026-01-13 14:52:02 +05:30