* 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>
* 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>
* feat: add Linux system-wide setup
Install shared shims, managed configuration, and login-shell PATH integration so golden images and multi-user hosts can protect package installs for every user.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: keep local design documents untracked
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: harden and simplify Linux system install
Tighten shim detection, profile repair, and install ordering while
trimming over-specific doctor/info hints from the system-install path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: clarify system-install doctor alias and shim path checks
Use UserBinDir for PATH checks and pass aliases as not required under
system install without treating that as active interception.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: tighten event-log soft-fail warning prefix
Prefix the warning with [pmg] and drop the redundant continuing clause.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: add Linux system-install e2e and pin pnpm for add flake
Cover root system setup, PATH/profile.d, managed config, non-root
interception, and remove. Pin pnpm 11.10.0 on the package-manager e2e
job after an integrity crash on pnpm add.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: bump packageManager to pnpm 11.10.0 for e2e
Align package.json with the pnpm version we want in CI so action-setup
stops erroring on a version mismatch after the e2e integrity flake.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: use npm init for pnpm e2e to avoid integrity crash
pnpm 11.x `pnpm init` still writes onFail:download; `pnpm add` then
fails after PMG analysis even on 11.10.0. Seed the temp package with
npm init instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: revert packageManager pin to pnpm 11.1.3
The e2e integrity crash is avoided by npm init; the 11.10.0 bump is
no longer needed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: harden system-install review findings
Require root-owned, non-group/other-writable pmg for --system install;
allow remove without that validation. Doctor checks npm resolution for
PATH precedence, uses ImpliesInterception instead of message matching,
and documents version-manager shadowing. Pass profile bin dir from the
shim manager and note that system config ignores per-user files.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: harden doctor PATH checks and attribute cloud events by OS user
Doctor now verifies every installed package manager against the shim
directory, and system-install validation only requires a safe parent
directory. Cloud sync records username/uid on invocation context for
multi-user hosts sharing one endpoint.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: address system-install review findings
- shim: make system executable resolution injectable so tests pass under
umask 002; skip the root-owner test when running as root
- doctor: treat resolution into either the system or per-user shim dir as
intercepted, and collapse the shim-in-PATH check to a single call site
- setup: make remove (both --system and per-user) best-effort with
errors.Join so one failed step no longer strands the other artifact
- shim: allow a group-writable install parent dir (Debian/Ubuntu ship
/usr/local/bin as root:staff 2775) while still rejecting world-writable
and non-root-owned parents
- audit: attribute cloud events to SUDO_USER when running under sudo
- docs: drop the soft-fail event-logging claim (hard-fail is retained)
* ci: normalize /usr/local/bin perms before system-install e2e
The GitHub ubuntu-latest runner ships /usr/local/bin world-writable so
tooling can install without sudo. System install correctly refuses a
world-writable dir for the shared binary (any local user could replace
it and hijack every user's npm/pip). No FHS-compliant distro or Docker
image ships it world-writable — it is always root:root 0755 or
root:staff 2775 — so this normalizes only the anomalous CI runner back
to standard perms and still exercises the real /usr/local/bin path.
* fix: actionable remedy for root-created per-user config dir
A pmg run as root with a preserved HOME (GitHub runners, sudo -E, su
without -) creates the invoking user's ~/.config/safedep as root-owned,
and event-log init then fail-closes every later non-root command.
Make that state self-solvable:
- event-log init permission errors exit with a usefulerror naming the
likely cause and the chown fix instead of a bare fatal
- pmg setup doctor probes event-log dir writability and reports the
same fix via a new per-result Fix override
- document the mechanism and remedy in system-install.md, along with
the binary ownership requirements for --system
- consolidate this branch's doctor tests into doctor_test.go
* fix: resolve per-user paths from root's own home when running as root
Path resolution trusted HOME (and XDG_*), which sudo and su can preserve
from the invoking user (GitHub runners, sudo -E, su without -). Any pmg
run as root then created root-owned ~/.config/safedep inside that user's
home, and event-log init fail-closed every later non-root pmg/npm/pip
run for them. System install made sudo pmg the documented flow, turning
this latent bug into the happy path.
When euid is 0, configDir and cacheDir now resolve from root's passwd
home instead of the environment, so root state lands under /root and
user homes are never touched. PMG_CONFIG_DIR/PMG_CACHE_DIR still win,
non-root resolution is unchanged, and Windows is unaffected (no euid).
Event-log init stays fatal on failure; sudo-run package events are
attributed via SUDO_USER and synced by the exit auto-sync as usual.
E2E: GitHub runners preserve HOME under sudo, so assert that no sudo
pmg run leaks state into the runner's home, and that the managed-config
refusal fails for the documented reason rather than a permission brick.
* fix: triage the unwritable config dir remedy by cause
The chown hint is only correct when another account created files
inside the current user's own home. When a leaked HOME or
XDG_CONFIG_HOME points at another user's home (e.g. sudo -u on GitHub
runners), following it would chown that user's directory and brick
their pmg instead. Classify the failure against the passwd home,
which the leaked environment cannot influence, and prescribe:
- dir inside own home: restore ownership with chown
- dir outside own home: fix the leaked environment, never chown
- explicit PMG_CONFIG_DIR: make it writable
Used by both the fatal event-log error and the doctor check, and the
docs troubleshooting now carries the same two-case triage.
* ci: pin XDG_CONFIG_HOME for the cross-user e2e step; terse doctor fix
GitHub runners export XDG_CONFIG_HOME=/home/runner/.config and it leaks
through sudo -u, so the pmgtest pmg resolved the runner user's config
dir and fail-closed on its runner-owned log file (run 29289868727 shows
the triaged error catching exactly this). Set it inside the login shell
so it wins regardless of how the leak is delivered.
The remedy now returns a full-help and doctor-table pair from a single
triage, and drops the do-not-chown tail from the leak message.
* fix: adapt event-log error to the two-value remedy signature
Belongs with the previous commit; it was left unstaged and e0580b1
does not compile without it.
* fix: fall back to env path resolution when root has no passwd entry
Running as uid 0 without a resolvable root passwd entry (scratch
containers, minimal chroots) panicked at startup on every command,
because the euid-based path resolution had no fallback. Fall back to
env-derived resolution there: without a passwd database there is no
user switching, so the cross-user poisoning that branch prevents
cannot occur.
Also restore the underlying cause in the generic event-log init error
(minimal output hid it after the usefulerror change), and document
that root's per-user data lives under /root regardless of a preserved
HOME.
* fix: gate SUDO_USER trust and root path diversion; add doctor binary check
Address review findings on the system-install PR:
- cloud_sink: honor SUDO_USER for audit attribution only when euid==0.
Without the gate any user could set SUDO_USER and spoof cloud-audit
attribution to another account. Matches the guard in cmd/setup/cert.go.
- config: divert per-user paths to root's passwd home only on an actual
sudo elevation (euid==0 && SUDO_USER set), not for every root euid.
The blanket root diversion ignored HOME/XDG_CONFIG_HOME and silently
stopped reading genuine root users' config (golden Docker images),
regressing two tests that only fail when the suite runs as root.
Genuine root honors the environment as before; su without - leaves no
marker and stays a documented, loud-failing residual.
- doctor: add a system-only check re-validating that the binary the
installed shims exec is still root-owned and non-writable, catching
permission/ownership drift after install.
- shim: fold the duplicated shim-scan loop into firstShimContent.
* fix: harden system dirs at install; keep sudo attribution without passwd
Address remaining review comments:
- shim: force root:root 0755 on the managed system dirs (shim tree and
profile.d) after MkdirAll. A pre-created dir with weaker ownership,
possible under Debian's group-writable /usr/local/lib, would let a
non-root user replace the shims every account executes.
- audit: when SUDO_USER has no passwd entry (minimal containers),
attribute cloud events from sudo's recorded SUDO_USER/SUDO_UID env
instead of falling back to root. Still gated on euid 0.
- setup: reword the root-without---system warning; alias/shim install
follows HOME, so claiming it configures only root's home was wrong.
- shim: skip the non-root-owner validation test on Windows, where file
ownership is not resolvable.
* fix: reject system binaries unreachable by other users; consistent info
The system-install validation checked the binary's own permissions and
the parent's tamper-safety but never reachability: a 0755 root-owned
binary under a 0700 directory (e.g. /root/pmg) passed every check while
every non-root user's shim failed with exit 127. Walk the directory
chain to / and require the search bit for others; doctor's system
binary check inherits this. E2E gains a reject case for a binary under
a non-searchable directory.
setup info: render alias/user-shim/system-shim rows through one
installed-state formatter (location when installed, "not installed"
otherwise) instead of a mix of booleans, paths, and prose.
* fix: stop reowning /etc/profile.d; document group-writable and su gaps
writeSystemProfile chowned/chmod'd /etc/profile.d itself, a shared
system directory pmg does not own, silently overriding any perms a
sysadmin set on it. Secure only the file pmg writes (pmg.sh) via
secureSystemFile, which also forces 0644 explicitly so a restrictive
root umask cannot leave the snippet non-world-readable (which would
drop the shim dir from other users' login-shell PATH).
Docs: add Limitations entries for the group-writable install dir
bypass (validation is defeatable on non-sticky group-writable dirs
like Debian's /usr/local/bin) and the elevation-only scope (su without
- can still poison the caller's home; sudo -u cannot poison another
account). Trim the requireSafeParentDir comment to a pointer.
* refactor: separate unwritable-dir diagnosis from remedy rendering
Address the open review threads on #376:
- rename realUserHomeDir to currentUserHomeDir and fail when the passwd
entry has no home directory
- split UnwritableConfigDirRemedy into classifyUnwritableDir (cause
diagnosis) and pure message rendering so each function has one job
- replace cmd/setup's duplicate pathIsUnderDir with the shared
config.PathWithinDir, now guarding empty inputs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011rWmR6NS47FpedJsrTWk8A
* fix: repair umask-clamped modes on system shims and managed config
os.WriteFile and os.MkdirAll honor the process umask, so a hardened root
umask (e.g. 077) produced 0700 shims other users cannot execute and a
0600/0700 managed config non-root pmg runs cannot read - silently
disabling the system-wide policy. Chmod/chown the artifacts explicitly
after writing, with unix regression tests running under umask 077.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011rWmR6NS47FpedJsrTWk8A
* refactor: address system-install maintainability review
- move PathWithinDir and the root-owned mkdir/chmod helpers into a new
internal/fsutil package; config no longer exports a generic fs util
- create managed-config directories with MkdirAllRootOwned, which only
sets ownership and mode on directories it creates - drops the
hardcoded safedep parent-dir heuristic and never touches pre-existing
directories
- collapse NewSystemShimManagerForRemove into NewSystemShimManager and
validate the executable in Install, where the action needs it; Remove
works regardless of binary state
- rename ShimConfig.ManageProfile to SystemProfile and document it
- name the Linux-only system paths linuxSystemBinDir/ProfilePath and
document the Unix-only validation semantics
- share the PMG_BIN shim variable name between writeShimScript and
parseShimPMGBin via the shimPMGBinVar constant
- cloud attribution falls back to the effective uid when SUDO_UID is
absent, so sudo-invoked commands are not misattributed to root
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011rWmR6NS47FpedJsrTWk8A
---------
Co-authored-by: Sahilb315 <bansalsahil315@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude <noreply@anthropic.com>
* feat(sandbox): Seatbelt lockdown translation confines outbound to the PMG proxy
Under network_via_proxy_only the Seatbelt profile now denies all
network-outbound (with a target=direct violation marker) and allows only
the loopback proxy port. SBPL is last-match-wins, so the broad deny is
emitted first, specific allows after, and the allow_network_bind rules
last — keeping loopback-to-loopback dev traffic working under lockdown.
allow_direct_dns re-opens the /var/run/mDNSResponder unix socket that
the deny otherwise covers.
Replaces the temporary fail-closed rejection in the Seatbelt driver with
the real translation; non-lockdown profiles translate byte-identically
to before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
* test(sandbox): assert deny marker presence before ordering comparison
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
* feat(sandbox): render lockdown profiles without a proxy as deny-only with runtime note
pmg sandbox profile show renders profiles for debugging and must not
fail on lockdown profiles. Without a running proxy the translator keeps
the broad deny (rendered profile stays fail-closed, never looser than
runtime) and documents the runtime-only proxy-port allow in an SBPL
comment instead of fabricating a port. Execution is unaffected: the
driver validates the proxy address before translating.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(sandbox): ExecutionContext plumbing and fail-closed lockdown contract
Network lockdown needs the PMG proxy's address, which is only known at
spawn time. Thread an ExecutionContext from the proxy flow through the
runner and executor into every sandbox driver, and enforce the
network_via_proxy_only fail-closed contract: lockdown without a running
loopback proxy, or on a driver that cannot enforce it, is a hard error —
never a silent fallback to unrestricted network.
- sandbox.ExecutionContext{ProxyAddr} + 4-arg Sandbox.Execute
- sandbox.ValidateLockdown validates the proxy address (loopback only)
with usefulerror code SandboxRequiresProxy
- Seatbelt validates lockdown before translation (translation itself
lands next); bubblewrap and landlock reject lockdown as unsupported
until Linux enforcement is implemented
- executor.WithExecutionContext, runner.ExecuteOptions.SandboxProxyAddr,
proxy flow passes the live proxy address
- ApplySandbox also validates centrally before invoking the driver
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
* fix(sandbox): require numeric in-range proxy port in ValidateLockdown
The validated port string is embedded into generated sandbox profiles,
so service names, zero, and out-of-range ports are refused.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
* fix(sandbox): fail closed on Seatbelt lockdown until translation lands
A lockdown policy that passed proxy validation would silently receive
the pre-lockdown network rules from the translator. Reject it until the
lockdown profile translation is implemented, keeping the window between
plumbing and enforcement fail-closed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
* chore: review feedback — drop redundant comment, simplify stub help text
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(sandbox): add network_via_proxy_only and allow_direct_dns policy fields
Config surface for network lockdown: network_via_proxy_only confines a
sandboxed package manager's outbound network to the PMG proxy;
allow_direct_dns is its escape hatch re-opening direct DNS. Both follow
the existing pointer-bool inheritance pattern in MergeWithParent. Lint
warns when allow_direct_dns is set without network_via_proxy_only, where
it has no effect.
The fields are declared and inherited but unread; enforcement lands with
the ExecutionContext plumbing and Seatbelt lockdown translation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
* fix(sandbox): deep-copy new pointer flags in profile resolution
expandPolicyPaths re-points the older pointer booleans so callers cannot
corrupt the registry-cached policy; the new NetworkViaProxyOnly and
AllowDirectDNS fields need the same isolation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
---------
Co-authored-by: Claude <noreply@anthropic.com>
* ci: add macOS test job
Darwin-tagged tests (Seatbelt translator, diagnostics, upcoming network
lockdown tests) never ran in CI: the test matrix covered ubuntu and
windows only. The M0 sandbox lockdown milestone is macOS-first, so macOS
coverage must exist before enforcement work lands.
Also establishes the convention that darwin integration tests requiring
sandbox-exec must fail (not skip) when running in CI, so missing tooling
cannot silently hide security tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
* test(sandbox): make Seatbelt render golden test machine-independent
The golden file baked the authoring machine's HOME, CWD and TMPDIR parent
into the expected profile, so TestRenderSeatbelt_Golden could only pass on
that machine. It never ran in CI before the macOS job exposed it.
Normalize CWD, home and TMPDIR parents (raw and query-escaped forms) to
stable placeholders before comparison, longest path first so prefix
overlaps survive, and rewrite the golden accordingly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
* ci: make setup-go cache explicit in macOS job
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(cloud): surface real backend errors from pmg cloud sync
runSync wrapped every DrainToCloud failure as a network error, masking
the actual cause — an entitlement failure surfaced as "check your
network connectivity", which made backend issues very hard to diagnose.
Classify the error first (usefulerror gRPC converters map backend
statuses to authentication, entitlement, quota and server errors) and
pass it through. The network-flavored message remains only as the
fallback when nothing can classify the error. Bump safedep/dry to pick
up nested-Any ErrorInfo extraction so entitlement classification also
works against control-tower versions that re-wrap status details.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHdvpFXb2shyMzUDyR8QKA
* chore: bump safedep/dry to latest branch commit
Picks up the review follow-up in dry#128 (skip unmarshalling unknown
detail types when unwrapping Any).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHdvpFXb2shyMzUDyR8QKA
* chore: bump safedep/dry to post-merge main
Replaces the dry#128 branch pseudo-version with the squashed main
commit now that the nested-Any ErrorInfo fix has merged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHdvpFXb2shyMzUDyR8QKA
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add experimental Go module support via pmg go
Adds Go modules as a proxy-guarded ecosystem, opt-in only: the command
runs solely when invoked explicitly as `pmg go ...` and is deliberately
excluded from setup aliases and PATH shims so existing users are
unaffected.
- packagemanager: goPackageManager with fail-safe command classification
(vet/fix excluded from non-download since they can fetch on a cold
cache) and pinned-version extraction where only canonical semver
counts as explicit.
- GOPROXY normalization (fail-closed): effective GOPROXY read via
`go env` (honors go env -w), rebuilt comma-joined with `direct`
dropped so a 403 block is terminal and nothing silently falls back to
unanalyzed VCS fetches. GOPRIVATE/GONOPROXY surface a warning;
GOINSECURE is cleared. Contributed to the proxy flow through a new
ProxyRoutingProvider hook (extra child env + dynamic MITM hosts).
- Go interceptor with dynamic host matching from the user's effective
GOPROXY via InterceptorContext.GoProxyHosts. Malware analysis runs on
.zip only (the sole endpoint that delivers code); .info/.mod/@latest/
list pass through; /sumdb/ traffic and sum.golang.org are never
touched so checksum-db verification stays intact; golang.org/toolchain
is allowed on Go's own checksum verification.
- Dependency cooldown: publish time captured from .info responses
(body unmodified), in-window .zip blocked with 403; fails open for
cooldown only when the publish time was never observed.
- Cert gate: on macOS/Windows `pmg go` fails fast with actionable
guidance unless the persisted PMG CA is OS-trusted (Go ignores
SSL_CERT_FILE there); Linux works via the injected bundle.
- proxye2e: GOPROXY-protocol mock registry, Go driver and 10 hermetic
cases (allow/block/confirm, case-escaped paths, cooldown block and
fail-open, toolchain, sumdb passthrough).
Verified end-to-end on Linux: `pmg go get github.com/google/uuid@v1.6.0`
MITMs proxy.golang.org, analyzes the decoded module at the .zip fetch,
and go.sum verification succeeds through the tunneled checksum db.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xuhBeTVpfU4SdqVaarvuK
* fix(go): address review findings on experimental Go support
- Drop fmt/clean from NonDownloadCommands: both load packages via go
list and can download modules on a cold cache, which would bypass the
proxy under install_only.
- Support GOPROXY entries with a base path (e.g. corp Athens/JFrog at
https://corp/goproxy): the interceptor now receives host -> base URL
and strips the path prefix before parsing module URLs, so verdicts
and cooldown key on the real module path.
- Default unschemed GOPROXY entries to https, matching go's own
behavior, so corp mirrors configured as bare hosts are intercepted
instead of silently unanalyzed.
- Memoize the final verdict per module zip: go re-requests a failed
zip during go get's load phase, which double-recorded stats (the
report showed the same blocked module twice) and would have
re-prompted on Confirm verdicts.
- Fetch .info out-of-band on a cooldown cache miss: go serves .info
from its local module cache on any machine that used go before PMG,
which silently disabled cooldown. Failure of the side-fetch still
fails open for cooldown only.
- Move the noop package resolver into packagemanager.
Verified live: cold-cache cooldown block now records once; warm-cache
rerun is blocked via the side-fetch instead of failing open.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xuhBeTVpfU4SdqVaarvuK
* docs: collapse Go proxy-mode details by default
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xuhBeTVpfU4SdqVaarvuK
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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#326https://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>
* fix(proxy): do not override explicitly-set CI env var
pmg forces CI=true for non-interactive (non-PTY) proxy runs so package
managers behave non-interactively. This clobbered a CI value the user set
explicitly (e.g. CI=false on a build server), changing downstream tool
behavior unexpectedly.
Only inject CI=true when CI is not already present in the environment,
preserving the user's intent. mergeEnv override semantics are left intact
since other overrides (HTTP_PROXY, etc.) must clobber.
Fixes#335
* test(proxy): snapshot/restore CI env explicitly in override test
Address review feedback: make the unset-CI subtest's intent explicit by
snapshotting the original CI value, unsetting it for the test, and
restoring it in t.Cleanup instead of relying on t.Setenv cleanup.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(sandbox): support pnpm workspaces and macOS cache dir in pnpm profile
pnpm in a workspace (monorepo) creates a node_modules directory inside
every workspace package to symlink direct dependencies. The profile only
allowed writes to the root node_modules, so installs failed with EPERM
on mkdir of e.g. apps/mobile/node_modules.
pnpm on macOS also writes its cache (lockfile verification, metadata)
under ~/Library/Caches/pnpm, while the base profile only covers the XDG
path ~/.cache/pnpm.
Fixes are scoped to the pnpm leaf profile, not the shared
npm-restrictive base.
Ref: https://github.com/safedep/pmg/issues/329
* fix(sandbox): emit regex parent rule for nested-glob allow patterns on Seatbelt
For allow patterns ending in /**, the translator auto-allows the parent
directory so mkdir/stat of the directory itself succeeds. The rule was
always emitted as a literal, which can never match when the parent still
contains glob characters (e.g. ${CWD}/**/node_modules from a workspace
allowance) — silently leaving the directory's own creation denied.
Emit a regex rule for glob-bearing parents instead. This stays strictly
narrower than the Linux drivers (Bubblewrap binds the prefix before the
first /** read-write; Landlock grants the glob expansion or its parent),
and deny rules are emitted after allows, so mandatory credential denies
still override.
Ref: https://github.com/safedep/pmg/issues/329
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(pty): treat background jobs as non-interactive to avoid SIGTTOU stop
IsInteractiveTerminal only checked that stdin/stdout are TTYs. A background
job (pmg npm run test &) still has the TTY on stdin/stdout, so pmg picked
PTY mode and called tcsetattr to enter raw mode. Changing terminal modes
from a background process group makes the kernel stop the process with
SIGTTOU, leaving the job hanging in Stopped state.
Check that the process group is the terminal's foreground process group
(tcgetpgrp == getpgrp) before treating the terminal as interactive, so
background jobs fall through to direct execution.
Fixes#322https://claude.ai/code/session_01PBBo5CKkzg68MMGrgCTQcY
* test(pty): fail on output copy timeout to avoid racy buffer read
Reading the output buffer after a silent select timeout races with the
io.Copy goroutine still writing to it. Fail the test on timeout instead.
Also fix a grammar nit in the IsInteractiveTerminal doc comment.
https://claude.ai/code/session_01PBBo5CKkzg68MMGrgCTQcY
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(proxy): harden MITM proxy reliability and scale for bulk installs
Deep-dive analysis of dropped connections during large installs (5000+
packages with concurrent downloads) surfaced three issues, each verified
with a reproduction test before fixing.
1. Transient upstream errors tore down whole keep-alive tunnels.
goproxy returns false (closing the entire MITM client tunnel) when a
single upstream round-trip errors. Under load, CDN-fronted registries
(e.g. Cloudflare for registry.npmjs.org) intermittently reset
connections, so one transient reset dropped a pooled keep-alive socket
and surfaced to the package manager as ECONNRESET / "socket hang up".
Fix: route upstream round-trips through a resilient round tripper that
retries idempotent, body-less requests with bounded linear backoff,
absorbing transient resets and keeping the tunnel alive. A reproduction
test shows the tunnel count drop from 3 to 1 across a transient failure.
2. Head-of-line amplification against the external analysis service.
Concurrent requests for the same package version each issued their own
gRPC call. Fix: de-duplicate in-flight analyses with singleflight so a
burst of identical requests collapses into one upstream call.
3. Per-request goproxy verbose logging on the hot path.
proxy.Verbose was always on, formatting several log lines per request
even when discarded below debug level. Fix: enable goproxy verbose
logging only when PMG runs at debug level.
Note: the hypothesis that the http.Server Read/WriteTimeout leaks onto
hijacked CONNECT tunnels was investigated and disproven (Go clears the
deadlines on Hijack); the behavioral guard tests for long-lived
connections and slow transfers are retained.
* fix: Type assertion error handling
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: authenticated Malysis analyzer with tenant exclusion support
When SafeDep Cloud credentials are available (keychain or environment),
PMG now uses an authenticated malware analysis query against
api.safedep.io instead of the unauthenticated community endpoint
(community-api.safedep.io). The API key and tenant ID are supplied via
the gRPC connection.
The authenticated response may carry a tenant-specific malicious package
exclusion. This is honored as an opt-in trust signal: a flagged package
is downgraded to allow only when a concrete exclusion (non-empty ID) is
present for the exact package version queried. Exclusions are never
honored for community queries and never weaken the verdict for packages
that were not flagged. Allowed-by-exclusion packages are surfaced as a
warning so the trust decision is never silent.
Changes are additive; non-authenticated usage is unchanged. Credential
resolution is extracted into internal/cloudauth and reused by both the
analyzer factory and the existing cloud sync client.
* fix: surface tenant exclusions in proxy mode; clarify comments
- Warn when proxy interceptor allows a flagged package due to a tenant
exclusion, matching the guard flow so the trust decision is not silent.
- Remove stray doc comment above warnIfExcluded.
- Clarify that a verified-malware verdict can be downgraded by an
exclusion in applyExclusion.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(sandbox): classify helper-tool errors with usefulerror
Sandbox helper commands (profile lint/diff/show/init/list) used to bubble
up plain fmt.Errorf chains from the registry layer, which the TUI then
classified as Unknown and decorated with a bug-report link. Wrap each
error path at the cmd/sandbox boundary so the TUI prints NotFound,
InvalidArgument, or PermissionDenied with actionable hints instead.
Closes#269
* refactor(sandbox): classify registry errors via sentinel wrapping
Replace the fragile substring match in profileLoadError with errors.Is
against new sandbox.ErrProfileNotFound / sandbox.ErrProfileInvalid
sentinels. Every fmt.Errorf in registry.go that previously communicated
"missing" or "malformed" by message text now wraps the corresponding
sentinel, so the cmd layer can classify without inspecting strings.
* fix(sandbox): detect IO error class when wrapping helper errors
Replace static ErrCodeUnknown / ErrCodePermissionDenied wrappings with
ioErrorCode, which inspects the error chain for fs.ErrPermission and
fs.ErrNotExist before falling back. Applied to runProfileList (where an
unreadable user profile directory now classifies as PermissionDenied),
registryInitError, and the stat/MkdirAll/WriteFile paths in profile init.
Also drop redundant doc comments on helpers whose names are self-evident.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: Add support for background sync
* refactor: Maintain single source of truth for command defn
* fix: Code review fixes
* fix: Code review fixes
* docs: Add corner case inline doc
- Remove top banner and add a tagline under the H1 so the value
proposition sits above the fold; move the demo GIF above the badges.
- Reframe "How PMG Works" as defense in depth with three explicit
layers (Threat Intelligence, Policy/Cooldown, Sandbox) and promote
Dependency Cooldown out of the Features table.
- De-duplicate install instructions between Quick Start and Installation,
and drop the all-"Yes" Status column from the supported package
managers table.
- Update GitHub Actions snippet to actions/setup-node@v6 and add a
comment recommending SHA pinning for third-party Actions.
- Add Mini Shai-Hulud (300+ npm packages compromised) to the recent
malicious-package examples.
- Tighten prose: remove filler adverbs, passive voice, em-dashes, and
marketing fluff; split semicolon-joined clauses into separate
sentences.
* feat: add GitHub Action for one-step PMG setup in CI
Composite action at repo root that downloads PMG (with SHA-256 verification
against the upstream checksums.txt), runs `pmg setup install`, and wires
shims onto $GITHUB_PATH so subsequent `npm install` / `pip install` calls
are transparently analyzed.
Defaults are conservative: malware blocking + dependency cooldown + proxy
mode (matching PMG's own defaults). Sandbox is opt-in because enabling
Landlock/Bubblewrap on ubuntu-latest requires relaxing AppArmor
user-namespace restrictions.
Cloud sync uses the documented SAFEDEP_API_KEY / SAFEDEP_TENANT_ID env-var
fallback so we skip the keychain codepath that has no usable backend in
headless CI. When cloud is enabled and no endpoint-id is supplied, the
action sets PMG_CLOUD_ENDPOINT_ID=github-actions/${GITHUB_REPOSITORY} so
events aggregate per repository instead of per ephemeral runner hostname.
Closes#248.
https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh
* fix(action): drop github.repository template from input description
Action manifest validation rejected the action.yml because the endpoint-id
input description contained ${{ github.repository }} — template expressions
aren't evaluated in input description text and trip the validator with
"Unrecognized named-value: 'github'". This caused every job using uses: ./
to fail before any step ran.
Also switch the config-file e2e job to verify the staged file directly
instead of calling `pmg config get`, which is not in the v0.13.0 release
that "latest" resolves to today.
https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh
* fix(action): address review comments on PR #263
- Drop opinionated defaults on PMG_* toggle inputs. All defaults are now
empty strings, and the action only exports PMG_* env vars when the
caller explicitly sets the input. Without this, defaults like
PMG_PARANOID=false silently shadowed config-file overrides because env
vars beat config.yml in Viper precedence.
- Verify cached PMG against upstream checksums.txt on every cache hit.
The cached tarball is stored alongside the binary and re-hashed against
the freshly-fetched checksums.txt; on drift, the cache entry is evicted
and re-downloaded.
- Export PMG_* env vars BEFORE running `pmg setup install` so settings
like disable-telemetry actually apply during setup, not just to
subsequent package-manager calls.
- Add `|| true` to the grep that extracts the expected checksum so
set -e doesn't kill the script before the friendly error message fires
when no checksum entry is found.
- Pin third-party actions (actions/checkout, actions/setup-node) to
commit SHAs to match the repo's supply-chain hardening convention.
- Fix the malicious-package E2E test capturing tee's exit code instead
of npm's; redirect to a file and check the actual command exit code.
- Add an E2E job that asserts PMG_PARANOID is unset when only
config-file is provided — regression guard for the precedence fix.
https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh
* ci(action-e2e): scope sandbox tests to action setup, not PMG runtime
The landlock job was running `npm install express` with no explicit
sandbox profile and the default profile blocks something npm needs
(PMG's own e2e uses `--sandbox-profile npm-restrictive` to make this
viable). Bubblewrap happened to pass, but verifying the default sandbox
profile is permissive enough for arbitrary package installs is PMG's
e2e responsibility — this workflow's job is to assert the action wires
sandbox config correctly.
Switch both drivers to a matrix and verify only what the action owns:
PMG_SANDBOX_* env vars propagated, pmg binary runs, bwrap is installed
when requested, AppArmor user-ns restriction relaxed.
https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh
* ci(action-e2e): bump setup-node to 24
Node 20 reached end-of-life and setup-node now warns on it. Match the
version pinned by publish-npm.yml (the repo's newest workflow). Updated
the README and docs/github-action.md quick-start examples to match.
https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
* 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
* feat: Add experimental proxy based npm interceptor
* refactor: Analysis cache
* ci: Add E2E for npm proxy
* fix: Handle dry-run in proxy flow
* fix: Handle special case for scope package name
* fix: Misc fixes
* fix: Code review fixes
* fix: Code review fixes
* refactor: Reusable code into base registry interceptor
* Pause npm process during user confirmation (#90)
* pause npm process when prompting user for confirmation
* disable progress bar
* fix logging and close chan on return
* update use of deprecated field
* refactor: Separation of concerns for handling process state
* fix: Safe permission for cert file
* fix: Handle nil check for interaction hook
* fix: Add test for base registry
* Fix goreleaser for windows build (#93)
* introduce platform specific process control
* rename common.go to common_flow.go
* feat: Add support for pause resume on windows
* fix: Code review fixes
* test: Add confirmation handler tests
---------
Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
* introduce a persistent config
* add tests and refactor config creation
* update config handling and add support for removing config
* add support to skip suspicious pkgs marked as trusted
* add support for config dir Env & unexport functions
* small fixes
* add assert for dir
* fix tests
* fix shell source line & trusted pkgs parsing
* fix flag inconsistency
* update config to read on each invocation and create if does not exist
* fix flags value being overridden
* remove redundant func call
* modify trusted pkg check to be config bound
* modify RemoveConfig to rm files & not dir. add tests for paths.go
* add versions for package for e2e
* modify tests to reset config
* fix: Simplify config persistence
* fix: Misc comments
* fix: Misc fix
* fix: Do not overwrite config file if exists
* fix: Do not overwrite config file if exists
* fix: Config cobra command should override and not replace
* fix: Create dir before writing config template
* fix: Create dir before writing config template
* fix: Misc refactoring
* test: Add test for is trusted package version
* Update cmd/setup/setup.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
* Update config/config.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
* Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
* fix: Remove unused constant in config
* fix: Resolve conflict with event logger
* docs: Add doc for eventlogger.Logger interface
* test: Add E2E for config file creation
* fix: Code review fixes
---------
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Sahilb315 <bansalsahil315@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>