* 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>
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>
* refactor: Extract shared cooldown helpers to package-level functions
* feat: Add PyPI cooldown handler with PEP 691 file parsing
* feat: Add PyPI cooldown file stripping logic
* feat: Implement PyPI cooldown HandleMetadataRequest with PEP 691 filtering
* feat: Wire PyPI cooldown into pypi_registry interceptor
* update headers for no cache
* fix: Strip conditional GET headers to prevent 304 bypass in cooldown handlers
pip and npm clients cache Simple API / registry responses with ETags. On
subsequent requests they send If-None-Match, which causes the server to
return 304 Not Modified with no body. The cooldown response modifier
received an empty body, failed to parse it, and failed-open — letting
the client use its stale cached (unfiltered) response.
Fix: delete If-None-Match and If-Modified-Since from the request before
forwarding, forcing a full 200 response so the modifier always has a
body to filter.
Also removes the Content-Type guard from the PyPI modifier (the empty
Content-Type on 304 responses was a symptom of the same root cause) and
replaces Cache-Control: no-cache with the more targeted header deletion.
* docs: Add PyPI cooldown limitation for pip < 22.3 to dependency-cooldown docs