Back to Cheat Sheets

🔗 Dependency Chain Abuse

OWASP Top 10 CI/CD Security Risks - CICD-SEC-3

CRITICAL RISK

📋 What Is It?

Dependency Chain Abuse covers every way an attacker can abuse how a build system fetches its dependencies to trick the build into pulling a malicious package instead of the intended one. The flaw is not in the dependency you chose; it is in the resolution and retrieval process that decides which bytes land on the build agent - which registry answers, which version wins, whether the name is trusted, whether an integrity hash is checked, and whether install-time scripts run. Because installing is executing, a malicious postinstall or setup.py runs on one of the most privileged, least-monitored machines in the supply chain.

CICD-SEC-3 OWASP Rank
Resolution Focus Area
5 Classes Of Abuse

⚠️ Common Exploits

  • Dependency Confusion: A public package shadows an unscoped private name; "highest version wins" fetches the attacker's copy
  • Typosquatting: A name one keystroke away (reqeusts, loadsh) is installed by mistake
  • Brandjacking: A package impersonates a trusted vendor namespace (acme-official-sdk)
  • Dependency Hijacking: Takeover of a real package via abandoned/expired/compromised maintainer account
  • Transitive Poisoning: A deep indirect dependency you never chose rides in through the graph
  • Install-Time Scripts: Lifecycle hooks run automatically on the agent - no import needed
  • Registry Merging: Public + private indexes queried together, public can win
  • Lockfile/Cache Poisoning: One bad resolution is pinned and re-installed forever

🔴 Attack Flow

1. Discover internal package names (commits, logs, bundles)
↓
2. Position: publish look-alike OR hijack a maintainer account
↓
3. Win resolution: higher version / public fallback / poisoned update
↓
4. Execute: postinstall / setup.py runs as the CI user
↓
5. IMPACT: Steal build secrets, poison the signed artifact!

❌ Vulnerable Code

# .npmrc - public registry is the only source; scopes fall back publicly registry=https://registry.npmjs.org/ # No @acme scope mapping, so unscoped internal names resolve PUBLICLY # -> dependency-confusion window is open. // package.json - floating ranges, unscoped internal name { "dependencies": { "acme-auth-utils": "^1.0.0", // internal name, unscoped, floating "lodash": "*" // any version the registry serves } } # pip.conf - private index MERGED with public PyPI; highest version wins [global] index-url = https://pypi.org/simple extra-index-url = https://pypi.internal.acme/simple # CI build step - loose install that trusts whatever resolves + runs scripts - run: npm install # regenerates lock, highest match, runs postinstall # A public "acme-auth-utils@99.99.99" wins and its postinstall executes.

✅ Secure Code

# .npmrc - single internal proxy; @acme bound PRIVATELY, no public fallback registry=https://registry.internal.acme/npm/ @acme:registry=https://registry.internal.acme/npm/ //registry.internal.acme/npm/:_authToken=${NPM_TOKEN} always-auth=true ignore-scripts=true # no lifecycle scripts during install // package.json - scoped internal name, exact versions { "dependencies": { "@acme/auth-utils": "1.4.2", // scoped -> only the private registry "lodash": "4.17.21" // exact version, matched to lockfile } } # CI install stage - locked, hash-verified, script-free install: script: - npm ci --ignore-scripts # exact, sha512-checked, no scripts - npm audit signatures # verify registry provenance # No deploy secrets here; ephemeral, non-root user, restricted egress. # pip equivalent - single internal index, hashes enforced, wheels only pip install --require-hashes --only-binary=:all: -r requirements.txt

✓ Prevention Checklist

  • Fetch only from a controlled internal proxy with an allow-list
  • Scope all internal names and claim the scope publicly
  • Bind private scopes to the private registry - no public fallback
  • Never merge public + private indexes (no extra-index-url)
  • Pin exact versions AND verify integrity hashes
  • Install in locked mode: npm ci / --require-hashes
  • Disable install scripts (ignore-scripts / wheels-only)
  • Verify signatures/provenance where the ecosystem supports it
  • Run SCA on every build; monitor for new look-alike names
  • Run the install stage least-privilege, with no deploy secrets

🔍 Detection & Tools

Supply-Chain Tooling:

Trivy Sigstore / cosign SLSA Provenance npm audit signatures OSV-Scanner Chainguard

Registry & Proxy:

Artifactory / Nexus Socket.dev Dependabot Scoped registries CycloneDX SBOM

How to Test:

  • Check if unscoped internal names can resolve publicly
  • Confirm scopes are claimed on the public registry
  • Verify the build fails on a lockfile/hash mismatch
  • Confirm install scripts are disabled on the agent
  • Audit for extra-index-url merging of public + private
  • Test that the install stage holds no deploy secrets

🌍 Real-World (Incident Classes)

  • 2021 Dependency-Confusion Research Wave: A researcher published public packages matching unclaimed internal names at high version numbers; benign proof-of-concept code was pulled into the internal builds of numerous major companies - the canonical substitution example that triggered scope reservation and registry pinning across the industry.
  • Maintainer-Account Hijack (event-stream class): A widely-depended-upon npm utility was handed to a new volunteer maintainer who added an obfuscated malicious transitive dependency, riding into countless projects through the graph.
  • Recurring Typosquat & Brandjack Waves: Automated campaigns continually flood npm and PyPI with look-alike names carrying install-time secret-harvesting scripts; registries remove them in routine batches.
  • Leaked Publish Token → Poisoned Release: A registry API/publish token exposed in a public repo or CI log lets an attacker ship a malicious version of a real package to everyone who upgrades.

📌 Quick Tips

  • DO NOT leave internal names unscoped
  • DO NOT merge public and private indexes
  • DO NOT run install scripts by default
  • DO pin versions and verify hashes
  • DO install in locked mode (npm ci)
  • DO claim your scopes on public registries
  • DO verify signatures / provenance

📜 Compliance

Related Standards:

  • SLSA Provenance & Build Integrity
  • NIST SSDF PW.4 - Reuse Secure Components
  • EO 14028 SBOM Requirements
  • ISO 27001 A.14 - Secure Development
  • PCI-DSS Req 6.3 - Software Integrity