* docs(specs): add custom block messages and package blocklist spec
* docs(specs): add custom block messages and package blocklist implementation plan
* feat(config): add blocked_packages list and custom block messages
* feat(audit): add package_blocklist_blocked event and blocklist model
* feat(proxy): block blocklisted packages in the policy gate before analysis
* feat(guard): block blocklisted packages before trust skip and analysis
* feat(ui): render blocklist blocks and custom messages, fix silent-mode block output
* feat(proxy): append custom messages to malware and go-cooldown block bodies
* test(proxye2e): cover blocklist enforcement and custom block messages
* docs(specs): remove spec and plan documents
* refactor: drop guard-flow blocklist enforcement and trim docs
Guard mode is being deprecated; the blocklist is enforced in proxy mode
only. Remove the trusted_packages mirroring references outside the docs.
* refactor(config): consolidate blocklist and block message under top-level block section
Replace dependency_cooldown.message, malware.message and blocked_packages
with a single block section: block.message is appended to every block
output regardless of which control blocked, and block.packages is the
package blocklist.
* fix(ui): render block.message as info note with clean spacing
* fix(ui): indent wrapped continuation lines in block reasons and messages
* update config template
* refactor(config): replace block section with top-level advisory_message
Remove the package blocklist (will be implemented as part of policies in
the future) and replace block.message with an optional top-level
advisory_message appended to every block output.
* chore(config): move advisory_message near top-level scalar configs in template
* 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>
* 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.
* 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