01
Pin every GitHub Action to a full commit SHA
Replace mutable tags (uses: org/action@v1) with the full 40-char commit SHA. ratchet is a vendor-neutral OSS CLI that rewrites the YAML for you, locally - no SaaS dependency. GitHub's own security-hardening guide is the canonical reference for the practice. Mutable tags are the root vulnerability TeamPCP exploited against Trivy and KICS - this is the single highest-impact change you can make. Plus per Snyk's analysis: LiteLLM was compromised because its CI/CD pipeline fetched unpinned Trivy from apt - every defender on free-form `latest` is on borrowed time.
Resources
Would have prevented: trivy-main-strike, kics-github-action +3
02
Move maintainer accounts to passkeys (FIDO2/WebAuthn)
Replace SMS/TOTP 2FA with passkeys on every maintainer account on GitHub, npm, and PyPI. Per Okta's threat-intel analysis: TeamPCP's July 2025 PyPI phishing and Sept 2025 npm 2FA-reset phishing both relied on the human-defeatable steps of phishable second factors. Passkeys are bound to a domain and can't be phished. Required for every account that can publish.
Resources
Would have prevented: pypi-phishing-july-2025, npm-maintainer-phishing-sept-2025
03
Default to npm install --ignore-scripts (or pnpm v10 / Bun-as-installer)
Per Snyk SAP CAP analysis: the entire Mini Shai-Hulud worm fires via npm `preinstall` hooks before any code review. Setting `--ignore-scripts` (with an explicit allowlist for packages that legitimately need them) neutralizes that vector globally. pnpm v10 ships secure-by-default lifecycle policies; switching package manager is an option for teams that want this enforced for them. Two additional pnpm settings to enable in the same config: `trustPolicy: no-downgrade` (aborts installs when a package's trust signal regresses vs. a prior version) and `blockExoticSubdeps` (refuses `git+ssh` / `git+https` URLs in transitive dependencies). Placement matters: pass `--ignore-scripts` on the command line (or in your CI script), not via a global `~/.npmrc` - some npm lifecycle operations (e.g. `npm publish`) bypass global config for script execution and may re-enable hooks you thought you'd suppressed.
Resources
Would have prevented: sap-cap-mini-shai-hulud, tanstack-mini-shai-hulud +2
04
Use npm ci (not npm install) in CI pipelines and Dockerfiles
`npm install` re-resolves against your package.json version ranges on every run - if a lockfile is present but out of sync, npm silently updates it and installs the resolved (potentially newer, potentially malicious) version. `npm ci` installs exactly the versions recorded in `package-lock.json` and aborts with an error if the manifest and lockfile disagree, making any dependency drift a visible build failure rather than a silent substitution. Use it in every CI job and every `RUN npm` step in Dockerfiles.
Resources
Would have prevented: antv-wave, sap-cap-mini-shai-hulud +1
05
Pin Docker base images to a SHA digest
Docker tags like `FROM node:20` are mutable - the image behind the tag can be silently replaced, the same way GitHub Action mutable tags enabled the Trivy strike. Replace every `FROM <image>:<tag>` with `FROM <image>@sha256:<digest>`. Renovate and Dependabot both support digest-pinning and will send PRs when new digests are released, so you keep the security without missing updates.
Resources
Would have prevented: trivy-main-strike
06
Block network egress from CI runners during package install
Supply-chain payloads phone home at install time - CanisterWorm C2 traffic, the Miasma GitHub dead-drop, and the Shai-Hulud `api.anthropic.com` masquerade all depend on outbound connectivity during or immediately after `npm install`. Restricting egress to only the registry host (registry.npmjs.org, pypi.org) and your internal mirrors during the install phase kills the callback before it lands, regardless of whether the payload was caught upstream. GitHub Actions can enforce this with job-level network policies; for self-hosted runners and container builds, use firewall rules or a network policy layer.
Resources
Would have prevented: canisterworm, sap-cap-mini-shai-hulud +3
07
Require CODEOWNERS review for dependency manifest changes
Add a `.github/CODEOWNERS` rule requiring sign-off from a named security owner or senior maintainer before any PR can merge that touches `package.json`, `package-lock.json`, `pnpm-lock.yaml`, `requirements.txt`, `pyproject.toml`, `.npmrc`, or any workflow file under `.github/workflows/`. This turns dependency bumps and CI-config changes into a mandatory two-person control without adding friction to non-dependency code.
Resources
Would have prevented: tanstack-mini-shai-hulud, sap-cap-mini-shai-hulud
08
Adopt minimum-release-age windows (refuse versions < 24h old)
Configure your package manager to refuse versions published less than N hours ago. The @antv mass-republish on May 19 lived on npm for ~22 minutes per burst; the Nx Console malicious version was live for 18 minutes. A 24-hour cooldown catches both before they reach production builds. pnpm has had `minimumReleaseAge` for several years. npm added native `min-release-age` in v11.10.0 (Feb 2026) - set it in `.npmrc` as `min-release-age=1` (days) or pass `--min-release-age=1` on the command line; note it is mutually exclusive with `--before`. Also apply the same discipline to your update bots: Renovate's `minimumReleaseAge` config and Dependabot's `cooldown` option enforce a hold period before auto-merging bumps, so a poisoned package can't ride into your codebase on an auto-merged PR.
Resources
Would have prevented: antv-wave, sap-cap-mini-shai-hulud +3
09
Audit every workflow using pull_request_target
Run `grep -r pull_request_target .github/workflows/` in every repo. Remove the trigger if it's not strictly needed; never check out PR code in a pull_request_target workflow. This trigger is the initial-access vector for the Feb 27 Trivy PwnRequest, the May 11 TanStack attack, and (per SANS) the earlier SpotBugs and Nx breaches.
Resources
Would have prevented: trivy-pwn-request, tanstack-mini-shai-hulud
10
Gate OIDC / Trusted Publishing behind a protected branch - not just a workflow
On PyPI, configure Trusted Publishers so packages are publishable only from a specific GitHub Actions workflow on a specific repo; npm has a similar provenance/OIDC model. CRITICAL caveat per Snyk SAP CAP: the @cap-js packages were published via OIDC trust scoped to the entire `cap-js/cds-dbs` repo, so the worm just ran the trusted workflow. The June 1 Miasma / Red Hat compromise sharpened the gap (per BoostSecurity): npm trusted publishing validates org + repo + workflow FILENAME but NOT the git branch - so the attacker pushed a counterfeit ci.yaml to throwaway `oidc-*` branches and minted tokens from them while protected `main` was never touched. The control trusted-publishing config alone can't give you: gate the publish job behind a GitHub Environment with protected-branch rules (mandatory, not optional), and verify the provenance REF (assert GITHUB_REF is a release branch/tag) - not merely that provenance exists. Valid SLSA provenance proves 'came from this repo at this ref', never 'came from a trusted branch'.
Resources
Would have prevented: litellm-pypi, telnyx-pypi +6
11
Require multi-approval PRs and digital commit signing
Require N approvals on every PR merge, and digitally sign every PR + commit. The TanStack PR #7378 (Mini Shai-Hulud initial vector) was a single-approver merge - multi-approval slows the attack and creates a paper trail. gitsign (Sigstore) gives free keyless signing tied to identity. Pair with lockfile-lint in CI to catch attackers who tamper with `package-lock.json` or `pnpm-lock.yaml` on accepted PRs (e.g., redirecting an integrity URL to a malicious host).
Resources
Would have prevented: trivy-pwn-request, tanstack-mini-shai-hulud
12
Run OpenSSF Scorecard against your dependencies
Hosted at deps.dev (Google) and scorecard.dev. Surfaces packages whose maintainers don't have 2FA enabled, whose actions aren't pinned, whose repos don't have branch protection. The cheap defender check is: if a package you depend on scores under 6/10, ask why before the next deploy.
13
Subscribe to CISA KEV + OSV.dev + GitHub Security Advisories
CVE-2026-33634 (Trivy) was added to CISA's KEV catalog with an April 8 federal remediation deadline. If you're not already subscribed to the KEV RSS feed, that's a 60-second fix and gives you the canonical 'rotate now' signal for everyone downstream. OSV.dev aggregates the open-source vulnerability picture across ecosystems; GitHub's advisory database is the same data with a browsable UI.
Resources
Would have prevented: trivy-main-strike, tanstack-mini-shai-hulud
14
Validate lockfile integrity in CI
Run lockfile-lint on every PR + CI run to assert lockfile entries resolve from trusted hosts (registry.npmjs.org), pin to SHA-512 integrity hashes, and use https. Lockfile injection - first disclosed by Liran Tal in 2019 - is where an attacker modifies `package-lock.json` or `pnpm-lock.yaml` to swap a dependency's resolved URL or integrity hash for a malicious one. It is silent at code review and devastating at install time. Free, OSS, drop-in to any GitHub Actions workflow.