Switch the audit cloudSink from the authenticated SyncClientBundle to
dry's new emit-only endpointsync.EventEmitterClient. Normal command
invocations now persist events to the local cloud-sync WAL without
resolving credentials or opening a data-plane connection; delivery
still happens only through the authenticated sync paths (pmg cloud
sync, sync-background auto-sync, and the proxy daemon drain), which
are unchanged.
Previously, running with cloud.enabled but no credentials skipped the
cloud sink entirely, so no events were recorded. Those events are now
staged in the WAL and sync once the user authenticates.
Co-authored-by: Claude Fable 5 <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>
* 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>
* feat: emit cloud events for dependency cooldown and host observations (#237)
Wire cooldown blocks and proxy host observations through the cloud sync
pipeline so they appear as telemetry in Control Tower.
- Cooldown blocks emit PACKAGE_DECISION with COOLDOWN_BLOCKED action and
PmgDependencyCooldown context (publish date, cooldown days, days since
publish, days remaining)
- Proxy host observations emit HOST_OBSERVATION with PmgHostObservation
(hostname, method)
- Session summary now includes cooldown_blocked_count
- Updated buf API dependency for new proto schema
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* format file
* fix: add explicit eventlog mapping for EventTypeDependencyCooldown
Follow the existing pattern where every audit event type has an explicit
case in mapEventType and a corresponding constant in the eventlog package.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Apply suggestion from @devin-ai-integration[bot]
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
---------
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>