235 Commits
Author SHA1 Message Date
Abhisek DattaandGitHub c601e17cdc fix: Sandbox profile for Go ecosystem (#361)
* fix: Sandbox profile for Go ecosystem

* fix: Sandbox violations for network bind
v0.22.1
2026-07-06 23:38:38 +05:30
02965143d0 fix: fail fast when package manager is not installed (#360)
* fix: fail fast with clear error when package manager is not installed

When PMG shims intercept a package manager that isn't installed (e.g. a
clean JAMF-provisioned laptop where PMG is set up before dev tooling),
real-binary resolution failed with a generic "unknown" error and a
bug-report link, making it look like PMG itself had crashed.

Introduce a typed BinaryNotFoundError that exits with code 127 (standard
"command not found") and maps to a new PackageManagerNotFound error code
with an actionable message instead of the Unknown classification.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: improve missing package manager help message

Use a concise, dynamic install hint instead of explaining PMG's
PATH forwarding internals.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: mention PATH in missing package manager help text

Covers the common case where a package manager is installed but its bin
directory is not on PATH yet (e.g. after curl | bash install).

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
v0.22.0
2026-07-06 18:31:18 +05:30
b19473945b Add experimental Go module proxy support (#358)
* 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>
2026-07-03 18:19:31 +05:30
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
Sahil BansalandGitHub 0fb6faa951 add runner hostname to CI invocation context (#356)
* add runner hostname to CI invocation context

* update test
v0.21.3
2026-07-01 17:23:59 +05:30
Kunal SinghandGitHub f9ca26d112 Add Trendshift badge to README (#355)
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2026-06-30 13:25:10 +05:30
Sahil BansalandGitHub 2a7ecdb556 ci: add E2E for pmg action server-mode (#354)
* ci: add E2E for pmg action server-mode

Exercises the safedep/pmg action with server-mode enabled: the action
starts the proxy daemon and injects HTTP_PROXY + CA env vars, then bare
npm/pip installs are intercepted. Asserts benign installs pass, malicious
npm/pip installs are blocked, and 'pmg proxy stop --fail-on-violation'
fails the job on the blocks.

Scoped via paths to action.yml and the proxy server sources so it only
runs when relevant. Complements persistent-proxy-e2e.yml, which covers
the branch's proxy internals via raw pmg proxy commands.

* ci: extract shared proxy E2E assertions into composite action

Both the action server-mode E2E and the persistent-proxy E2E run the same
benign/malicious install assertions. Factor them into a local composite
action (.github/actions/proxy-e2e-assertions) so the assertions live in one
place and both workflows exercise the composite itself.

Proxy teardown (pmg proxy stop --fail-on-violation) stays inline in each
workflow since the setup/teardown differs per binary-under-test (released
binary via the action vs. branch build via make).
2026-06-26 16:33:37 +05:30
Abhisek DattaandGitHub 7b13b6bfb3 docs: Update README GHA example (#353) v0.21.2 2026-06-26 09:47:14 +00:00
Sahil BansalandGitHub 5ac0f275b8 feat(proxy): emit session summary on persistent proxy shutdown (#352)
* feat(proxy): emit session summary on persistent proxy shutdown

The persistent proxy daemon never ran the per-invocation flow that calls
LogSessionComplete, so no session summary reached the cloud for server-mode
runs. Emit one from the daemon's aggregate stats collector on shutdown,
before the final cloud flush, so it is delivered with the run's events and
carries the CI/invocation context.

Extract the event emission into a shared LogSessionSummary(SessionData); both
the per-invocation flow and the daemon now use it. The daemon serves every
package manager, so the summary carries no single package manager.

* chore(action): shorten default cloud endpoint id prefix to gha/
v1 v0.21.1
2026-06-26 14:47:53 +05:30
c47776db27 feat(proxy): persistent proxy server mode (#351)
* refactor(flows): extract SetupCACertificate for reuse

Move the CA load/generate/merge logic out of proxyFlow into an exported
flows.SetupCACertificate so the persistent proxy server can reuse it.

* feat(proxy): add persistent proxy server with start/stop/env/status

Introduces 'pmg proxy' commands backed by internal/proxyserver: a long-lived
MITM proxy that intercepts package managers via env vars (no shims). Supports
--daemon (Unix), --state, --port; generic 'env' output that skips cert vars
when the CA is OS-trusted; opt-in 'stop --fail-on-violation' (fail-closed on
crash) with a synchronous cloud event flush; and the malysis analysis cache.

* feat(action): add server-mode for persistent proxy

When server-mode=true the action starts the proxy daemon and injects proxy
env vars into the job instead of installing shims.

* test(proxy): add persistent proxy server E2E workflow

* docs(readme): document persistent proxy server mode

* fix(proxy): create cache dir before writing state file and daemon log

On a fresh CI runner the cache directory does not exist yet; os.OpenFile and
os.WriteFile do not create parent dirs, so 'pmg proxy start --daemon' failed
with 'no such file or directory'. MkdirAll the parent before writing.

* docs: add persistent proxy server architecture doc

* refactor proxyserver

* fix(proxy): always emit cert env vars instead of skipping on OS-trust status

npm/pip/yarn/requests trust the MITM CA inconsistently across tools, versions,
and configs; many still use bundled CA stores. Always emitting the cert-path
env vars is the conservative choice that works regardless, and is harmless for
tools that read the OS store (they ignore the vars). Skipping them when a
system CA exists would silently break any tool still on a bundled store.

* refactor(proxy): drop redundant audit init in daemon; rely on main.go

main.go's PersistentPreRun already initializes the audit pipeline for every
command (including the daemon's re-exec'd child) and closes it at process exit.
Re-initializing in proxyserver.Run created a second auditor and a second
cloud-sync WAL connection, orphaning the first. Removing it makes the daemon
consistent with the normal proxy flow, which never self-initializes audit.

* fix(proxy): bypass proxy env when flushing events to cloud on stop

pmg proxy stop inherits HTTP(S)_PROXY (injected by 'pmg proxy env') pointing at
the PMG proxy it just shut down. The cloud sync gRPC client honored those vars
and routed api.safedep.io through the dead proxy, failing with 'connection
refused' so no events were delivered. Clear the proxy env vars before the sync
so PMG's own cloud traffic goes direct.

* chore(proxy): address review feedback

- configurable bind host via proxy.server.listen_host (default loopback)
- proxy commands use ui.ErrorExit instead of returning errors to cobra
- rename errcode to ProxyPolicyViolation (covers malware + cooldown)
- share cloud sync via audit.DrainToCloud (de-dup with cmd/cloud/sync)
- centralize proxy CA bundle path in certmanager
- docs: persistent proxy cert trust + bind address

* fix(proxy): show real message on fail-on-violation error

stopExitError set only WithMsg, but ui.ErrorExit renders HumanError, so the
framed error showed 'no human-readable message available'. Set both from one
string, and emit the framed error before the stdout summary so the blocked
count is stated once.

* fix(proxy): flush cloud events from the daemon, not stop

The stop process inherits HTTP_PROXY (from 'pmg proxy env'), so its cloud
client routed api.safedep.io through the already-stopped proxy and failed with
connection refused. Move the flush into the daemon's shutdown, which has no
proxy env (it started before env injection) and dials SafeDep directly.

- daemon flushes on shutdown via audit.DrainToCloud and records the result in
  the state file; stop surfaces it (on both success and fail-on-violation
  paths) since the daemon's own logs aren't visible to stop
- coordinate stop's wait with the daemon shutdown budget; on timeout, error
  out without reading stale state or deleting the file (fail-closed)
- persist blocked count before the flush so the gate stays correct if the
  flush hangs or the daemon is killed mid-flush
- remove now-redundant cloud_flush.go

* disable auto-sync for proxy cmds

* feat(proxy): periodic cloud sync + move proxy env vars to packagemanager

- daemon runs a periodic cloud-sync ticker so the shutdown flush stays small;
  the run total is reported by stop, and shutdown timeouts are coordinated
- move EnvVarForProxy from config to packagemanager (it is package-manager
  knowledge); the shared function now builds the proxy URL and NO_PROXY itself,
  removing the duplicated construction in the per-command and persistent paths
- relocate the #319 yarn and #339 IPv6 regression tests alongside the function
- enable cloud sync in the persistent-proxy E2E workflow and fix the stale
  internal/proxystate path filter

* refactor(proxy): rename cloudFlushLockTimeout to cloudFlushLockWait

Consistent timeout naming: *LockWait is the lock-acquire bound, *Timeout is
the sync-RPC bound. Previously the final-flush pair was cloudFlushLockTimeout
vs cloudFlushTimeout — two lookalike names for different operations.

* refactor(proxy): extract cloudFlush and trim duplicate shutdown comments

The shutdown's final-flush block is now a cloudFlush helper, symmetric with
startCloudSyncLoop (one-shot vs loop). Removed the triplicated ticker/lock
contention comments, keeping the contract on the function doc and one-line
pointers at the call sites.

* docs: update persistent proxy cloud sync to daemon-owned model

The daemon now owns cloud delivery (periodic sync while serving + final flush
on shutdown); stop signals it, waits, and reports the result. Rewrite the Cloud
event sync section, fix stop attributions, add the cloud_sync state field, and
update the sequence diagram.

* docs: move Usage section up below How it works

Put the copy-paste recipes near the top so users find them before the internals.

* refactor(proxy): address PR review feedback

- configurable bind host/port via --host/--port flags + config (listen_host,
  listen_port), bound directly to config fields per PMG's flag pattern
- daemon log path via --log-file and readiness timeout in ProxyDaemonConfig;
  Daemonize no longer owns path policy (caller validates, fails fast)
- gate periodic cloud sync on auto_sync; suppress detached background sync for
  proxy commands instead of flipping the flag
- pmg proxy env --export emits shell-quoted lines for eval (spaces survive)
- extract shared flows.BuildCachedMalysisAnalyzer, dropping the analyzer+cache
  duplication between proxy flow and proxy server
- add internal/proxyserver/doc.go documenting the package + boundary vs flows
- E2E: assert malicious installs are blocked (drop continue-on-error)
- docs: trim Commands/State-file to user contracts; refresh bind address

* refactor(proxy): proactive alignment fixes from whole-PR review

- gate the shutdown cloud flush on auto_sync too, matching the periodic ticker
  (auto_sync consistently controls all daemon-driven cloud delivery)
- ResolveStatePath takes cacheDir instead of *RuntimeConfig, keeping state.go
  free of config dependency
- drop the empty-host comment in listenAddr; keep the loopback guard so a blank
  host never silently binds all interfaces

* fix: Decouple localdb with malysis analyser construction

* fix: Persist global args before proxy server daemon exec

* fix: GitHub Action for cloud auto-sync in server mode

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
v0.21.0
2026-06-26 11:19:28 +05:30
Abhisek DattaandGitHub 7ee4187d50 refactor: Event log use audit event as SSOT (#350) 2026-06-24 15:43:20 +05:30
Abhisek DattaandGitHub 25dd12d7a4 test: Add proxy e2e test (#348)
* test: Add proxy e2e test

* fix: Code review fixes

* test: Add dependency cooldown skip list test case
v0.20.0
2026-06-23 22:21:57 +05:30
Abhisek DattaandGitHub d360e75897 feat: Add malysis cache implementation with proxy flow integration (#346)
* feat: Add malysis cache implementation with proxy flow integration

* fix: Code review fixes
2026-06-22 10:12:02 +05:30
327c9c7068 feat(cooldown): respect trusted_packages in dependency cooldown (#342)
* feat(cooldown): respect trusted_packages in dependency cooldown

Trusted packages are now treated as a superset waiver that bypasses every
PMG control (malware analysis, cooldown, and any future controls). A
globally trusted package is automatically exempt from the cooldown window
and no longer needs a duplicate entry in dependency_cooldown.skip.

The skip list remains the narrower, cooldown-only waiver for packages
that must bypass the cooldown wait but still be malware-scanned.

* refactor(cooldown): tag skip reason and audit-log skipped packages

Address review feedback on #342:

- Restore cooldownSkip to a pure single-list function (SRP); the merge
  into trusted_packages now happens in a separate mergeCooldownSkip step,
  driven by the exported CooldownSkip wrapper.
- Extend CooldownSkipInfo with a CooldownSkipReason (TrustedPackage /
  CooldownSkipList) on both SkipAll and per-version entries, so callers
  can tell apart the broad waiver from the cooldown-only one. When both
  lists match the same package, trusted_packages wins.
- Add audit.LogCooldownSkipped and emit it from the npm and PyPI
  interceptors on the SkipAll path, alongside the existing info log,
  carrying the source list as the reason.

* refactor(cooldown): inline list merge, audit per-version exemptions

Address further review feedback:

- Drop the separate mergeCooldownSkip helper; cooldownSkip now writes
  into a shared *CooldownSkipInfo and is called twice from CooldownSkip
  (cooldown skip list first, trusted_packages on top so trusted entries
  override the reason on overlap).
- Audit log every exemption, not just SkipAll: a new auditCooldownSkip
  helper in proxy/interceptors/cooldown.go emits one event per match
  (package-wide or per-version), each tagged with its source list.
  LogCooldownSkipped gains a version argument for the per-version case.
- Cover the trusted_packages reason path in TestCooldownSkip.

* fix(cooldown): avoid double-auditing trusted package exemptions

auditCooldownSkip now only emits EventTypeCooldownSkipped for entries
that came from dependency_cooldown.skip. Trusted-package exemptions
already get an EventTypeInstallTrustedAllowed event at tarball-download
time (proxy/interceptors/base_registry.go), so emitting a cooldown event
for them too would double-count the same waiver.

* emit trusted and cooldown skip events to cloud

* fix tests

* refactor(cooldown): return value from collectCooldownSkip, short-circuit on trusted SkipAll

Address PR review feedback:
- Rename cooldownSkip to collectCooldownSkip and return CooldownSkipInfo
  instead of mutating an input pointer.
- Add mergeCooldownSkip to combine per-list results with trusted_packages
  taking precedence on overlap.
- CooldownSkip now consults trusted_packages first and returns immediately
  on a package-wide trusted exemption (DC skip list cannot add anything).
- Extend tests to cover disjoint pinned entries across both lists and the
  case where DC version-less subsumes a trusted pinned entry.

* fix(audit): address cooldown review feedback

* fix(cooldown): audit cooldown skips at download time with concrete version

Backend rejects PackageVersion messages without a version, and audit logs
should reflect the runtime fact (a specific version was skipped) rather
than the config rule. Move the audit emission from metadata-request
handling to download-request handling, where the concrete version is
known, and require version in LogCooldownSkipped.

* chore(audit): drop dead scope assignment in LogCooldownSkipped

* refactor(cooldown): move skip-list logic into cooldown handlers

Registry interceptors no longer compute CooldownSkip or branch on SkipAll;
they just call HandleMetadataRequest. The npm and pypi cooldown handlers
own the skip lookup, the package-wide exemption short-circuit, and (for
pypi) the canonical-name denormalization. Also align LogCooldownSkipped
with other LogXxx signatures by taking *packagev1.PackageVersion.

* fix: Simplify audit logging for dependency cooldown skip

* refactor: Simplify cooldown handling and maintain separation of concepts for trusted and DC skip packages

* fix: Code review fixes

* fix: Emit cooldown skipped audit event ONLY when an in-window version is skipped

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2026-06-21 18:22:15 +05:30
c17b941ac3 fix: expand ${CWD}/${HOME}/${TMPDIR} in --sandbox-allow path overrides (#344)
The runtime --sandbox-allow CLI override path never expanded the supported
sandbox variables (${CWD}, ${HOME}, ${TMPDIR}), so a value like
write='${CWD}/**' was treated as a literal path segment and the allow rule
never matched. Profile-loaded sandbox paths already expand these via
sandbox/util.ExpandVariables.

Expand the variables in resolveToAbsolute, the shared chokepoint for
read/write/exec overrides, before resolving to an absolute path. Glob
characters are preserved through expansion and filepath.Clean.

Fixes #257

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-18 12:17:33 +05:30
Abhisek DattaandGitHub 55f3f2a252 fix: Decouple cloud sync from telemetry (#343) 2026-06-17 16:03:26 +05:30
dmdhrumilmistryandGitHub baf637be97 feat(analyzer): analysis cache contract (MalysisCache interface + config) (#334) 2026-06-17 15:07:47 +05:30
61230fbcd7 feat(cooldown): add dependency_cooldown.skip list (per-control exemption) (#328)
Let dependency cooldown respect an explicit skip list so first-party /
internal packages that must be installed the moment they are published
(e.g. to sanity-test a freshly released version) are not held back by the
cooldown window.

Per review, this is a per-control skip list — NOT a second definition of
"trusted package". There remains a single top-level `trusted_packages`
(which waives malware analysis); `dependency_cooldown.skip` waives ONLY
the cooldown wait, so a fast-tracked package is still malware-scanned.

Matching:
- a PURL without a version skips cooldown for all versions of the package
  (package-level) — the metadata passes through unmodified;
- a PURL with a version skips cooldown for that version only — that
  version is preserved during stripping while other recent versions are
  still held.

- config: DependencyCooldownConfig.Skip + CooldownSkip()/CooldownSkipInfo.
- npm/pypi interceptors: bypass on package-level skip; thread per-version
  exemptions into the cooldown stripper so pinned versions survive.
- docs + config template; unit tests for the matcher (package/version
  level, precedence, mismatches) and the skip-vs-trusted independence.

Signed-off-by: dmdhrumilmistry <56185972+dmdhrumilmistry@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:44:55 +05:30
Abhisek DattaandGitHub 26d5c0ad71 test(e2e): verify proxy-mode NO_PROXY does not crash Python httpx (#341) v0.19.1 2026-06-15 15:40:02 +05:30
6d82b95e78 fix: use bare ::1 in NO_PROXY to avoid crashing Python httpx (#340)
* fix: use bare ::1 in NO_PROXY to avoid crashing Python httpx

Bracketed [::1] is URL authority syntax, not valid NO_PROXY syntax.
Python's urllib/httpx parses bracketed entries as a URL and crashes
with 'Invalid port: :1]'. Use the bare IPv6 loopback ::1 instead,
which both Node and Python accept.

Fixes #339

https://claude.ai/code/session_01NnkUKCn82Dc83VandSsgim

* chore: condense NO_PROXY comment

https://claude.ai/code/session_01NnkUKCn82Dc83VandSsgim

* docs: note Node IPv6 literal NO_PROXY trade-off

https://claude.ai/code/session_01NnkUKCn82Dc83VandSsgim

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-15 15:29:00 +05:30
9a02e8de87 Respect explicitly set CI environment variable (#336)
* 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>
v0.19.0
2026-06-13 09:56:43 +05:30
788a031003 Fix glob parent directory allowance for patterns with glob characters (#331)
* 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>
2026-06-12 12:11:12 +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
7620097613 feat : adds pipx support to PMG (#292)
* adding the pipx support to project

Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>

* create excutors for pipx

Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>

* add pipx yml

Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>

* chores

Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>

* pipx to use standard executor pattern

Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>

* Address PR feedback for pipx support

Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>

* Update pipx flags comments

Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>

---------

Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
2026-06-10 16:26:33 +05:30
Abhisek DattaandGitHub ab1a5386d3 fix: Malysis analyzer should fallback to Community Mode when API Credentials are Incorrect (#325) 2026-06-10 13:42:09 +05:30
9714a6f4c2 fix(pty): treat background jobs as non-interactive to avoid SIGTTOU stop (#324)
* 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 #322

https://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>
v0.18.2
2026-06-10 09:57:08 +05:30
Sahil BansalandGitHub 141894ed8f fix(shim): recognize shims at arbitrary paths via PMG_SHIM_PATH (#323)
* fix(shim): recognize shims at arbitrary paths via PMG_SHIM_PATH

The recursion guard in FilterPMGFromPath hardcoded the `/.pmg/bin`
suffix, so shims placed anywhere else (e.g. `/usr/local/lib/pmg/bin`,
`/shims`, or any future system-wide location) would not be stripped
from PATH when PMG resolved the real package manager. The shim would
resolve back to itself and PMG would re-exec it in an infinite loop.

This blocks moving shims out of `~/.pmg/bin` — needed for a future
`pmg setup install --system` (#317) — and also any user attempt to
relocate shims manually.

Have the shim export its own path before exec'ing pmg, and let the
filter use that to strip the exact dir at runtime. Keep the legacy
suffix check as a fallback so already-installed shims keep working
until they are regenerated.

Also drop `PMG_SHIM_PATH` from the env passed to the real package
manager so child processes don't inherit a stale marker.

* docs(shim): clarify PMG_SHIM_PATH is internal and unsupported to set manually

* remove comment

* update comment
2026-06-09 20:33:14 +05:30
Sahil BansalandGitHub 872c5d663c fix(sandbox): bind parent dir for globstar allow_write on bwrap (#321)
* fix(sandbox): bind parent dir for globstar allow_write on bwrap

Fine-grained per-path mounts under read-only project binds broke pip
install into in-project .venv directories. Always mount the parent tree
for ** write rules instead.

Fixes #315

* test(sandbox): tighten globstar bind assertions and ensure ~/.npm exists for e2e

Strengthen TestBubblewrapAllowWriteGlobstarBindsParentOnly to verify the
parent dir is writably bound and the child path is read-only bound, not
just substring presence. Pre-create ~/.npm in the e2e harness so
bubblewrap --bind-try does not skip the npm cache dir on fresh runners.

* switch pnpm to /tmp in sandbox e2e

* test(sandbox): update glob ** test for parent-bind semantics

Globstar allow_write now binds the parent dir only (e2e740d), so the
test should assert the parent is writably bound and child subdirs are
not individually bound, instead of substring-matching subdir names.

* fix(sandbox): bind correct base dir for in-pattern globstar allow_write

Globstar allow_write previously used extractGlobParentDir, which walks past
the first ** and yields the wrong root for patterns like /a/b/**/d/**/e.
Introduce extractGlobstarWriteBaseDir, which takes the prefix before the
first /**, and use it in processWriteRule. Also dedup the coarse-fallback
parent-bind loop to mirror the read-rule fallback.
v0.18.1
2026-06-07 10:03:23 +05:30
Abhisek DattaandGitHub f3e00a7f6e fix: yarn proxy mode proxy environment injection (#320) v0.18.0 2026-06-05 18:39:37 +05:30
Abhisek DattaandGitHub 4f0db15ede feat: Add opt-in support for proxy CA cert installation (#318)
* feat: Add opt-in support for proxy CA cert installation

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* docs: Add limitation for MacOS MDM script
2026-06-03 23:15:02 +05:30
c3f3920d2e fix(proxy): harden MITM proxy reliability and scale for bulk installs (#314)
* 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>
2026-06-02 21:41:57 +05:30
Sahil BansalandGitHub 0f084931c6 docs: add safe-chain to comparison table (#316) 2026-06-01 17:28:25 +05:30
f6e1d9e733 feat: authenticated Malysis analyzer with tenant exclusion support (#313)
* 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>
2026-06-01 15:32:22 +05:30
5018f8e2ff fix: Suppress non-PMG error from Critical UI Error (#311)
* fix: Suppress non-PMG error from Critical UI Error

* fix: Code review fixes

---------

Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
v0.17.4
2026-05-29 14:49:53 +00:00
Sahil BansalandGitHub db1a5e58b3 fix: bump ptyx to fix debug log staircasing in raw mode (#312) 2026-05-29 20:04:28 +05:30
6087bc922f feat: populate CI invocation context on cloud events (#304)
* feat: add CloudSinkEnvResolver interface with default implementation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add GitHub Actions environment resolver for cloud sink

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: populate invocation context with CI environment on cloud events

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address lint errors in cloud sink tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: use getter-based CloudSinkCIResolver with nil-when-no-CI

Rename to CloudSinkCIResolver with focused CI concern. Factory returns
nil when no CI is detected, removing the need for IsCI() and a default
resolver. Leaves room for a separate agent resolver in the future.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add CI metadata support using updated API SDK

Update SDK to include SetMetadata on EndpointCIContext. Add Metadata()
to CloudSinkCIResolver interface and GitHub Actions implementation
(workflow, job, run_attempt, server_url). Wire metadata into
buildInvocationContext.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: address review comments on CI resolver

- Inject CloudSinkCIResolver as dependency into newCloudSink for testability
- Check both GITHUB_ACTIONS and GITHUB_RUN_ID for GHA environment detection
- Make factory and constructor package-private (newCloudSinkCIResolver,
  newGithubActionsCIResolver)
- Attach invocation context only to session complete events, not every event

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: fail fast on os.Getwd error instead of swallowing it

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
v0.17.3
2026-05-28 19:12:12 +05:30
Abhisek DattaandGitHub 4540dafccc fix: Handle platform specific PTY polling for terminal copy (#279)
* fix: Handle platform specific PTY polling for terminal copy

* fix: Code review fixes

* fix: Code review fixes
2026-05-28 18:11:11 +05:30
Arunanshu BiswasandGitHub 1c25395d74 feat: migrate pmg to nx based release automation (#293)
* feat: Migrate release system to Nx with platform-specific npm packages

* add go.work.sum

* fix: CI deprecations, stale action pins, and signal propagation

* fix: update e2e workflows to pnpm 11 and latest action SHAs

* fix: update pmg-e2e.yml to Node 24 with Go and pnpm caching

* fix: resolve E2E failures, remove goreleaser-test, update action SHAs

* fix: restore goreleaser-test (required check)

* fix: escape pnpm workspace detection for yarn/pnpx tests, update action versions
v0.17.2
2026-05-28 17:22:11 +05:30
Sahil BansalandGitHub 19b9f2ca1f fix: avoid resolving symlinks in shim PMG binary path (#303)
* fix: avoid resolving symlinks in shim PMG binary path

Shims hardcoded the resolved Cellar path (e.g.
/opt/homebrew/Cellar/pmg/0.16.0/bin/pmg) instead of the stable
Homebrew symlink. This broke all shims after `brew upgrade` since
the old Cellar directory is removed.

Fixes #302

* fix pip test pkg version
v0.17.1
2026-05-27 16:00:43 +05:30
37958f4441 docs: mark Socket as supporting no account or API key (#299)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-27 12:12:11 +05:30
a651761636 docs: Update README (#296)
* docs: Update README

* docs: Add diagram for how PMG works

* docs: Updated featured docs

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Update README.md

Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
2026-05-26 16:45:14 +00:00
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
v0.17.0
2026-05-26 21:06:57 +05:30
Sahil BansalandGitHub 321896996f fix: skip PyPI dependency cooldown for clients without PEP 691 support (#295)
PMG was forcing Accept: application/vnd.pypi.simple.v1+json on all
Simple API requests regardless of client capability. Older pip versions
(< 22.3) that only understand text/html would reject the JSON response,
breaking installs entirely.

Now checks the client's original Accept header before applying cooldown.
If PEP 691 is not supported, the request passes through unchanged with
a warning log recommending pip upgrade.
2026-05-26 20:50:10 +05:30
Sahil BansalandGitHub 083f82dd79 feat: Add pmg setup doctor command (#290)
* feat: add doctor check runner core types and logic

* feat: add doctor checks for config, binary, directory, and aliases

* feat: add doctor checks for sandbox and security features

* feat: add protection verification check using test malicious packages

* feat: add summarized package manager availability check

* feat: add pmg setup doctor command with compact output

* feat: add PATH shim verification to doctor command

* fix: improve doctor command UX and alias detection

- Capitalize all check messages for consistent output
- Dim passing checks, color warn/fail for visual clarity
- Silence empty Cobra error output on doctor failure
- Remove redundant pmg binary check (self-evident)
- Fix alias IsInstalled to skip commented-out source lines
- Improve protection failure message

* refactor: remove package manager availability check from doctor

* fix: handle os.RemoveAll error in doctor protection check

* refactor: use table layout for setup doctor, extract shared table renderer

Move renderTable, truncate, and visibleWidth helpers from cmd/sandbox
to internal/ui so both sandbox and setup doctor share them. Rewrite
setup doctor output to use the same table structure as sandbox doctor.
Fix VisibleWidth to count runes instead of bytes for correct alignment
with multi-byte UTF-8 characters.

* docs: add pmg setup doctor to README, remove manual verification step

* refactor: use constants for check names, rename and inline doctor helpers

Address PR review comments: extract check name constants, rename
CheckConfigFile to CheckFileExists and CheckDirectoryWritable to
CheckDirectoryExists for reusability, inline trivial wrappers
(CheckSandbox, CheckSecurityFeature, CheckProxyMode), and add
fix hints for all checks with correct config keys.

* refactor: inline simple doctor checks into command layer

* fix: skip protection check when aliases and shims are inactive

Protection checks now fail immediately when shell aliases and shims
are both inactive, instead of falsely passing by running through the
pmg binary directly. Also clean up summary messages to remove
redundant fix hints and truncated paths.
2026-05-26 12:21:39 +05:30
Abhisek DattaandGitHub 8cb7b7d1c0 fix: npm Dependency Cooldown Select Stable Version (#291)
* fix: npm Dependency Cooldown Select Stable Version

* fix: Code review fixes
2026-05-25 14:38:39 +05:30
dc3f3e6618 Promote gofrs/flock to direct dependency (#281)
Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com>
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2026-05-25 02:16:48 +00:00
DivyanshuandGitHub 88d8a566bc ci: pin codeql-action to commit SHA for Node 24 support (#289) 2026-05-25 07:39:19 +05:30
Abhisek DattaandGitHub ffb4e6dd1a chore: Update DRY to fix stable endpoint identity in CI/CD (#287) 2026-05-24 15:06:35 +05:30
Abhisek DattaandGitHub 219d743b80 chore: Standardise Error Codes (#286)
* fix: Misc error handling fixes

* fix: Sandbox error translation
2026-05-24 12:46:06 +05:30
20e01d5cae refactor : Refactor error handling to use dry/usefulerror (#283)
* 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>
2026-05-24 12:22:19 +05:30