Package repository and proxy integrity testing¶
Use this workflow during authorized assessments of CI/CD pipelines, developer workstations, internal package mirrors, and supply-chain bug-bounty programs (registries, proxies, lockfile tooling). It turns the Trail of Bits October 14, 2026 study "Package managers have an integrity problem" (25 package managers tested against a simulated malicious repository) into a reusable operator checklist. Source: Trail of Bits, 2026-10-14.
Why this matters offensively¶
The post's threat model is the one red-teamers actually exploit: a network position, a compromised registry/proxy/mirror, or a rogue internal mirror injecting code into legitimate packages. Fewer than half of the 25 tested package managers verified downloaded contents against the lockfile by default, and only two verified the integrity of every package they downloaded. If you control (or can intercept) the delivery leg, most ecosystems hand you their build artifacts unverified.
Durable targeting facts from the study:
- Verifies contents by default: Go (checksum database + hardcoded key), vcpkg (hashes shipped in the manager source).
- Verifies only if a lockfile with hashes is present and honored: Bun, Bundler, Cargo, npm, pnpm, Poetry, SwiftPM, uv, Yarn.
- Needs lockfile + extra opt-in steps: Conda, Gradle, Pip, NuGet, Pub.
- No effective verification with default flow: CocoaPods, Composer, Conan, cpanminus, Gem, Maven, PowerShellGet, PSResourceGet, R.
- cpanminus uses plaintext HTTP to the Perl repository by default — any on-path network attacker injects code with no TLS to break first. Known for years, unfixed because HTTPS-by-default is "a breaking change."
The "hash exists but doesn't verify" catalog¶
These are the highest-value recon facts: the lockfile looks like an integrity control but the manager doesn't enforce it, so a target team believes they're protected and they are not.
| Ecosystem | Illusion | Reality (per the study) |
|---|---|---|
| Pub (Dart/Flutter) | pubspec.lock carries SHA-256 | On mismatch Pub warns and installs anyway, then rewrites the lockfile; --enforce-lockfile is non-default. |
pnpm < 11.4 (May 2026) | lockfile hashes | On mismatch it overwrote the lockfile with the attacker's hash until 11.4 changed the default. |
| Conan (C/C++) | 128-bit "recipe revision" hash | MD5 used as an identifier, not a check — it is never recomputed after download, and platform package revisions aren't even in the lockfile. |
| CocoaPods | SHA-1 SPEC CHECKSUMS | The checksum covers the Podspec, not the code — a compromised Git host serves a malicious tree at the same tag. |
| Composer (PHP) | composer.lock commit reference | The GitHub zipball is not validated against the commit (shasum empty) — the host can serve any ZIP for a real commit hash. |
| Gradle / Gem | lockfiles exist | gradle.lockfile / Gemfile.lock ship without content hashes. |
| NuGet | signature validation exists | Default signatureValidationMode is accept — invalidly signed packages install until require + trusted signers are configured. |
| Ruby/Java signing | RubyGems/Maven PGP available | Not verified by default; requires gem cert --add / pgpverify-maven-plugin allowlists. |
| Pip / uv / Poetry | PEP 740 attestations exist | No verification path implemented for identity-based attestations. |
Audit-the-auditor trap¶
When a target (or you) reach for an "audit" command to close the gap, check where the audit tool gets its trust material:
npm audit signaturesdoes real Sigstore-backed registry-key validation — but only when run, and never duringnpm install.pnpm audit signaturesfetches the registry public keys from the registry being audited — an adversary who controls the repository simply adds their own key and signature and passes the check. Treat any verifier that pulls trust anchors from the untrusted channel as a null control; this generalizes to any internal "supply chain scanner" you find on a target: ask which channel the scanner's own trust material arrives over.- Go's checksum database is strong (transparency log, key hardcoded in the toolchain) but concentrates trust in one signing key, and transparency only protects against selective targeting if someone watches the log.
Engagement playbook (authorized scope)¶
- Fingerprint the target's package flows. From dev workstations, CI images, and
Dockerfiles: which managers, which lockfiles committed, which mirrors/proxies inPATH/config (internal Artifactory/Nexus/verdaccio,GOPROXY,PIP_INDEX_URL,npm config get registry, Mavensettings.xml, cpanminus defaults). - Classify against the tables above. A committed
Gemfile.lockorgradle.lockfileis reproducibility theater — there is no content hash to defend.composer.lock/Podfile.lockintegrity depends on the honesty of GitHub/Bitbucket serving, not on the lockfile. - Network-position test (lab or explicit scope only). Reproduce the study harness shape: disposable Docker image + sample project + a mitmproxy leg that mutates repository responses/package bodies; success = the sample program executes injected inert canary code. This is a validated design for testing your own pipelines and for registry/proxy bug bounties (Sigstore, registry, and proxy programs).
- Prove the mismatch branch. For Pub/pnpm-class managers, force a hash mismatch and show the client rewrites the lockfile instead of failing — one screenshot closes the finding.
- Flag the HTTP defaults. cpanminus-class cleartext fetches turn any shared network into a code-injection position without breaking TLS at all.
Bug-hunting heuristics¶
- For registry/proxy/Sigstore programs: the reportable classes are (a) verifiers that take keys from the untrusted channel, (b) selective-targeting windows a transparency log would have caught, (c) attestation surfaces that exist but have zero consuming verifiers (PEP 740 today).
- For CI/CD assessments: treat every unpinned or unverified dependency leg behind a mirror you can influence (or a mirror cert the runner trusts) as standing code-execution into the build; pair with the wiki's CI/CD-to-cloud pivot chain once you have runner code execution.
- For library-vs-application nuance: many ecosystems tell libraries not to commit lockfiles — so a vulnerable library's downstream consumers have no hash to check against, silently widening any repository-side attack to every user.
Containment
Malicious-repository simulation belongs in disposable containers against mirrors you own or that are explicitly in scope. Never mutate public registry content, never intercept third-party developer traffic, and keep injected payloads inert (print-only canaries).