Repository navigation
feat(supply-chain): verify lockfile hashes against upstream registries - #2867
Conversation
Adds scripts/check_lockfile_integrity.py + 52 tests covering npm package-lock.json, Cargo.lock, and pip requirements --hash pins. For every added or changed lockfile entry, fetches upstream registry metadata over HTTPS (npm, crates.io sparse index, PyPI JSON) and compares the cryptographic hash. Defends against lockfile poisoning attacks where the version is unchanged but the bytes have been swapped. Extends the existing supply-chain-check workflow with a lockfile-integrity job; same on:pull_request trigger and minimal contents:read permissions. DoS cap (--max-deps 2000) and tight SSRF / log-injection guards on all registry IO. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
PR Review Summary
Verdict: AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims. |
…script cspell flagged Python stdlib identifiers (optparse, fromhex, getsize, getsockname, DEVNULL, splitlines), domain terms (ssri, cksum, NOREDIRECT, userinfo, alphanum, metachar, basenames), and pytest fixture names (rfoo, evilpkg, chex, phex, rrequirements, cconstraints, Gpattern, etc.) introduced by scripts/check_lockfile_integrity.py and its tests. These are correct identifiers, not misspellings; appending to the existing .cspell-repo-terms.txt allow-list rather than the inline words array since that is the established pattern for technical vocabulary in this repo. Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
…le-integrity Signed-off-by: Jack Batzner <jackbatzner@microsoft.com> # Conflicts: # .cspell-repo-terms.txt
|
The CI failure is generated-file drift: |
|
Thanks Imran Siddique (@imran-siddique) — your diagnosis was right. The Verified locally: Current CI on |
Imran Siddique (imran-siddique)
left a comment
There was a problem hiding this comment.
Lockfile integrity check: comprehensive threat model, 5 rounds of adversarial self-review, 250 tests. Generated-workflow drift was fixed in the latest commit. Approving.
6ef3a9b
into
microsoft:main
Description
Adds a new CI check that verifies every added/changed entry in a lockfile has an integrity hash matching what the upstream registry actually publishes today. This catches lockfile-poisoning attacks where an attacker pins a known-good version number but swaps the bytes — a different attack class from G1 / proposal #1 (install-script bypass), which is about new entries with hostile lifecycle scripts rather than tampered hashes on otherwise-known entries.
Supports
package-lock.json(npm v7+packagesdict, SRI hashes),Cargo.lock(crates.io sparse index, hexcksum), andrequirements*.txtwith--hash=sha256:pins. Out of scope (noted as future work):yarn.lock,pnpm-lock.yaml, NuGetpackages.lock.json.Pure stdlib (
urllib,json,hashlib,base64,tomllib). No package installation, no shell-out beyondgit show/git diff --name-only/git ls-files. Local-only run with--paths …is supported for offline testing; in CI it diffs lockfiles against the PR base ref viagit show <base>:<path>, the same patternscripts/check_install_scripts.pyalready uses.Type of Change
Package(s) Affected
.github/workflows/supply-chain-check.ymlextended with one new job; new files underscripts/Checklist
ruff check --select E,F,W --ignore E501clean on both new files)scripts/tests/test_check_lockfile_integrity.py, 250 scripts-wide all green)pytest scripts/tests/ -x -q→ 250 passed)Attribution & Prior Art
Prior art / related projects:
ssrisemantics for SRI parsing (W3C SRI spec, MIT) — no code copied, design referenced inline.https://index.crates.io/<aa>/<bb>/<name>) — public crates.io spec; URL construction follows the documented bucket scheme.scripts/check_install_scripts.pyin this repo.AI Assistance
GitHub Copilot CLI assisted with parser scaffolding and an iterative red-team review loop (see "Adversarial review history" below). All hash logic, registry interactions, threat-model decisions, and final code were reviewed line-by-line.
IP, Patents, and Licensing
Related Issues
Part of the supply-chain hardening series (proposals #1–6); this is proposal #5 of 6. Other proposals are being implemented in parallel and do not overlap with this scope.
What runs in CI
New job in
.github/workflows/supply-chain-check.yml:on: pull_requestcontents: read(top-level), no per-job writes${{ }}script interpolationregistry.npmjs.org,index.crates.io,pypi.org. No redirects followed.--max-deps 2000(DoS exit 2); 5 MB per registry response; 16 MB per lockfile; 64 MB pergitstdout; 15 s request timeoutSample PR experiences
Clean PR (no lockfile churn):
lockfile-integrity ✓ — checked 0, okLegitimate
axios1.7.0 → 1.7.9 bump:lockfile-integrity ✓ — checked 1, okTampered hash on otherwise-known version:
Threat model summary
sha512-EVIL sha512-LEGIT)npm-suspicioussentinel; verify always errors-iURLformpip-suspicioussentinel covers all three forms_NoRedirectopener refuses any 3xxhttpsonly--upload-pack=…,@{u},.., leading-/.)^[A-Za-z0-9_/][A-Za-z0-9_./-]*$+..reject + length cap_safe()strips control chars + tight name regex per ecosystemgitblob in historyPopen+ 64 MB stdout cap; overflow kills subprocess--max-deps 2000with exit 2 on overflowtomllibAdversarial review history
This script went through five rounds of self-attack before being opened for review. Each round found real defects:
pip-suspicioussentinel), urllib redirect SSRF (_NoRedirect), weak ref regex-/./..in refs-iURL,-rfoo,-efoo,-ffoo,-cfoo)fh.read()lockfile DoS, unboundedsubprocess.rungit stdout DoSnode_modules/log injection, pip--hashwhitespace tricks all already mitigatedEvery finding above has a regression test in
scripts/tests/test_check_lockfile_integrity.py. A fresh maintainer eye is still worthwhile — self-attack reaches diminishing returns.