Supply Chain Incident Response
Threat Model
Scanner-clean post-advisory does not mean clean during the exposure window. The 4-point check (lockfile, CI cache, secrets, persistence) is the irreducible work to determine what the bad version actually touched.
Triage Flow
Run in order. Each step has a verifying command and a concrete output. Do not advance until the previous step's output is captured.
Step 1 — Parse the advisory
# If GHSA: fetch and parse
gh api /advisories/<id> --jq '{summary, affected: .vulnerabilities | map({pkg: .package.name, range: .vulnerable_version_range})}'
# If CVE: lookup on GitHub
gh api search/issues --raw-field q="<cve-id>" --jq '.items[].html_url' | head -5
Output: affected package names, version ranges, and exposure window (publish time of bad version → yank time, or now if unyanked).
Step 2 — Determine when this project resolved the bad version
# Show lockfile history for the affected package
git log --oneline --follow -p -- <lockfile> | grep -B 2 -E "<affected-package>"
Output: commits and timestamps showing when this project pinned the bad version. Cross-reference against the exposure window from Step 1 to confirm overlap. If no overlap → PASS, stop here, document.
Scope determination (which lockfiles even mention the package) is the Lockfile item in the 4-Point Check below — go there next.
The 4-Point Check
For each item: run the command, record PASS / EXPOSED / UNKNOWN with evidence for the incident report.
- Lockfile — did any of our lockfiles resolve to the affected version?
# Search every ecosystem's lockfile + manifest in one pass for pkg in <pkg1> <pkg2>; do git grep -nE "\"$pkg\"|^$pkg\\s|^$pkg==" -- '**/lock*' 'pyproject.toml' 'package.json' 'pixi.toml' 'Cargo.toml' 'go.mod' done - CI cache — did any workflow cache contain a build that used the affected version during the exposure window?
gh actions cache list --sort created --order desc --json key,createdAt | jq '.[] | select(.createdAt > "<exposure-start>")' - Secrets — what secrets were in scope when the bad version executed?
gh secret list && grep -rE 'env:|secrets\.' .github/workflows/ - Persistence — did the bad version leave any artifact that survives?
# Python .pth find $(python -c "import site; print(site.getsitepackages()[0])" 2>/dev/null) -name "*.pth" 2>/dev/null # npm bin/ find node_modules/.bin -type f -newer <date>
Every result feeds the incident report's 4-Point Check Results table.
Phases
Containment
Pin a known-good version in the lockfile. Invalidate every CI cache built during the exposure window (gh actions cache delete <key>). Disable any workflow that ran the affected version until eradication is complete (gh workflow disable <file>).
Done when: every lockfile pin is updated and every cached artifact from the window is purged.
Eradication
Rotate every secret enumerated in the 4-point check using references/secret-rotation.md. Hunt persistence artifacts using the appropriate runbook: references/runbook-account-takeover.md for npm/PyPI maintainer takeover, references/runbook-tag-hijack.md for GitHub Action tag hijack.
Done when: every in-scope secret has a new value AND every persistence artifact is removed.
Recovery
Rebuild the project from the clean lockfile with no caches (gh workflow run <release> --field skip-cache=true). Verify the rebuilt artifacts' provenance using the supply-chain-sbom-provenance skill if SBOM/attestations are configured.
Done when: rebuilt artifact matches expected provenance and tests pass.
Post-incident
Fill assets/incident-report-template.md. Update posture using the supply-chain-threat-awareness skill. File a GHSA at https://github.com/<owner>/<repo>/security/advisories/new if this project itself shipped an affected version.
Done when: incident report is complete and prevention items are linked to skills/commands.
Choose Your Runbook
- Maintainer account takeover (Shai-Hulud-style npm/PyPI compromise) →
references/runbook-account-takeover.md - Release tag hijacking (TeamPCP-style GitHub Action mutation) →
references/runbook-tag-hijack.md - Secret rotation (every incident, regardless of vector) →
references/secret-rotation.md
Output: Incident Report
Produce a filled copy of assets/incident-report-template.md. Every section must be populated — leave none as placeholder. Each 4-point check item gets PASS / EXPOSED / UNKNOWN with evidence (file:line, run URL, cache key, or "absent") and a follow-up action. Every Prevention Item must link to a skill or /command in this plugin.