Repository navigation
feat(security)!: enforce trustPolicy by default, add paranoid bundle, security docs - #333
Conversation
…ust evidence Mirrors pnpm's `failIfTrustDowngraded` algorithm. Two evidence sources ranked `_npmUser.trustedPublisher` (2) > `dist.attestations.provenance` (1); installation fails when an earlier-published version of the same package carried stronger evidence than the version being picked. Previously `trustPolicy`, `trustPolicyExclude`, and `trustPolicyIgnoreAfter` were plumbed end-to-end into `DependencyPolicy` but the resolver never read them — a silent no-op that made the documented setting misleading. The settings.toml docstring even acknowledged the gap. `trustPolicyExclude` parser supports the full pnpm syntax: `name`, `name@1.0.0`, `name@1.0.0 || 1.0.1` (exact versions only — ranges rejected), `is-*` name globs, and scoped names. Exclude check runs before the time lookup so excluded packages don't error on registries that omit `time`. Picking a stable version ignores prior prerelease evidence (matches pnpm v10.24.0+). Default stays `off`; planned to flip to `no-downgrade` in the next major version (mirrors the `jailBuilds` v2-default plan). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Builds `paranoid=true` forces both `trustPolicy = no-downgrade` (override) and `jailBuilds = true` (logical OR), regardless of how those settings are configured individually. One switch for the strict-security posture without remembering each underlying setting name. Also fixes formatting that CI rejected on the previous commit (rustfmt wanted alternate line breaks in three trustpolicy files). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers the full security feature surface in one place: paranoid switch, default-deny lifecycle scripts, jailed builds, trustPolicy, minimumReleaseAge, blockExoticSubdeps, tarball/CAS integrity, auth-token handling, and `aube audit`. Each section links to the settings reference. Root SECURITY.md is the GitHub-recognized vulnerability disclosure file — points reporters at the private security advisory form (issues are disabled on this repo) and gives a one-glance feature table linking to the docs page. Sidebar gets a new "Security" section with the overview + jailed builds entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README "Secure defaults" bullet now points at the security overview rather than just the jailed-builds page, lists trust downgrades alongside the existing protections, and surfaces the `paranoid: true` master switch. Hero proof-item retargeted from /package-manager/jailed-builds to /security, with the visual rows updated to reflect what's actually default-on today (exotic deps blocked, trust downgrades blocked, jailed builds opt-in). Headline broadened from "Jailed builds for dependency scripts" to "Supply- chain defaults across the install path". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile SummaryWires the previously-stubbed Confidence Score: 5/5Safe to merge; all findings are P2 (cosmetic error-message quality and a PR-description discrepancy). No P0 or P1 bugs found. The algorithm correctly mirrors pnpm's No files require special attention; Important Files Changed
Reviews (4): Last reviewed commit: "fix(resolver): align TrustPolicy::defaul..." | Re-trigger Greptile |
The corgi-shortcircuit at resolve.rs:219-235 returns the abbreviated packument early when the top-level `modified` timestamp predates the `minimumReleaseAge` cutoff, on the assumption that every selectable version is old enough to skip the per-version time check. That assumption breaks when `trustPolicy=NoDowngrade` is also active — the trust check needs per-version `time` entries to compare evidence across versions, and corgi packuments don't carry them. Without the fix, the install fails with a spurious `TrustCheckMissingTime` error on every version when both `minimumReleaseAge` and `trustPolicy=NoDowngrade` are set. Reported by Cursor Bugbot on PR #333. Regression test in trust_policy_disables_minimum_release_age_short_circuit verifies the shortcircuit is suppressed by serving differentiated corgi vs full responses based on the Accept header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…E copy Three review-comment fixes from greptile-apps[bot] on PR #333: 1. `is_truthy` now matches JS semantics — falsy includes `0`, `0.0`, `""`, `NaN`, not just `null`/`false`. In practice `_npmUser.trustedPublisher` is always an object or absent, but matching JS keeps a hostile or buggy registry from sneaking trust evidence past us via `0` or `""`. 2. `TrustExcludeRules::parse_lossy` returns `(Self, Vec<Error>)` so the caller can log per-pattern errors without parsing the list twice. The previous code parsed each entry to filter, then re-parsed the whole list and silently fell back to empty rules on any error — exactly the security-regression shape the comment warned against. 3. README and landing-hero copy no longer imply that trust-downgrade detection and the build jail are default-on. Both genuinely default to off (planned to flip in v2). The bullet now separates real defaults (exotic deps blocked, lifecycle scripts deny-by-default) from `paranoid: true` opt-ins (trust-downgrade detection, jailed approved scripts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… bundle
Two behavior changes for v1.next:
1. `trustPolicy` default flipped from `off` to `no-downgrade`. Installs
that previously succeeded will now fail at resolve when a picked
version carries weaker trust evidence than any earlier-published
version of the same package. Escape hatches unchanged:
`trustPolicy = off`, `trustPolicyExclude`, or `trustPolicyIgnoreAfter`.
Settings.toml's "planned to default in next major" disclaimer is gone;
this is now the live default.
2. `paranoid` bundle extended from {trustPolicy, jailBuilds} to also force
`minimumReleaseAgeStrict`, `strictStoreIntegrity`, and `strictDepBuilds`
on. The bundle now turns every soft gate into a hard fail.
Also fixes a Cursor Bugbot catch from the prior commit: the `provenance`
trust-evidence check used `.is_some()` while `trustedPublisher` used
`is_truthy`. Pnpm's source evaluates both fields in JS boolean context,
so `provenance` is now `is_truthy`-gated too — `provenance: false` no
longer counts as evidence.
README and landing-hero copy updated to reflect that trust-downgrade
detection is now default-on, not gated behind paranoid.
BREAKING CHANGE: installs of packages whose newest version lost trust
evidence (provenance attestation or trusted-publisher OIDC link) compared
to any earlier published version will now fail unless the user opts out.
The trust check fires only when the registry serves per-version `time`
entries; cold-cache installs see a slightly larger packument fetch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the existing `evidence_falsy_trusted_publisher_is_none` test for the provenance code path, locking in the `is_truthy` gate that 089631a added so a future refactor can't silently regress to `.is_some()` and let `provenance: false` count as evidence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit adf2b61. Configure here.
Bats failures on PR #333: every install against the local Verdaccio fixtures was failing with TrustCheckMissingTime because the fixtures ship packuments without per-version `time` entries. Same pattern exists for private registry forks and proxies that strip the field. When the entire `time` map is empty, skip the trust check rather than failing every install. Diverges from pnpm's strict-throw — defensible because pnpm requires opt-in (so users explicitly accepted the strictness contract), while aube now defaults trust on (so a single non-compliant registry would brick first-install for every aube user). Per-version time gaps (where `time` IS shipped but lacks the picked version specifically) still error — that's the registry-quirk shape pnpm's check is designed to catch, and skipping it would let an attacker with a tampered packument bypass the check by stripping just the relevant time entry. Also fixes greptile P1: SECURITY.md table had `trustPolicy` listed with default `true` (a boolean) — but trustPolicy is a string-enum setting. Now reads `"no-downgrade"`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The settings.toml default flipped to `no-downgrade` in 089631a, but the Rust-side `#[default]` on `TrustPolicy` was still `Off`. The install command path overrides via the codegen accessor so end users were fine, but library consumers calling `Resolver::new(...)` (which uses `DependencyPolicy::default()`) were silently getting trust checking disabled despite the docs. Caught by Cursor Bugbot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Summary
Activates
trustPolicy=no-downgradeend-to-end (previously a documented but no-op setting), adds aparanoidbundle switch for the strict-security posture, ships a security overview doc + GitHub-recognizedSECURITY.md, and flips thetrustPolicydefault to on. Mirrors pnpm'sfailIfTrustDowngradedalgorithm exactly — verified against pnpm's source + test suite at/tmp/pnpm.Trust enforcement
Trust evidence ranks:
_npmUser.trustedPublisher(2) >dist.attestations.provenance(1). Install fails when any earlier-published version of the same package carried stronger evidence than the picked one.crates/aube-resolver/src/trust.rs— exact mirror of pnpm'sfailIfTrustDowngradedincluding the v10.24.0+ "skip prior prereleases when picking stable" rule and the JS-style truthiness on both evidence fields.trustPolicyExcludeparser supports the full pnpm syntax:name,name@1.0.0,name@1.0.0 || 1.0.1(exact versions only —^/~/>=rejected),is-*name glob, scoped names. Exclude check runs before the time lookup so excluded packages don't error on registries that omittime.PickResult::Found, mirrors pnpm's placement. Forces the full-packument fetch path so per-versiontimeis available, including suppressing the corgi-shortcircuit when bothminimumReleaseAgeandtrustPolicyare active.timeat all (Verdaccio fixtures, private mirrors). Per-version time gaps still error — that's the registry-quirk shape pnpm's check catches.paranoidmaster switchparanoid: trueforces every setting in the strict-security bundle on, regardless of how each is configured individually:trustPolicyno-downgrade(default)no-downgrade(forced)jailBuildsfalsetrueminimumReleaseAgeStrictstrictStoreIntegritydist.integritystrictDepBuildsDefault flip
trustPolicydefaults tono-downgrade. Both the codegen settings layer andTrustPolicy::default()agree (the latter aligned with the former in30e3cd8so library consumers callingResolver::newdirectly inherit the documented behavior).Cost analysis: trust check itself is microseconds (a few
BTreeMap::getlookups). Cold-cache installs pay one full-packument fetch instead of corgi (~3-5× larger response); warm-cache installs are free. Same hitminimumReleaseAgealready takes today.Documentation
SECURITY.mdat the repo root — GitHub-recognized vulnerability disclosure path (private advisory form, since issues are disabled) + at-a-glance feature table.docs/security.md— comprehensive overview coveringparanoid, default-deny lifecycle scripts, jailed builds, trust policy, minimum release age, exotic-deps blocking, tarball/CAS integrity, auth-token handling,aube audit. Each section links to the relevant setting./securityand accurately list defaults vs opt-ins.settings.tomland regenerated intodocs/settings/index.md.Test plan
cargo test --workspace— 297 + 162 + ~700 more tests pass, including 30+ new trust unit tests (truthiness, exclude parsing, prerelease handling, exclude-bypasses-missing-time, empty-time-map skip, ignore-after cutoff, downgrade detection)Error::TrustDowngradefires +trustPolicyExcludeunblocks, one is the regression test for the corgi-shortcircuit interaction withminimumReleaseAgecargo clippy --workspace --all-targets -- -D warningscleancargo fmt --checkcleanbundled_dependencies,add,global_install,node_gyp_rebuild)Review-comment fixes landed
minimumReleaseAge+trustPolicyprovenanceevidence nowis_truthy-gated to matchtrustedPublisherTrustPolicy::default()aligned with settings.toml defaultis_truthycovers all JS falsy values (0,"",NaN)TrustExcludeRules::parse_lossyreturns(rules, errors)so we don't double-parse with a silent fail-open fallbackSECURITY.mdtable corrected (trustPolicyis a string enum, not a bool)🤖 Generated with Claude Code