Software Supply Chain Security Guide

A defender’s reference for software supply chain risks — threat model across the SDLC, package-registry attack patterns, CI/CD hardening, artifact provenance and signing, SBOMs, dependency scanning, case studies, and a checklist. Compiled from 54 research articles, advisories, and incident writeups in raw/Supply Chain/.


Table of Contents

  1. Fundamentals
  2. Threat Model Across the SDLC
  3. Package Registry Risks
  4. Dependency Confusion, Typosquatting, Slopsquatting
  5. Maintainer Account Compromise
  6. CI/CD Pipeline Hardening
  7. Container Image Provenance & Verification
  8. SLSA Framework
  9. Sigstore, Cosign, in-toto
  10. SBOMs (SPDX, CycloneDX)
  11. Dependency Scanning Tooling
  12. Developer Host Hardening
  13. Admission Control & Runtime Verification
  14. Case Studies — Defensive Lessons
  15. Detection Signals & IOCs
  16. Defender Checklist
  17. Reference Configurations

1. Fundamentals

A software supply chain attack compromises a dependency, tool, build system, or distribution channel that the target trusts, rather than attacking the target directly. The malicious payload rides in on a routine npm install, pip install, docker pull, or CI build — bypassing perimeter defenses because the artifact appears legitimate.

Why the category exploded:

FactorEffect
Modular package ecosystemsOne compromised transitive dep can reach thousands of downstream apps
~85% of enterprises use OSSHuge shared attack surface
Registries default to “latest”Window between publish and detection is the blast radius (Chalk attack: 16 min from account access to 18 packages weaponized; 2 hrs live before removal, yet reached 10% of cloud environments)
Lifecycle scripts (postinstall, prepare)Arbitrary code executes during install, before any runtime control
AI-suggested dependencies~20% of LLM-suggested packages do not exist — creates slopsquatting openings
CI/CD secrets co-located with buildsStealing them grants further publishing rights → worm behavior

OWASP Top 10 2025 — A03 Software Supply Chain Failures:

MetricValue
Community ranking#1 (50% of respondents)
Avg incidence rate5.19% (highest in Top 10)
Mapped CWEs477, 1035, 1104, 1329, 1357, 1395

Growth: Supply chain attacks on package registries surged 73% in 2025–2026. Sonatype tracked a 742% increase between 2019 and 2022. OSSF and Bastion 2026 reports attribute the spike to nation-state actors (notably DPRK clusters publishing ~1,700 malicious packages across npm, PyPI, Go, and Rust), credential-stealing worms, and industrialized typosquatting.


2. Threat Model Across the SDLC

Map threats to lifecycle stages. Each stage has distinct actors, assets, and controls.

StageAssetsRepresentative ThreatsPrimary Controls
DevelopSource code, IDEs, dev workstations, SSH/GPG keysStolen creds, IDE extensions (Glassworm, OpenVSX), malicious packages on dev host, AI slopsquattingMFA, commit signing, dev-host hardening, release-age gates, disabled install scripts
SourceGit repo, branches, PRs, code reviewForce-pushed tags, compromised reviewer, self-merge, secret commitBranch protection, required reviews, signed commits/tags, CODEOWNERS, secret scanning
BuildCI runners, build tools, cache, ephemeral credsPoisoned runner, compromised action/plugin, cache poisoning, post-build provenance forgeryEphemeral isolated runners, pinned action SHAs, SLSA L3 hosted builds, OIDC federation, egress allowlist
PackageArtifacts, signatures, SBOMs, attestationsUnsigned release, forged provenance, hidden transitive dep injectioncosign sign, in-toto attestations, SLSA provenance, SBOM attached
DistributeRegistries (npm/PyPI/GHCR/Docker Hub), mirrors, CDNAccount takeover, typosquat, dep confusion, registry abuse, force-push image tagScoped tokens, 2FA, publisher policies, repository firewall, signed pulls
DeployKubernetes, cloud, admission policiesUnsigned image rollout, drift, sidecar injectionAdmission controllers (Kyverno/OPA/Gatekeeper), signature verification, image allowlists
RuntimeRunning containers, nodes, secrets storesBackdoor C2, token exfil via metadata, lateral movementeBPF runtime, egress filtering, metadata service IMDSv2, least-priv IAM

STRIDE per stage: Every stage maps to Spoofing (forged provenance), Tampering (injected code), Repudiation (missing attestations), Information disclosure (leaked secrets via install scripts), DoS (dependency deletion à la left-pad), and Elevation (signing-key theft).

Blast-radius formula:

blast_radius = trust_score(package) × downstream_installs × window_before_detection × privilege(install_context)

Reduce any factor. Release-age gates attack window_before_detection; ignore-scripts attacks privilege(install_context); pinning attacks trust_score drift.


3. Package Registry Risks

Registry-by-registry quick reference

RegistryLanguageNotable attack patterns (2025–2026)Key defender features
npmJavaScript/TypeScriptChalk/Debug, Shai-Hulud/Shai-Hulud 2.0, Axios, Nx/s1ngularity, CanisterWorm, Glasswormmin-release-age, ignore-scripts, 2FA, scoped publish tokens, trusted publishers via OIDC
PyPIPythonLiteLLM, Telnyx, Ultralytics (GH Actions script injection → coinminer), DPRK ghost-package swarms, wallet stealers2FA mandatory for top projects, Trusted Publishers (OIDC), --require-hashes, revoke unused API tokens alongside Trusted Publishers
Maven CentralJavaTyposquats of groupIds, unsigned POMsGPG signing mandatory, namespace verification
RubyGemsRubyStrong_password-style hijacksMFA, bundle config set --global frozen true
Go proxyGoTyposquat import paths, DPRK-seeded modulesModule proxy immutability, go.sum verification, GOSUMDB
crates.ioRustTyposquats, DPRK-seeded cratesLockfile enforcement, cargo vet, cargo-audit
Docker Hub / GHCRContainersHijacked image tags, base-image tampering, Trivy v0.69.4 force-pushSigned images, content trust, immutable digests
VS Code Marketplace / OpenVSXIDE extensionsGlassworm, TeamPCP OpenVSXExtension signing (preview), allowlists
GitHub ActionsCI actionsTag repoint, compromised action → secret dump, tj-actions/changed-files + reviewdog cascading compromise (CVE-2025-30066/CVE-2025-30154), SpotBugs → reviewdog → tj-actions chain, log-based exfilPin by SHA, permissions: read-all, OIDC, allowlisted actions, audit contributor team privileges, avoid pull_request_target with unsanitized inputs

Attack pattern taxonomy

PatternDescriptionDefender signal
Direct malware uploadNet-new package with malicious payloadBehavioral scanning (Socket.dev), new-publisher heuristics
TyposquatName differs by 1–2 chars from popular packageLevenshtein monitoring, registry deny-list
Dependency confusionPublic package with same name as private internalNamespace private scopes (@org/*), lockfile + private-registry priority
Transitive injectionLegitimate package quietly pulls new malicious depLockfile diffs, SBOM diff alerts
Account takeoverPhishing maintainer → legit publishPublisher-change alerts, 2FA enforcement
Worm / self-propagationPayload steals tokens → republishesAnomalous publish events, new runner names, post-install egress
Force-push tag repointGit tag moved to malicious commitSigned tag verification, tag immutability
Install-time executionpostinstall / setup.py runs payload on installignore-scripts, sandboxed installs, CI egress allowlist
GH Actions script injectionUnsanitized pull_request_target input (e.g., PR title) → arbitrary code in buildzizmor workflow linter, avoid pull_request_target, sanitize all user-controlled inputs in workflow expressions
Cascading action compromiseCompromise action A → steal creds → compromise action B that depends on APin all composite action deps by SHA, audit transitive action dependencies
Log-based exfiltrationSecrets dumped to CI workflow logs instead of external C2Audit log scrubbing, restrict public repo workflow log visibility
Dead man’s switch / destructive fallbackWorm deletes user home directory if exfil + propagation failEDR alerting on rm -rf ~, filesystem monitoring
AI-tool exploitationMalware invokes local AI CLI tools (Claude, Gemini, Q) with --dangerously-skip-permissions to enumerate secretsReview AI tool permissions, disable --yolo flags
Protestware / maintainer sabotageMaintainer introduces destructive logicBehavioral scanning, diff review of minor versions
SlopsquattingLLM hallucinates package name → attacker registers itVerify AI-suggested packages before install

4. Dependency Confusion, Typosquatting, Slopsquatting

Dependency confusion (Birsan 2021)

Occurs when a package manager prefers public registries over private ones for a name used both internally and publicly.

Root causeFix
Package manager falls back to public when private lookup fails or private registry is unreachableConfigure scoped registries; hard-fail if internal package not found in private index
Internal package names leaked in package.json / requirements.txtUse @org/name scopes on npm; prefix on PyPI; private Maven groupIds
Higher version on public winsPre-register internal names on public registry as “security holder” stubs

npm mitigation:

# .npmrc
@myorg:registry=https://nexus.myorg.local/repository/npm-private/
registry=https://registry.npmjs.org/
always-auth=true

pip / uv mitigation:

# pip.conf — single index, no extra-index-url fallback
[global]
index-url = https://artifactory.myorg.local/artifactory/api/pypi/pypi/simple/

Avoid --extra-index-url — it merges indices and picks highest version. Use --index-url to a repository-manager virtual registry that proxies public content behind policy.

Typosquatting

Detection leverTool / technique
Name similarity to top-1000 packagesSocket.dev, Phylum, Sonatype Repository Firewall
New package + suspicious postinstallAikido, GitGuardian
Namespace deny-list in repo managerNexus / Artifactory policies
Lockfile diff alertsGitHub Actions: compare package-lock.json PR diffs

Slopsquatting (AI-coined)

LLMs regularly hallucinate plausible-sounding package names. Attackers squat those names. Defensive verification before installing any AI-suggested package:

# npm
npm view <pkg> time.created versions maintainers --json

# PyPI
curl -s https://pypi.org/pypi/<pkg>/json | jq '{name:.info.name,author:.info.author,home_page:.info.home_page,releases:(.releases|keys|length)}'

Red flags: created <30 days ago, single maintainer, no linked repo, download count in tens, no release history.


5. Maintainer Account Compromise

Phishing an npm/PyPI maintainer became the single most prolific entry vector in 2025–2026 (Chalk, Debug, Axios, LiteLLM, Telnyx, Shai-Hulud, Shai-Hulud 2.0).

PhaseAttacker actionDefender control
ReconIdentify maintainers of high-traffic packagesPublic — accept
PhishingLookalike npm/PyPI login page, OAuth consent phishingHardware-backed 2FA (WebAuthn), phishing-resistant MFA, allowlisted OAuth apps
PublishUse legitimate account to push malicious versionTrusted Publishers (OIDC from CI only), publish-alerts, velocity anomaly detection
PropagateStolen tokens from infected victims used to publish new packages (worm)Short-lived tokens, token scoping per package, detect new runner names (e.g. SHA1HULUD)

Key principle: a legitimate publisher account with a valid 2FA bypass is indistinguishable from the real maintainer at the artifact level. The only durable defense is post-publish attestation verification (SLSA provenance + Sigstore identity) tied to a CI workflow identity rather than a human account.


6. CI/CD Pipeline Hardening

CI/CD is high-value: it holds secrets, has network egress, and signs artifacts. Compromising it turns it into a factory for malicious releases.

Control baseline

ControlRationale
Ephemeral runnersNo state between builds; no cache contamination
Isolated builds (SLSA L3)User build steps cannot touch signing material
OIDC federation (no long-lived cloud creds)Revokes secret exfil value; token valid minutes
Pin GitHub Actions by SHAuses: actions/checkout@b4ffde65... not @v4; defeats tag repoint
Minimal permissions:Default contents: read; grant id-token: write only when signing
Read-only filesystemLimits payload persistence
Egress allowlist on runnersBlocks curl-to-C2, anomalous registries
Secret masking + short-lived tokensShrinks exfil window
Separation of dutiesNo single human pushes code → prod
Signed commits/tags enforced by branch protectionAttacker cannot force-push malicious tag without signing key
Runner fingerprintingDetect rogue self-hosted runners (e.g., SHA1HULUD)
Audit log export + alertingDetect anomalous workflow_dispatch, new deploy keys
Restrict pull_request_targetNever pass unsanitized PR titles/branch names into run: expressions; use zizmor to lint workflows (Ultralytics root cause)
Audit composite action dependenciesComposite actions inherit transitive deps — a compromised reviewdog/action-setup infected tj-actions/eslint-changed-files which infected tj-actions/changed-files
Contributor team privilege reviewAutomated invitations to maintainer teams enabled the reviewdog compromise; review team membership and write access regularly
Tag immutability enforcementThe tj-actions attacker re-pointed all version tags (v1–v44) to a single malicious commit; consider GitHub tag protection rules
Workflow log access controlLog-based exfiltration worked because public repos expose workflow logs; restrict log retention and visibility

GitHub Actions hardened job skeleton

name: build-and-release
on:
  push:
    tags: ['v*']

permissions:
  contents: read
  id-token: write      # for keyless cosign + OIDC to cloud
  packages: write      # scoped; only if publishing to GHCR

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1 pinned SHA
        with:
          persist-credentials: false

      - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8  # v4.0.2
        with:
          node-version: 20
          cache: 'npm'

      - run: npm ci --ignore-scripts

      - run: npm run build

      - name: Generate SBOM
        uses: anchore/sbom-action@d94f46e13c6c62f59525ac9a1e147a99dc0b9bf5
        with:
          format: cyclonedx-json
          output-file: sbom.cdx.json

      - name: Scan SBOM
        uses: anchore/scan-action@be7a22da4f22dde4c7c9e0b14dea0d0f7a4a16a3
        with:
          sbom: sbom.cdx.json
          fail-build: true
          severity-cutoff: high

      - name: Build & push image
        id: build
        run: |
          IMAGE=ghcr.io/${​{ github.repository }}:${​{ github.ref_name }}
          docker build -t "$IMAGE" .
          docker push "$IMAGE"
          echo "digest=$(docker inspect --format='{​{index .RepoDigests 0}}' $IMAGE)" >> "$GITHUB_OUTPUT"

      - name: Sign image (keyless)
        env:
          COSIGN_EXPERIMENTAL: "1"
        run: cosign sign --yes ${​{ steps.build.outputs.digest }}

      - name: Attest SBOM
        run: |
          cosign attest --yes --predicate sbom.cdx.json \
            --type cyclonedx ${​{ steps.build.outputs.digest }}

      - name: Attest SLSA provenance
        uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
        with:
          image: ${​{ steps.build.outputs.digest }}

Runner egress allowlist (example)

registry.npmjs.org
pypi.org
files.pythonhosted.org
ghcr.io
*.actions.githubusercontent.com
fulcio.sigstore.dev
rekor.sigstore.dev
oauth2.sigstore.dev

Block everything else. Most supply-chain malware phones home on install — an allowlist surfaces it immediately.


7. Container Image Provenance & Verification

Container images are the delivery unit and must be identified by digest, not tag.

BadGood
FROM node:20FROM node:20.11.1-alpine3.19@sha256:7c3...
docker pull myimage:latestdocker pull myimage@sha256:…
Tag-based Kubernetes rolloutDigest-pinned manifests + admission verification

Base image hygiene

PracticeWhy
Prefer distroless / chainguard / wolfi base imagesSmaller attack surface, signed by producer
Rebuild on base CVE, not periodicallyProvenance ties to cause
Pin base by digest in FROMDefeats silent tag repoint
Scan final image with Grype/TrivyCatches inherited CVEs
Verify base image signature in CIcosign verify cgr.dev/chainguard/static@sha256:…

cosign image sign + verify

# Keyless sign (uses OIDC → Fulcio → Rekor)
COSIGN_EXPERIMENTAL=1 cosign sign --yes \
  ghcr.io/myorg/app@sha256:abcd...

# Verify against expected CI workflow identity
cosign verify \
  --certificate-identity-regexp="^https://github.com/myorg/app/\.github/workflows/release\.yml@refs/tags/v.*$" \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  ghcr.io/myorg/app@sha256:abcd...

The identity regex is the critical verification step — it binds the signature to a specific workflow file in a specific repo, so a compromise of a different repo cannot produce a valid signature for this image.


8. SLSA Framework

SLSA (Supply-chain Levels for Software Artifacts, “salsa”) gives a vocabulary and progressive maturity levels for build integrity. Developed originally from Google’s Binary Authorization for Borg, donated to OpenSSF in 2021, now at v1.1.

Build track levels

LevelNameRequirementsProtects against
L0No SLSANothingNothing
L1Provenance existsScripted build, auto-generated provenanceAccidental wrong artifact; incident traceability
L2Signed provenanceL1 + cryptographically signed by hosted build platformPost-build forgery; builds from dev workstations
L3Hardened buildsL2 + isolated, ephemeral runners; signing keys inaccessible to build user code; non-forgeable provenanceCross-build contamination, insider tampering, signing-key theft by build step

Note: SLSA v1.0+ consolidated former L4 — hermetic and reproducible builds are now recommended practices, not strict requirements.

Provenance attack vectors addressed

VectorWithout SLSAWith SLSA L3
Source tamperingHidden in commit; hard to detectProvenance binds artifact to commit digest
Build tamperingUndetectable mid-buildHardened runner isolates build from signer
Dependency poisoningInstalled silentlyProvenance lists dependencies used
Provenance forgeryTrivialNon-forgeable; signed by platform, not user
Compromised credentialsUsed to publishOIDC-scoped short-lived, verifiable

Minimal in-toto / SLSA provenance document

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [{
    "name": "ghcr.io/myorg/app",
    "digest": {"sha256": "abcd1234..."}
  }],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://github.com/slsa-framework/slsa-github-generator/container@v1",
      "externalParameters": {
        "repository": "https://github.com/myorg/app",
        "ref": "refs/tags/v1.2.3"
      },
      "resolvedDependencies": [
        {"uri": "git+https://github.com/myorg/app", "digest": {"gitCommit": "f00..."}}
      ]
    },
    "runDetails": {
      "builder": {
        "id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.0.0"
      },
      "metadata": {
        "invocationId": "https://github.com/myorg/app/actions/runs/1234567890/attempts/1",
        "startedOn": "2026-04-01T12:00:00Z"
      }
    }
  }
}

SLSA 3 for Go modules (GitHub Actions + Sigstore)

GitHub provides a reusable workflow (slsa-framework/slsa-github-generator-go) that automates SLSA L3 for Go projects. The workflow uses the Actions OIDC token to bind provenance to the exact repository, branch, commit, and workflow file. Cosign + Fulcio issue a short-lived certificate from the OIDC identity, and Rekor logs the signing event. No long-lived signing keys are needed. This pattern is extensible to other ecosystems via the generic slsa-github-generator.

Consumer verification workflow

  1. Download artifact + provenance bundle.
  2. Verify Sigstore signature against certificate transparency log (Rekor).
  3. Check builder.id matches allowlisted trusted builder.
  4. Match artifact digest against subject.digest.
  5. Match externalParameters.repository and ref against expected.
  6. Policy engine (Kyverno / Conftest / OPA) approves or rejects.

9. Sigstore, Cosign, in-toto

Component map

ComponentRole
cosignCLI for signing/verifying container images, blobs, SBOMs, attestations
FulcioShort-lived code signing CA; issues certs from OIDC identity
RekorImmutable transparency log of signing events
policy-controller / cosign verifyEnforcement at admission or deploy time
in-toto attestationsStatement format (subject, predicateType, predicate)
SLSA provenanceA predicateType inside in-toto that describes build facts

Keyless signing flow

dev/CI → OIDC (GitHub, Google, etc.) → Fulcio (issues 10-min cert bound to identity)
      → cosign signs artifact with ephemeral key
      → signature + cert logged in Rekor transparency log
      → verifiers check cert identity + Rekor inclusion proof

Benefit: no long-lived signing keys to manage or exfil.

Common cosign operations

# Keyless sign a blob
cosign sign-blob --yes --bundle release.cosign.bundle release.tar.gz

# Verify blob using expected identity
cosign verify-blob \
  --bundle release.cosign.bundle \
  --certificate-identity-regexp="^https://github.com/myorg/app/.*$" \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  release.tar.gz

# Attach an SBOM attestation
cosign attest --yes --predicate sbom.cdx.json --type cyclonedx \
  ghcr.io/myorg/app@sha256:abcd...

# Download + inspect attestation
cosign download attestation ghcr.io/myorg/app@sha256:abcd... | \
  jq -r '.payload' | base64 -d | jq .

# Verify SLSA provenance predicate
cosign verify-attestation --type slsaprovenance \
  --certificate-identity-regexp="^https://github.com/slsa-framework/.*$" \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  ghcr.io/myorg/app@sha256:abcd...

cosign verification of ecosystem provenance bundles

Since cosign v2.4.0, you can verify attestations in the bundle format used by npm provenance, GitHub Artifact Attestations, and Homebrew provenance. Over 16,000 npm packages publish with provenance.

# Verify npm provenance (e.g., semver@7.6.3)
curl https://registry.npmjs.org/semver/-/semver-7.6.3.tgz > semver-7.6.3.tgz
curl https://registry.npmjs.org/-/npm/v1/attestations/semver@7.6.3 | \
  jq '.attestations[]|select(.predicateType=="https://slsa.dev/provenance/v1").bundle' > npm-provenance.sigstore.json
cosign verify-blob-attestation --bundle npm-provenance.sigstore.json --new-bundle-format \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  --certificate-identity-regexp="^https://github.com/npm/node-semver/.github/workflows/release-integration.yml.?" \
  semver-7.6.3.tgz

# Verify GitHub Artifact Attestation
gh attestation verify gh_2.54.0_linux_armv6.tar.gz --owner cli

The Ultralytics incident demonstrated the value: PyPI staff used Sigstore transparency logs and publish attestations to determine the first malicious releases came through the legitimate GitHub Actions workflow (build-phase injection), while the second round had no attestations at all (stolen API token). Once tooling enforces attestation presence at install time, tokenless publishes become detectable.

Key-based signing (fallback for air-gapped builds)

cosign generate-key-pair                        # cosign.key / cosign.pub
cosign sign --key cosign.key ghcr.io/app:1.0.0
cosign verify --key cosign.pub ghcr.io/app:1.0.0

Store private keys in HSM or KMS (--key awskms://…, --key gcpkms://…); never embed in CI secrets if OIDC is available.


10. SBOMs (SPDX, CycloneDX)

An SBOM is a machine-readable inventory of components, versions, licenses, and relationships in an artifact. It answers “is package X at version Y present?” in seconds when the next Log4Shell drops.

FormatBodyTypical use
SPDXLinux FoundationLicense compliance, regulatory (NTIA, EO 14028)
CycloneDXOWASPAppSec, vuln scanning, VEX, SaaSBOM, ML-BOM

CISA 2025 Minimum Elements update

CISA updated its SBOM guidance (originally NTIA 2021) to reflect current maturity. Key additions: machine-processable formats are now required (not just recommended), SBOMs must integrate into broader cybersecurity practices (not standalone compliance artifacts), and the guidance emphasizes scalable implementation across both public and private sectors. Organizations should align SBOM programs to this updated baseline.

Minimum viable SBOM practice

  1. Generate at build time (not post-hoc from a registry).
  2. Attach to the artifact (cosign attest --type cyclonedx).
  3. Sign the SBOM.
  4. Store versioned alongside the release.
  5. Feed to a vuln scanner on every pull request and on new CVE disclosure.
  6. Diff SBOMs between releases to surface new transitive deps.

Generation commands

# Syft — filesystem
syft dir:. -o cyclonedx-json=sbom.cdx.json -o spdx-json=sbom.spdx.json

# Syft — container image
syft ghcr.io/myorg/app@sha256:abcd... -o cyclonedx-json=sbom.cdx.json

# cdxgen (language-native)
cdxgen -r -t javascript -o sbom.cdx.json

# Trivy
trivy image --format cyclonedx --output sbom.cdx.json ghcr.io/myorg/app@sha256:abcd...

# Docker buildx native
docker buildx build --sbom=true --provenance=mode=max -t myimage .

SBOM vulnerability scan

# Grype against the SBOM (consistent regardless of runtime env)
grype sbom:sbom.cdx.json --fail-on high

# OSV-Scanner against the SBOM
osv-scanner --sbom sbom.cdx.json

# Dependency-Track upload
curl -X POST "https://dtrack.myorg.local/api/v1/bom" \
  -H "X-API-Key: $DTRACK_TOKEN" \
  -F "autoCreate=true" \
  -F "projectName=myorg/app" \
  -F "projectVersion=1.2.3" \
  -F "bom=@sbom.cdx.json"

SBOM diff for transitive-dep injection detection

# Naive: detect new direct or transitive deps between tags
diff <(jq -r '.components[] | "\(.name)@\(.version)"' old.cdx.json | sort) \
     <(jq -r '.components[] | "\(.name)@\(.version)"' new.cdx.json | sort)

Run in PR CI — alert a human on any new dependency added by a minor/patch version bump of an existing dependency. This is the signal that would have caught the Axios → plain-crypto-js injection.


11. Dependency Scanning Tooling

ToolKindStrengthLimitation
DependabotAuto-PR bumps + advisoriesFree, GitHub-native, alertsOnly known CVEs; noisy PRs
RenovateAuto-PR bumpsHighly configurable grouping, schedules, merge confidenceConfig complexity
SnykSCA + IaC + containerGood proprietary DB + license checksCommercial
OSV-ScannerGoogle OSV DBOpen DB, fast, works on SBOM, lockfiles, directoriesCVE-only (but catches GHSA, PYSEC, etc.)
TrivyImage + SBOM + IaC + secretsOne binary does a lotNoisy defaults
GrypeSBOM-driven scannerWorks with Syft output, SPDX and CycloneDXCVE-only
Dependency-TrackContinuous SBOM mgmtAggregates SBOMs, policy, VEXSelf-hosted
Socket.devBehavioral (not CVE)Detects install scripts, obfuscation, network accessCommercial for private
PhylumBehavioral + reputationPre-install blockingCommercial
Sonatype Repository Firewall / Nexus IQRegistry proxy w/ quarantineBlocks known-malicious before cacheCommercial

OSV-Scanner CI usage

- name: OSV scan
  uses: google/osv-scanner-action/osv-scanner-action@v1.9.0
  with:
    scan-args: |-
      --lockfile=package-lock.json
      --lockfile=poetry.lock
      --recursive
      --fail-on-vuln

Dependabot minimal config

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule: { interval: "daily" }
    open-pull-requests-limit: 10
    groups:
      production:
        dependency-type: "production"
      development:
        dependency-type: "development"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule: { interval: "weekly" }
  - package-ecosystem: "docker"
    directory: "/"
    schedule: { interval: "weekly" }

Renovate minimal config

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended", ":pinAllExceptPeerDependencies"],
  "minimumReleaseAge": "7 days",
  "pinDigests": true,
  "packageRules": [
    {"matchManagers": ["github-actions"], "pinDigests": true},
    {"matchUpdateTypes": ["patch"], "automerge": true, "automergeType": "pr"}
  ],
  "vulnerabilityAlerts": {"enabled": true, "labels": ["security"]}
}

minimumReleaseAge: "7 days" is the single most effective Renovate setting: it refuses to auto-bump into a version published less than 7 days ago, which is the window most malicious releases live before takedown.


12. Developer Host Hardening

Developer workstations and personal laptops are now prime targets (Shai-Hulud, OpenVSX compromises, Glassworm). Two host-level defenses block the majority of opportunistic install-time attacks:

  1. Release-age gates — refuse to install any package version published <7 days ago.
  2. Disable install lifecycle scriptspostinstall, preinstall, prepare. This is the #1 execution vector for npm malware.

Per-package-manager configs (from 2026 practitioner gist)

uv (Python)~/.config/uv/uv.toml

exclude-newer = "7 days"

pip (Python)~/.config/pip/pip.conf + shell alias

[global]
index-url = https://artifactory.myorg.local/artifactory/api/pypi/pypi/simple/
# macOS / BSD date
alias pip='pip --uploaded-prior-to $(date -u -v-7d +%Y-%m-%dT%H:%M:%SZ)'
# Linux / GNU date
alias pip='pip --uploaded-prior-to $(date -u -d "7 days ago" +%Y-%m-%dT%H:%M:%SZ)'

npm~/.npmrc

min-release-age=7
ignore-scripts=true

pnpmpnpm-workspace.yaml

minimumReleaseAge: 10080   # minutes = 7 days
minimumReleaseAgeExclude:
  - "@myorg/*"
  - "esbuild"
pnpm config set ignore-scripts true --global

yarn~/.yarnrc.yml

npmMinimalAgeGate: "7d"
enableScripts: false
npmPreapprovedPackages:
  - "@myorg/*"

bun~/.bunfig.toml

[install]
minimumReleaseAge = 10080

Bun disables lifecycle scripts by default.

Additional host hygiene

ControlNote
Full-disk encryption + MDMTable stakes
Hardware-backed MFA (YubiKey, TouchID WebAuthn)Resists npm/PyPI phishing
Separate publish identity from daily-driverScoped npm tokens per project
EDR with script execution telemetryCatches post-install spawning shells
Allowlist outbound from dev VMBlock C2 traffic
Rotate SSH keys, move to signed-commit keys (GPG / SSH)Prevents force-push forgery
Review VS Code / JetBrains extensions regularlyGlassworm / OpenVSX TeamPCP vector
Don’t store long-lived cloud creds on diskUse SSO + short-lived tokens

13. Admission Control & Runtime Verification

CI signing is only as useful as the deploy-time verification that enforces it. Without admission control, a compromised runner or registry can push unsigned images straight into production.

Kyverno policy — require cosign-verified images

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  background: false
  webhookTimeoutSeconds: 30
  rules:
    - name: check-image-signature
      match:
        any:
          - resources:
              kinds: [Pod]
      verifyImages:
        - imageReferences:
            - "ghcr.io/myorg/*"
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/myorg/*/.github/workflows/release.yml@refs/tags/v*"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev

Sigstore policy-controller — verify SLSA provenance predicate

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: require-slsa-provenance
spec:
  images:
    - glob: "ghcr.io/myorg/**"
  authorities:
    - keyless:
        identities:
          - issuer: https://token.actions.githubusercontent.com
            subjectRegExp: "https://github.com/slsa-framework/slsa-github-generator/.*"
        ctlog:
          url: https://rekor.sigstore.dev
      attestations:
        - name: must-have-slsa
          predicateType: https://slsa.dev/provenance/v1
          policy:
            type: cue
            data: |
              predicate: {
                buildDefinition: {
                  externalParameters: {
                    repository: =~"^https://github.com/myorg/"
                  }
                }
              }

OPA Gatekeeper — deny unpinned image tags

package kubernetes.admission

deny[msg] {
  input.request.kind.kind == "Pod"
  image := input.request.object.spec.containers[_].image
  not contains(image, "@sha256:")
  msg := sprintf("image %q must be pinned by digest", [image])
}

Runtime

LayerControl
NodeeBPF-based runtime detection (Falco, Tetragon) for exec of shells from package dirs
NetworkEgress NetworkPolicy; deny-by-default cluster egress; CoreDNS deny lists
SecretsVault/ExternalSecrets with short-lived leases; no baked-in secrets
IAMWorkload identity (no static cloud keys); IMDSv2 only
ImageScan on pull; expired image quarantine

14. Case Studies — Defensive Lessons

Each case below summarizes what happened, how it was detected, and what defenders learned. No reproduction steps.

14.1 SolarWinds SUNBURST (2020)

AspectDetail
VectorBuild-system compromise; backdoor injected into Orion build
Scale~18,000 organizations, including US federal agencies
DetectionFireEye discovered it in its own environment via anomalous 2FA enrollment
Dwell timeMonths — code was signed with SolarWinds’ legitimate cert
Defensive lessonCode signing alone is insufficient; the build platform itself must be trusted and attested (SLSA L3). Provenance would have shown “built on dev workstation, not expected builder.”

14.2 Codecov Bash Uploader (2021)

AspectDetail
VectorAttackers modified a Bash uploader script distributed via Codecov’s Docker image creation
PayloadExfiltrated CI environment variables (secrets, tokens)
DetectionCustomer noticed checksum mismatch on the script
Defensive lessonChecksum-verify any script piped from the internet. Never use production creds in CI. Use short-lived OIDC-federated creds.

14.3 Log4Shell / CVE-2021-44228 (2021)

AspectDetail
VectorJNDI lookup RCE in a transitive dependency most teams didn’t know they used
DetectionPublic disclosure after Minecraft PoC
Defensive lessonYou cannot patch what you can’t see. SBOMs answer “am I using Log4j anywhere?” in minutes.

14.4 Event-stream (2018)

AspectDetail
VectorOriginal maintainer handed off publish rights to an unknown contributor who added a malicious transitive dep
PayloadCrypto wallet stealer targeted at one specific downstream user
DetectionDeveloper noticed deprecated API warning, investigated
Defensive lessonMaintainer handoffs are a red flag. Registry-level notifications for ownership change. Lockfile diffs would have caught the new dep.

14.5 Chalk / Debug / color-* (September 2025)

AspectDetail
VectorMaintainer “qix” phished via fake npmjs[.]help domain impersonating npm support (Sept 5); within 16 minutes of account access, attacker published trojanized versions of 18+ packages: chalk, debug, ansi-styles, strip-ansi, supports-color, color-convert, color-string, color-name, ansi-regex, wrap-ansi, slice-ansi, color, simple-swizzle, supports-hyperlinks, has-ansi, chalk-template, backslash, is-arrayish; DuckDB ecosystem packages also hit
Reach2.6 billion combined weekly downloads; Wiz found malicious code reached 10% of cloud environments within the 2-hour live window
PayloadBrowser-side interceptor hooking fetch(), XMLHttpRequest, window.ethereum, Solana signing APIs; rewrote crypto wallet addresses with look-alike substitutions before signing; targeted ETH, BTC, SOL, TRX, LTC, BCH
Financial impact~$500 direct theft, but massive industry “denial-of-service” in remediation hours
DetectionAikido Security first; subsequent analysis by Wiz, Sygnia, Palo Alto Unit 42, Semgrep, Sonatype, ArmorCode, Vercel, JFrog
Defensive lessonA single phished maintainer of a utility package cascades into the entire JS ecosystem. 2FA with phishing-resistant factors (WebAuthn) is now mandatory for any popular-package maintainer. Release-age gates would have stopped most installs. The 16-minute attack window demonstrates that human-speed response is insufficient — automated controls must pre-empt.

14.6 Nx / s1ngularity (August 2025)

AspectDetail
VectorFlawed GitHub Actions workflow using pull_request_target with unsanitized PR titles enabled code injection; malicious telemetry.js postinstall in Nx packages 20.9.0–21.8.0 and @nx/devkit, @nx/js, @nx/node, @nx/workspace, @nx/eslint, @nx/enterprise-cloud, @nx/key
PayloadSystematically harvested GitHub tokens, npm keys, SSH keys, crypto wallets, .env files, AI CLI tool credentials (Claude, Gemini, Q); used AI tools with --dangerously-skip-permissions / --yolo / --trust-all-tools flags to enumerate more secrets; exfiltrated via double-base64 to attacker-created s1ngularity-repository GitHub repos; appended sudo shutdown -h 0 to shell startup files for sabotage
Scale2,349 credentials harvested from 1,079 compromised systems; 1,100+ credentials remained valid; Phase 2 (Aug 28): attackers used stolen GitHub tokens to make 10,767 private repos public, exposing 82,901 additional secrets (11,168 valid); 400+ users/orgs impacted, 5,500+ repos exposed
DetectionStepSecurity, Wiz, GitGuardian, Aikido; root cause identified by Adnan Khan
Defensive lessonBuild-tool compromise executes inside the build context with full access to secrets. Egress allowlists on runners would have blocked exfil. Never use pull_request_target without input sanitization; use zizmor to lint workflows. AI CLI tools are now an attack surface — their elevated permissions make them high-value targets. GitGuardian released the open-source S1ngularity Scanner for post-compromise assessment.

14.7 Shai-Hulud npm worm (September 2025) — “First successful self-propagating npm worm”

AspectDetail
VectorCredential-harvesting phishing spoofing npm → maintainer account access → postinstall scripts harvest credentials → stolen npm tokens used to auto-publish malicious versions of victim’s other packages (worm propagation via NpmModule.updatePackage function)
PropagationSelf-spreading; 500+ package versions compromised including ngx-bootstrap (300k/wk), ng2-file-upload (100k/wk), @ctrl/tinycolor (2.2M/wk); ~3.6MB Webpack-bundled bundle.js payload
Payload mechanicsInstalled TruffleHog for broad secret scanning (800+ secret types); targeted npm, GitHub, AWS, GCP, Azure tokens; exfiltrated to attacker-created Shai-Hulud GitHub repos with double-base64 encoding; also injected .github/workflows/shai-hulud-workflow.yml using ${​{ toJSON(secrets) }} for persistent exfil; Linux/macOS only (skipped Windows)
DetectionReversingLabs, StepSecurity, Trend Micro, CISA advisory (Sept 23); npm team acted
Detection signalsAnomalous publish events, new npm versions without corresponding release commits, outbound exfil to public GitHub repos, repos named “Shai-Hulud” with description “Shai-HuludRepository”, branches named shai-hulud
LLM involvementUnit 42 assesses with moderate confidence that an LLM was used to generate the malicious bash script (based on comments and emojis in code)
Defensive lessonDeveloper machines are now the target. ignore-scripts=true would have blocked execution. Never store long-lived npm tokens; use OIDC Trusted Publishers. Similarities with Nx/s1ngularity suggest related or copycat actors.

14.8 Shai-Hulud 2.0 (November 2025)

AspectDetail
Scale~25,000 repositories hijacked, ~500 GitHub users, 796 unique npm packages (20M+ weekly downloads) — including Zapier, ENS Domains, PostHog, Postman
Key evolutionSwitched from postinstall to preinstall — executes before any security checks; eliminates need for human interaction; bypasses static scanning tools
Payloadsetup_bun.js + 10MB obfuscated bun_environment.js; installed Bun runtime (likely to evade Node.js monitoring); used TruffleHog to scan for 800+ secret types; harvested AWS Secrets Manager entries (SDK pagination), GCP secrets (@google-cloud/secret-manager), Azure creds; exfil to repos described “Sha1-Hulud: The Second Coming”; fake commits under “Linus Torvalds” name
Dead man’s switchIf the worm cannot replicate or exfiltrate, it attempts to delete the user’s home directory — destructive fallback discovered by GitLab Vulnerability Research team
GH Actions abuseRegistered self-hosted runners as “SHA1HULUD”; backdoored “formatter” workflows dumped toJSON(secrets); malicious discussion.yaml for remote command execution
Self-replicationReads its own content to propagate — no C2 server needed for replication; automatically backdoors up to 100 packages per victim
DetectionAikido, ReversingLabs, Wiz, Datadog Security Labs, Microsoft Defender, Trend Micro, GitLab via GitHub Archive telemetry; Microsoft Defender issued dedicated alert “Sha1-Hulud Campaign Detected”
Defensive lessonTreat self-hosted runners as production. Audit runner registrations. Restrict permissions: blocks. Avoid toJSON(secrets) anti-patterns. Commit signature verification defeats fake persona commits. The destructive fallback means incident response must account for data loss, not just exfiltration.

14.9 Axios / plain-crypto-js (March 2026) — DPRK-attributed

AspectDetail
VectorMaintainer account hijack (email changed to ifstap@proton.me) → axios@1.14.1 and axios@0.30.4 published with a brand-new hidden transitive dep plain-crypto-js@4.2.1 instead of inline malware
AttributionGoogle Threat Intelligence Group (GTIG) attributes to UNC1069, a financially motivated North Korea-nexus actor active since 2018, based on the WAVESHAPER.V2 backdoor and infrastructure overlaps
Reachaxios = 100M+ weekly downloads per affected version (1.14.1 + 0.30.4 combined ~183M/wk)
PayloadSILKBELL dropper (setup.js): custom XOR + Base64 string obfuscation; dynamically loads fs, os, execSync; OS-specific execution: Windows (copies powershell.exe to %PROGRAMDATA%\wt.exe, downloads payload via curl POST), macOS (AppleScript), Linux (Python); deploys WAVESHAPER.V2 backdoor (RAT)
Anti-forensicsDeleted setup.js after execution; rewrote package.json to remove postinstall hook and restore benign appearance
DetectionSonatype flagged within minutes (01:04 UTC March 31); GTIG, StepSecurity reporting
Live windowMarch 31, 2026 00:21–03:20 UTC (~3 hours)
Defensive lessonAttackers now inject a hidden transitive instead of modifying core code — SBOM diff is the fastest reliable signal. Repository firewalls that quarantine new packages would have blocked install. Nation-state actors (DPRK) are now directly conducting npm supply chain attacks for financial gain.

14.10 TeamPCP campaign (March 2026, ongoing)

Sub-incidentSummary
Trivy v0.69.4Stolen Aqua Security creds used to publish malicious release + force-push 76 GitHub Action tags. Propagated to GHCR, Docker Hub, ECR Public, deb/rpm, get.trivy.dev. Actions dumped CI runner memory and exfiltrated via lookalike domain.
CanisterWorm npmSelf-propagating worm across 40+ packages; stole tokens, bumped patch versions, republished; Kubernetes-targeting payload
Checkmarx KICS GitHub Action + OpenVSXSame credential-theft pattern; fallback exfil by creating public repo with victim GITHUB_TOKEN
LiteLLM (PyPI) 1.82.7/1.82.8Collected env vars, SSH keys, cloud creds, Kubernetes configs, Docker configs, shell history, DB creds, wallet files, CI secrets; encrypted locally and exfiltrated
Telnyx (PyPI) 4.87.1/4.87.2Same exfil payload

Unifying defensive lesson: credential theft → automated republishing is the modern blueprint. Required controls: (1) no long-lived publish tokens — OIDC Trusted Publishers only; (2) pinned, digest-bound Action SHAs; (3) signed tag enforcement; (4) egress allowlist on runners; (5) anomaly detection on publish velocity.

14.11 Glassworm (October 2025)

AspectDetail
VectorSelf-spreading VS Code extension on OpenVSX
Defensive lessonIDE extensions are code executing with user privileges. Treat extension installs like package installs — pin, review, scan.

14.12 PhantomRaven (October 2025)

AspectDetail
Vector126 npm packages with stealer payloads
Defensive lessonBehavioral scanners (Socket, Phylum) that look at what a package does catch these before CVE databases do.

14.13 DPRK 1,700-package flood (2025–2026)

AspectDetail
VectorNorth Korean cluster (Contagious Interview / Jackpot Panda overlaps) published across npm, PyPI, Go, Rust
Defensive lessonNation-state volume makes manual review infeasible. Automation + behavioral analysis + release-age gates are required.

14.14 XZ Utils backdoor / CVE-2024-3094 (March 2024)

AspectDetail
VectorMulti-year social engineering: attacker “Jia Tan” built credibility as an OSS contributor over 2+ years, gained co-maintainer status on the xz-utils project, then inserted an obfuscated backdoor into versions 5.6.0 and 5.6.1 via distribution tarballs (not visible in Git source)
PayloadMalicious shared object loaded by sshd via liblzma; hijacked RSA_public_decrypt to allow holders of a specific private key to execute arbitrary code pre-authentication; only activated on x86-64 DEB/RPM builds using gcc + GNU linker
AffectedFedora 40/41/Rawhide, Debian testing/unstable/experimental, Alpine Edge, Kali, openSUSE Tumbleweed, Arch Linux; NOT in Ubuntu, RHEL, Amazon Linux stable branches
DetectionAndres Freund (Microsoft) noticed 500ms SSH latency anomaly while benchmarking; disclosed March 29, 2024
Defensive lessonThe most sophisticated supply chain attack to date — code review alone is insufficient when the attacker is the maintainer. Binary/opaque files in commits are a red flag. Reproducible builds would have detected the tarball/source mismatch. Staged release pipelines (experimental → stable) limited blast radius. SBOM and provenance attestation would have surfaced the discrepancy between source and binary.

14.15 tj-actions/changed-files + reviewdog (CVE-2025-30066 / CVE-2025-30154, March 2025)

AspectDetail
VectorMulti-stage cascading compromise: (1) attacker exploited automated contributor invitation in reviewdog org → joined @reviewdog/actions-maintainer team → pushed malicious commit to reviewdog/action-setup, re-pointed v1 tag; (2) stolen credentials from reviewdog used to compromise tj-actions/changed-files via its dependency on tj-actions/eslint-changed-files → reviewdog/action-setup; (3) all version tags v1–v44.5.1 re-pointed to a single malicious commit. Origin traced to SpotBugs compromise in November 2024
Initial targetCoinbase (agentkit project) — attacker first targeted their public CI/CD flow but failed to access Coinbase secrets; then pivoted to broader attack
PayloadBase64-encoded script dumped CI runner memory containing workflow secrets to workflow logs (no external C2 needed — GitHub’s own logs served as exfiltration channel)
Scale23,000+ repositories used tj-actions/changed-files; Wiz identified dozens of repos with exposed AWS keys, GitHub PATs, npm tokens, private RSA keys
DetectionStepSecurity (March 14), Adnan Khan (linked to reviewdog, March 16), Unit 42 traced to SpotBugs origin
CISA advisoryJoint CVE-2025-30066 (tj-actions) and CVE-2025-30154 (reviewdog) advisory, March 18
Defensive lessonPin all GitHub Actions by commit SHA, not tag. Audit transitive composite action dependencies. Restrict contributor team auto-invitation. Log-based exfiltration bypasses network monitoring — restrict public workflow log visibility. ghs_ tokens are short-lived (<24h) so lower risk; custom secrets are highest priority for rotation.

14.16 Ultralytics PyPI (December 2024)

AspectDetail
VectorGitHub Actions script injection via pull_request_target trigger with unsanitized PR branch names (known vuln GHSA-7x29-qqmq-v6qc reported by Adnan Khan); attacker crafted malicious PR titles from forks to achieve arbitrary code execution in the build environment
PayloadXMRig coinminer downloaded via platform-specific dropper injected into downloads.py and model.py; affected versions 8.3.41, 8.3.42, 8.3.45, 8.3.46
Compounding failureVersion 8.3.42, intended as the fix, shipped with the same malware (maintainers didn’t properly locate the compromise); versions 8.3.45/8.3.46 published via stolen PyPI API token from the initial build-env compromise
Scaleultralytics: 60M+ PyPI downloads, 30,000+ GitHub stars
Attestation valuePyPI staff used Sigstore transparency logs and publish attestations to determine first malicious releases came through legitimate GH Actions (build-phase injection) while second round had no attestations (stolen API token) — provenance was the forensic key
Defensive lessonAudit GH Actions for pull_request_target + unsanitized input patterns using zizmor. Revoke unused API tokens when using Trusted Publishers. Build environment caches are an attack surface. When remediating, verify the fix is actually clean before publishing.

14.17 dYdX npm + PyPI (2026)

AspectDetail
VectorCompromised dYdX packages deliver wallet stealers + RAT
Defensive lessonFintech / Web3 packages are high-value targets. Multi-registry campaigns mean defenders must unify SCA across language ecosystems.

14.18 React2Shell CVE-2025-55182 (Nov–Dec 2025)

Not a supply-chain injection but relevant as a dependency-triggered RCE: React Server Components 19.0.0–19.2.0 had a pre-auth RCE. Discovered by Lachlan Davidson; exploited within days by Earth Lamia, Jackpot Panda, Contagious Interview. Over 77,000 vulnerable IPs scanned by Shadowserver. CISA KEV December 17.

Defensive lesson: SBOM-driven scanning plus an emergency response playbook are load-bearing. The fastest organizations to patch had automated SBOM → KEV lookups.

14.19 Oracle EBS CVE-2025-61882 (Oct 2025) — Clop

Oracle E-Business Suite zero-day (CVSS 9.8) exploited by Clop for mass data theft (Barts Health, Canon, GlobalLogic, LKQ, Logitech, Mazda).

Lesson: Commercial dependencies need the same patching SLAs as OSS.

14.20 ToolShell CVE-2025-53770/-53771 (July 2025)

Chained SharePoint on-prem exploits by Linen Typhoon, Violet Typhoon, Storm-2603; ~396 systems compromised; web shells deployed.

Lesson: Internet-facing internally-run commercial software is a supply chain node — treat it with the same monitoring as your own binaries.

14.21 CitrixBleed 2 CVE-2025-5777 (June 2025)

Out-of-bounds read bypassing MFA / hijacking session tokens in NetScaler ADC/Gateway.

Lesson: Network-appliance firmware is supply chain. SBOM your appliances.

14.22 Bybit $1.5B (Feb 2025)

Wallet-software supply chain compromise executing only when the target wallet was active.

Lesson: Conditional payloads evade test benches. Runtime behavioral monitoring complements static analysis.

14.23 Composite: SolarWinds, Codecov, Log4Shell, XZ Utils

Common thread: a small number of high-trust components → catastrophic downstream. The only durable defense is verifiable provenance + SBOM transparency + deploy-time enforcement.


15. Detection Signals & IOCs

Build / CI signals

SignalPossible attack
New postinstall / preinstall in a minor version bumpnpm dropper
New transitive dep introduced in a patch releaseAxios-style hidden injection
Runner egress to unrecognized domain during npm installC2 beacon
toJSON(secrets) in workflow diffShai-Hulud 2.0 pattern
Self-hosted runner registered with anomalous name (e.g. SHA1HULUD)Worm propagation
Force-push to release tagTrivy v0.69.4 pattern
Publish event without corresponding release commitWorm re-publish
Unexpected npm publish / twine upload in CI logsToken abuse
CI workflow logs containing double-base64 encoded stringstj-actions exfil pattern
preinstall script in a patch/minor bump referencing setup_bun.js or bun_environment.jsShai-Hulud 2.0
GitHub repo created with description containing “Shai-Hulud” or “Sha1-Hulud”Worm exfil target
Branch named shai-hulud with workflow file in .github/workflows/Persistence mechanism
pull_request_target workflow with unsanitized ${​{ github.event.pull_request.title }}Script injection vector (Ultralytics, Nx root cause)
PyPI publish without corresponding Sigstore attestation when project uses Trusted PublishersStolen API token (Ultralytics second wave)
GitHub contributor auto-invitation to teams with write accessreviewdog compromise vector

Package-level signals

SignalCheck
Obfuscated strings, base64/XOR decoding in install scriptsStatic scan
OS fingerprinting (process.platform, sys.platform) in install scriptsBehavioral
Writes to /tmp + chmod +x + exec in install scriptsBehavioral
Network fetch during installBehavioral
package.json rewritten at runtimeAnti-forensics — Axios pattern
Single-maintainer, <30 day age, no source repoMetadata heuristics
Bun runtime installation (curl -fsSL bun.sh/install) in install scriptsShai-Hulud 2.0 evasion of Node.js monitoring
TruffleHog binary downloaded during installCredential harvesting (Shai-Hulud 1.0/2.0)
rm -rf ~ or shred commands in install scriptsDead man’s switch / destructive fallback
Custom XOR + Base64 string deobfuscation patternsSILKBELL dropper (Axios/DPRK)

Workstation / dev signals

SignalSource
Shell spawn from node, python, pip, npm processesEDR
Read of ~/.aws/credentials, ~/.ssh/id_*, ~/.kube/config, ~/.docker/config.json by package installEDR + DLP
New files under /tmp, %TEMP%, ~/Library correlated with package installEDR
Outbound to GitHub Gists / raw.githubusercontent.com from installNetwork
AI CLI tools (Claude, Gemini, Q) invoked with --dangerously-skip-permissions, --yolo, --trust-all-toolsNx/s1ngularity AI exploitation
New GitHub repos named s1ngularity-repository* or Shai-Hulud in victim accountsExfiltration repos
Shell startup files (~/.bashrc, ~/.zshrc) modified with shutdown commandsNx sabotage payload
Copied system binaries (e.g., powershell.exe%PROGRAMDATA%\wt.exe)SILKBELL dropper evasion

Hunting queries (SIEM pseudocode)

# Anomalous npm publish from CI
process.name = "npm" AND argv contains "publish"
  AND runner.self_hosted = true
  AND runner.name NOT IN (allowlist)

# Credential file access by package install
process.parent IN ("npm","node","pip","python","uv")
  AND file.path MATCHES ("~/.aws/*","~/.ssh/*","~/.kube/*","~/.docker/*")

# New transitive dep in patch version
repo.event = "pull_request"
  AND file.path IN ("package-lock.json","poetry.lock","go.sum")
  AND diff.adds CONTAINS "new package" AND semver.bump = "patch"

16. Defender Checklist

Strategy

  • Inventory every supply chain node: registries, CI systems, artifact repos, IDE extensions, base images, vendor SaaS
  • Assign an owner per node with a runbook and rotation plan
  • Establish patch SLAs per severity; measure MTTR to KEV
  • Build an incident response playbook keyed to supply-chain scenarios (maintainer ATO, worm, hidden transitive, build poisoning)
  • Table-top a Chalk/Debug-scale and an Axios-scale incident annually (include destructive Shai-Hulud 2.0 scenario with data loss)
  • Align SBOM program to CISA 2025 Minimum Elements guidance

Source & Code

  • Branch protection on default branch
  • Required review (>=1), with CODEOWNERS for sensitive paths
  • Signed commits + signed tags enforced
  • Disallow force-push to protected branches/tags
  • Secret scanning enabled on all repos (GitGuardian / GitHub push protection)
  • No long-lived secrets in CI — OIDC federation to cloud

Dependencies

  • Lockfiles committed for all language ecosystems
  • --require-hashes or equivalent for reproducibility where supported
  • Dependabot or Renovate enabled, with minimumReleaseAge >= 7 days
  • OSV-Scanner or Trivy on every PR + on a nightly schedule
  • Dependency-Track (or equivalent) SBOM aggregation and VEX
  • Behavioral scanner (Socket, Phylum) wired into PR gate
  • Private-registry virtual proxy (Nexus/Artifactory) quarantining new packages

Build / CI

  • Ephemeral runners only
  • GitHub Actions pinned by commit SHA, not tag
  • Minimal permissions: per job; default deny
  • ignore-scripts=true where possible in CI installs
  • Egress allowlist on runners
  • No toJSON(secrets) anywhere
  • No pull_request_target with unsanitized user inputs; lint with zizmor
  • Audit composite action transitive dependencies
  • Review GitHub org team auto-invitation policies (reviewdog lesson)
  • Enable tag protection rules to prevent tag re-pointing
  • Tamper-evident audit log export
  • Separation of duties: code author != release signer

Artifacts / Packaging

  • SBOM generated at build (CycloneDX + SPDX)
  • SBOM attached to artifact as attestation (cosign attest)
  • Image signed keyless via cosign + Sigstore
  • SLSA L3 provenance via slsa-github-generator
  • Reproducible builds where feasible
  • Artifacts immutable once published

Registry / Distribution

  • 2FA required on all maintainer accounts (hardware, not SMS)
  • Trusted Publishers (OIDC) replacing long-lived tokens
  • Publish-alert notifications to shared channel
  • Namespace/scope claim for internal packages on public registries
  • Repository firewall quarantining newly published or low-reputation deps

Deploy / Runtime

  • Admission controller (Kyverno / policy-controller) verifies cosign signature + SLSA predicate
  • Images referenced by digest only
  • Deny-by-default egress NetworkPolicy
  • IMDSv2 only; workload identity; no baked creds
  • Runtime detection (Falco/Tetragon) for shells-from-package dirs
  • Canary / staged rollout — never deploy all systems simultaneously (OWASP A03 guidance)

Developer Host

  • Release-age gate configured in npm/pnpm/yarn/bun/pip/uv
  • ignore-scripts / enableScripts: false globally
  • Hardware MFA on registry accounts
  • EDR with script-execution telemetry
  • IDE extension allowlist; review extension updates
  • Verify AI-suggested packages before install (slopsquatting)
  • Review AI CLI tool permissions and disable dangerous auto-approve flags
  • Revoke unused PyPI API tokens when using Trusted Publishers

Response

  • On suspected compromise, assume full credential exposure — rotate all secrets, cloud keys, SSH keys, tokens touched by the affected environment
  • Rebuild affected workstations and CI runners from a clean image
  • Pull audit logs for the suspected window
  • Block the indicator at repository firewall and network egress
  • Publish an internal advisory with affected package/version/IOC list
  • Retrospect to close the gap that permitted the compromise

17. Reference Configurations

17.1 Full hardened .npmrc

registry=https://nexus.myorg.local/repository/npm-group/
@myorg:registry=https://nexus.myorg.local/repository/npm-private/
always-auth=true
audit=true
fund=false
save-exact=true
min-release-age=7
ignore-scripts=true
engine-strict=true
package-lock=true

17.2 Full hardened pip.conf

[global]
index-url = https://artifactory.myorg.local/artifactory/api/pypi/pypi/simple/
require-virtualenv = true
disable-pip-version-check = true
no-cache-dir = false
require-hashes = true

17.3 GitHub Actions repository default permissions

permissions:
  actions: read
  contents: read
  deployments: none
  id-token: none
  issues: none
  packages: none
  pages: none
  pull-requests: none
  repository-projects: none
  security-events: none
  statuses: none

Escalate per-workflow only where needed.

17.4 Trivy image + config scan

trivy image --severity HIGH,CRITICAL --exit-code 1 \
  --ignore-unfixed \
  --format sarif --output trivy.sarif \
  ghcr.io/myorg/app@sha256:abcd...

trivy config --severity HIGH,CRITICAL --exit-code 1 .
trivy fs --scanners secret,vuln,misconfig .

17.5 OSV-Scanner offline

osv-scanner --experimental-offline --experimental-download-offline-databases ./

17.6 Syft + Grype end-to-end

syft dir:. -o cyclonedx-json=sbom.cdx.json
cosign sign-blob --yes --bundle sbom.cosign.bundle sbom.cdx.json
grype sbom:sbom.cdx.json --fail-on high -o sarif > grype.sarif

17.7 Policy-as-code example (Conftest / OPA on Dockerfile)

package main

deny[msg] {
  input[i].Cmd == "from"
  val := input[i].Value[0]
  not contains(val, "@sha256:")
  msg := sprintf("FROM %s must use @sha256 digest", [val])
}

deny[msg] {
  input[i].Cmd == "run"
  contains(input[i].Value[_], "curl")
  contains(input[i].Value[_], "|")
  contains(input[i].Value[_], "sh")
  msg := "curl | sh is banned; use pinned, verified installers"
}

17.8 Dependency-Track policy example

PolicyConditionAction
New high-severity CVEcomponent.vuln.severity >= HIGHFail build
Unmaintained depcomponent.last_modified > 24 monthsWarn
Unapproved licenselicense NOT IN allowlistFail
Dep from untrusted registrycomponent.purl !~ allowed_registriesFail
New publishercomponent.publisher NEWManual review

17.9 Release playbook summary

1. Feature branch -> PR -> required review -> merge to main
2. Tag v* -> triggers release workflow (OIDC, read-only by default)
3. Workflow: build -> SBOM -> scan -> sign -> attest (SLSA + SBOM)
4. Publish artifact + signature bundle to registry
5. Admission controller verifies at deploy time
6. Canary rollout 5% -> 25% -> 100% with automated rollback
7. Post-deploy: runtime telemetry + SBOM archived alongside release

17.10 Emergency triage on a suspected compromised dependency

1. Identify affected package + version(s) from advisory
2. Query SBOM store: "which services ship this?"
3. For each hit:
   - Pin to last-known-good version and force-rebuild
   - Invalidate all secrets accessible from the build and runtime context
   - Rebuild and redeploy affected workloads from clean state
   - Review CI runner logs + egress traffic during vulnerable window
4. Block malicious version at repository firewall
5. Add advisory to internal KEV-equivalent list
6. Notify downstream consumers if you publish packages
7. Post-incident: determine which SLSA level / SBOM coverage / admission rule would have prevented it; file follow-up action items

Appendix A — Frameworks & Standards Quick Map

StandardBodyScope
SLSA v1.1OpenSSFBuild integrity levels, provenance format
in-totoCNCFAttestation statement format
Sigstore / cosignOpenSSFKeyless signing, transparency log
SPDX 2.3 / 3.0Linux FoundationSBOM (compliance-oriented)
CycloneDX 1.5/1.6OWASPSBOM (security-oriented), VEX, ML-BOM
NIST SSDF (SP 800-218)NISTSecure software development framework
EO 14028US Executive OrderFederal software supply chain baseline
NIST SP 800-161 Rev.1NISTC-SCRM for systems and organizations
ISO/IEC 5230 (OpenChain)ISOOSS compliance program
OWASP SAMM / ASVS V15OWASPSecure coding & architecture verification
CIS Software Supply Chain Security GuideCISBenchmark controls
SAFECode Software Integrity ControlsSAFECodeIntegrity control practices

Appendix B — Key CWEs

CWEDescription
CWE-477Use of Obsolete Function
CWE-1035Using Components with Known Vulnerabilities (2017 Top 10 A9)
CWE-1104Use of Unmaintained Third-Party Components
CWE-1329Reliance on Component That Is Not Updateable
CWE-1357Reliance on Insufficiently Trustworthy Component
CWE-1395Dependency on Vulnerable Third-Party Component

Appendix C — Sources (54 articles)

  1. 12 Months That Changed Supply Chain Security (Silobreaker)
  2. 16 Minutes to Impact: npm Supply Chain Abuse Deploys crypto-draining malware (Sygnia)
  3. 2025 Minimum Elements for a Software Bill of Materials (SBOM) (CISA)
  4. 2026 Supply Chain Security Report — Lessons from a Year of Devastating Attacks (Bastion)
  5. A03 Software Supply Chain Failures (OWASP)
  6. Achieving SLSA 3 Compliance with GitHub Actions and Sigstore for Go modules (GitHub Blog)
  7. Axios Compromise on npm Introduces Hidden Malicious Package (Sonatype)
  8. Axios npm Package Compromised in Supply Chain Attack (InfoQ)
  9. Breakdown: Widespread npm Supply Chain Attack Puts Billions of Weekly Downloads at Risk (Palo Alto)
  10. Compromised dYdX npm and PyPI Packages Deliver Wallet Stealers and RAT Malware (The Hacker News)
  11. Compromised ultralytics PyPI package delivers crypto coinminer (ReversingLabs)
  12. cosign Verification of npm Provenance, GitHub Artifact Attestations, and Homebrew Provenance (Sigstore Blog)
  13. CVE-2024-3094 XZ Backdoor: All you need to know (JFrog)
  14. Five Key Flaws Exploited in 2025’s Major Software Supply Chain Incidents (Infosecurity Magazine)
  15. GitHub Actions Supply Chain Attack: A Targeted Attack on Coinbase Expanded to the Widespread tj-actions/changed-files Incident (Unit 42)
  16. GitHub Action tj-actions/changed-files supply chain attack: everything you need to know (Wiz)
  17. GitLab discovers widespread npm supply chain attack (GitLab)
  18. Hackers Supply Chain Attack Moves From npm to PyPI as Trivy Breach Extends into LiteLLM (Semgrep)
  19. How to Prevent OWASP Software Supply Chain Failures (CrossClassify)
  20. LiteLLM PyPI Packages Compromised in Expanding TeamPCP Supply Chain Attacks (Help Net Security)
  21. Malicious PyPI and npm Packages Discovered Exploiting Dependencies in Supply Chain Attacks (The Hacker News)
  22. N. Korean Hackers Spread 1,700 Malicious Packages Across npm, PyPI, Go, Rust (The Hacker News)
  23. North Korea-Nexus Threat Actor Compromises Widely Used Axios NPM Package in Supply Chain Attack (Google Threat Intelligence / GTIG)
  24. NPM Supply Chain Attacks Explained — Dependency Confusion, Exploits, and Defense (jsmon)
  25. OWASP Top 10 2025 A03 — Software Supply Chain Failures (Authgear)
  26. Predictions for Open Source Security in 2025 — AI, State Actors, and Supply Chains (OpenSSF)
  27. Protecting Your Software Supply Chain — Typosquatting and Dependency Confusion (GitGuardian)
  28. PyPI, npm, and the New Frontline of Software Supply Chain Attacks (RapidFort)
  29. s1ngularity: supply chain attack leaks secrets on GitHub: everything you need to know (Wiz)
  30. Securing CI/CD Pipelines After the tj-actions and reviewdog Supply Chain Attacks (OpenSSF)
  31. Securing Software Supply Chains — Critical Infrastructure Priorities for 2026 (Leadership Connect)
  32. Shai-Hulud 2.0: Guidance for detecting, investigating, and defending against the supply chain attack (Microsoft Security Blog)
  33. Shai-Hulud 2.0 Supply Chain Attack: 25K+ Repos Exposing Secrets (Wiz)
  34. Shai-hulud npm attack: What you need to know (ReversingLabs)
  35. Shai-Hulud: Self-Replicating Worm Compromises 500+ NPM Packages (StepSecurity)
  36. “Shai-Hulud” Worm Compromises npm Ecosystem in Supply Chain Attack (Unit 42)
  37. SLSA Framework — The Definitive Guide for Securing Your Software Supply Chain (Practical DevSecOps)
  38. Software Supply Chain Attacks 2025–2026 — Axios, Shai-Hulud, Chalk, TeamPCP (Cyber Army)
  39. Software Supply Chain Risks — 2026 Software Supply Chain Report (Sonatype)
  40. Supply-chain attack analysis: Ultralytics (PyPI Blog)
  41. Supply-Chain Attack Defense — Developer Host Machine Hardening (gist)
  42. Supply-chain Levels for Software Artifacts (slsa.dev)
  43. Supply Chain Attack — How Attackers Weaponize Software Supply Chains (Netlas)
  44. Supply Chain Attacks Are Exploiting Our Assumptions (Trail of Bits)
  45. Supply Chain Attacks in Q4 2025 — From Isolated Incidents to Systemic Failure Modes (Sygnia)
  46. Supply Chain Compromise of Third-Party tj-actions/changed-files (CVE-2025-30066) and reviewdog/action-setup@v1 (CVE-2025-30154) (CISA)
  47. Supply Chain Security in CI — SBOMs, SLSA, and Sigstore (nathanberg.io)
  48. The 2026 Guide to Software Supply Chain Security (Cloudsmith)
  49. The Next Wave of Supply Chain Attacks — NPM, PyPI, Docker Hub Set the Stage for 2026 (LinuxSecurity)
  50. The Nx “s1ngularity” Attack: Inside the Credential Leak (GitGuardian)
  51. The Shai-Hulud 2.0 npm worm: analysis, and what you need to know (Datadog Security Labs)
  52. The XZ Utils backdoor (CVE-2024-3094): Everything you need to know, and more (Datadog Security Labs)
  53. Widespread npm Supply Chain Attack: Breaking Down Impact & Scope Across Debug, Chalk, and Beyond (Wiz)
  54. xz Backdoor CVE-2024-3094 (OpenSSF)

This document synthesizes publicly reported research and advisories for defensive reference. It contains no reproduction steps, no working malicious code, and no attacker tooling. All code snippets are defensive configurations.