A build that resolves dependencies from the public internet will run whatever code a registry hands it, often before any scanner has looked at it. CICD-SEC-3, Dependency Chain Abuse, is the third risk in the OWASP Top 10 CI/CD Security Risks. It is about how developer workstations and build environments fetch packages, not about known vulnerabilities in those packages, and it covers dependency confusion, dependency hijacking, typosquatting and brandjacking. This guide sets out what OWASP requires, translates each principle into package manager settings for npm and pip, explains the identity controls that stop a poisoned package from stealing pipeline credentials, and maps the risk to NIST, CIS, ISO, OpenSSF S2C2F and ATT&CK. It is for platform, build and AppSec engineers who own dependency resolution.
Key takeaways
- The fetch process is the attack surface. OWASP defines the risk as abuse of flaws in how workstations and build environments fetch code dependencies, resulting in a malicious package being fetched and executed when pulled.
- Install equals execute. Pre-install and post-install scripts run package code immediately, so a malicious package usually acts before tests, scans or review.
- Route everything through an internal proxy. OWASP's first principle is that no client should fetch directly from the internet; a curated internal repository gives a control point and an investigation trail.
- Pin, hash and scope. Lockfiles, hash checking, organisation scopes and committed package manager configuration close dependency confusion and most typosquatting paths.
- Keep secrets away from install steps. Recent npm worms harvested CI credentials at install time; running installs in a context without secrets limits the blast radius when prevention fails.
What it is and who it applies to
OWASP notes that packages are fetched by a dedicated client per language, usually from a mix of self-managed repositories such as JFrog Artifactory and public SaaS registries such as npm, PyPI and RubyGems. Many organisations scan dependencies for known vulnerabilities, but OWASP argues that an equally important set of controls governs how dependencies are pulled. A misconfigured client can lead an engineer, or worse the build system, to download a malicious package instead of the intended one. OWASP lists four attack vectors:
| Vector | OWASP definition (paraphrased) | Primary control | Example |
|---|---|---|---|
| Dependency confusion | Publishing a public package with the same name as an internal one so clients pick the public copy | Single trusted index, organisation scopes, internal namespace reservation | Alex Birsan's 2021 research reaching more than 35 organisations |
| Dependency hijacking | Taking over a maintainer account to publish a malicious version of a popular package | Lockfiles, version pinning, release age delay, maintainer MFA and trusted publishing | ua-parser-js, coa and rc npm hijacks cited by OWASP |
| Typosquatting | Publishing packages with names similar to popular ones, relying on misspelling | Curated allowlist, malware scanning at the proxy, review of new dependencies | No specific case cited by OWASP |
| Brandjacking | Publishing packages that imitate a brand's naming convention | Allowlist by publisher or scope, provenance checks | No specific case cited by OWASP |
The risk applies to any organisation that builds software with third-party packages, which in practice means all of them. It applies with most force to CI runners, because they hold deployment credentials and execute installs unattended. OWASP's impact statement names credential theft and lateral movement from the host that pulled the package, and malicious code reaching production through the build, often while the package keeps its expected functionality so detection is less likely. See the Malicious Package and Dependency Confusion Guide for the threat background; this page concentrates on the OWASP control set.
Structure and key requirements
OWASP accepts that mitigations are specific to each language client and proxy, but says all of them share the same guiding principles. The table turns each into concrete settings.
| OWASP principle | npm implementation | pip implementation | Verification |
|---|---|---|---|
| Pull through an internal proxy, not the internet | registry= in a project .npmrc pointing at the internal repository | --index-url (or index-url in pip.conf) pointing at the internal repository | Egress from runners to public registries blocked; proxy logs show all fetches |
| Disallow direct external pulls; enforce client configuration | Commit .npmrc in every repository; CI fails if registry differs | Never use --extra-index-url for private packages | Policy check on config files in pull requests |
| Checksum and signature verification | npm ci with package-lock.json integrity hashes; registry signatures and provenance where available | --require-hashes with sha256 hashes for every requirement | Builds fail on hash mismatch |
| Avoid "latest"; pin pre-vetted versions | Exact versions in the lockfile; npm ci never rewrites it | Pin with ==; hash mode requires pinning | No floating ranges in production manifests |
| Register and reference private packages under the organisation scope | @org/ scope, with @org:registry= mapped to the internal registry | Reserve internal names on the public index or use a prefix the proxy routes internally | Proxy rule blocks public packages matching internal names |
| Separate context for install scripts, without secrets | ignore-scripts=true or an allowlist; install in a job without deployment secrets | Prefer wheels with --only-binary :all: so no build scripts run | Install jobs have no secret or OIDC permissions |
| Do not publish internal project names | Strip package.json contents from public bundles and source maps | Keep internal requirements files out of public repositories | Secret and name scanning on published artefacts |
| Detection, monitoring and hardening | Proxy logging, malware scanning, and system hardening as described in CICD-SEC-7 Insecure System Configuration | Alerts on new packages, new maintainers and install-time network calls | |
Package manager documentation supports these settings directly. The npm documentation states that npm ci requires an existing package-lock.json, exits with an error if it does not match package.json, and never writes to either file, which makes installs effectively frozen. Each npm user or organisation has its own scope and only they can add packages to it. The ignore-scripts setting stops npm running scripts from package.json files, and the before setting restricts installs to versions available on or before a given date; current npm documentation also describes a min-release-age setting expressed in days. The pip documentation states that, by default, pip performs no checks against remote tampering and runs arbitrary code from distributions, and that hash-checking mode is all or nothing: hashes are needed for every requirement and every dependency, and requirements must be pinned. It recommends sha256 and notes that hashes supplied by the index in download URLs are not a guard against tampering.
Birsan's research explains why the index setting matters. pip's --extra-index-url checks both the internal and the public index and installs the higher version, so a public package with an inflated version number wins. He found internal package names in package.json files embedded in public JavaScript, in leaked paths and in public repositories, and recommended --index-url instead.
Identity and NHI requirements
Dependency chain abuse is usually described as a code problem, but its outcomes are identity outcomes. On both sides of the registry, machine credentials decide the damage.
- Consumer side, runner credentials: a malicious package runs with whatever the install step can reach. In the ChainDrop npm Worm, a preinstall script launched a payload that targeted npm, GitHub, cloud, Kubernetes and Vault credentials and, on GitHub Actions runners, read secrets from runner process memory. Install jobs should therefore receive no deployment secrets and no
id-token: writepermission; give those only to later jobs that consume vetted artefacts. See the CI/CD Pipeline Identity Security Guide. - Producer side, publishing identities: dependency hijacking is account takeover of a maintainer or a publishing token. npm and PyPI both support trusted publishing over OIDC, which replaces long-lived tokens; PyPI states that the API token it issues expires 15 minutes after creation. npm recommends that once trusted publishing works, packages are set to "Require two-factor authentication and disallow tokens". See the npm Tokens Security Guide.
- Provenance is not identity assurance: the ChainDrop malicious release carried valid provenance because the attacker controlled a legitimate maintainer's account. Provenance shows where a build ran; it does not prove the identity behind the change was safe.
- Proxy and registry service accounts: the internal repository's own credentials to upstream registries, and the CI identities that publish into it, are high-value NHIs. Give them write access only to the namespaces they own and review them with other pipeline identities.
- Bot-driven updates: automated update tools need registry credentials; GitHub documents OIDC authentication for Dependabot to private registries so those credentials need not be stored as secrets.
The Shai-Hulud npm Worm shows both sides at once: stolen publishing tokens let the worm republish victims' packages, and its install-time payload harvested further tokens from developer machines and CI runners. See also the OWASP NHI Top 10 NHI7 Long-Lived Secrets Guide.
Implementation steps
- Inventory clients and sources (weeks 1 to 3). List every package manager in use, every configured index or registry, every place
--extra-index-urlor multiple registries appear, and every internal package name. Generate SBOMs for main products. See the SBOM Guide. - Stand up or harden the internal proxy (weeks 2 to 8). Configure one virtual repository per ecosystem that serves internal packages and proxies public ones, with rules that refuse public packages whose names match internal ones. Enable logging and malware or reputation checks at the proxy.
- Claim namespaces (weeks 2 to 6). Move private npm packages under the organisation scope and map the scope to the internal registry. For ecosystems without scopes, reserve internal names on the public index or adopt a prefix the proxy routes internally only.
- Commit configuration and pin (weeks 4 to 12). Commit
.npmrcandpip.confor equivalent to each repository. Switch CI fromnpm installtonpm ci; generate hashed, pinned requirements for Python. Pin GitHub Actions to full-length commit SHAs, which GitHub's guidance recommends. - Pilot in audit mode (weeks 6 to 12). On a pilot group of repositories, log fetches that would fail the new rules without blocking. Review the list, add legitimate packages to the curated feed, then switch to enforce. Rollback: point the pilot repositories back at the previous registry configuration, kept in version control.
- Block egress (weeks 10 to 16). Once every repository resolves through the proxy, block runner and build network egress to public registries. This is the control that makes the proxy mandatory rather than advisory.
- Separate install from secrets (weeks 8 to 16). Restructure pipelines so dependency installation runs without secrets or cloud federation permissions; disable install scripts by default with a reviewed allowlist.
- Add a release age delay and monitoring (ongoing). Delay adoption of newly published versions for a few days where tooling supports it, alert on new direct dependencies and maintainer changes, and rehearse the response. See the Supply Chain Compromise Response Playbook.
Decision guidance
- Proxy everything or curate an allowlist: a transparent proxy is quick to deploy and gives logging and name-collision rules; a curated feed of pre-vetted packages, which OpenSSF S2C2F calls enforcing a curated OSS feed (ENF-2), gives stronger assurance but needs an approval workflow. Most organisations start with the proxy and curate high-risk ecosystems.
- Disable install scripts or isolate them: disabling scripts breaks some native packages; where that is unacceptable, run installs in an isolated job with no secrets and no network beyond the proxy.
- Small teams: a single project
.npmrc, lockfiles withnpm ci, organisation scopes and pinned Actions deliver most of the benefit without new infrastructure. - Large estates: enforcement has to be central: proxy, egress rules and policy checks on configuration files, because per-team guidance drifts.
Evidence and assessment
| Evidence item | Source | What good looks like |
|---|---|---|
| Registry configuration per repository | Committed .npmrc, pip.conf, nuget.config and policy check results | All point to the internal repository; no extra public indexes |
| Egress rules for build runners | Firewall or proxy policy | Public registries reachable only from the proxy |
| Lockfile and hash coverage | Repository scan | Every production build uses a lockfile or hashed requirements |
| Namespace ownership | Registry organisation and scope settings | All internal packages scoped or reserved |
| Pipeline secret placement | Workflow definitions | No secrets or OIDC permission in install jobs |
| Proxy logs and alerts | SIEM | Fetches attributable to builds; alerts on blocked collisions and malware hits |
Detection signals: requests for public packages matching internal names; first-seen packages or maintainers; versions published within hours of being pulled; outbound connections or DNS lookups from install steps; new lifecycle scripts in dependency updates; and credential use from runner IP ranges outside expected jobs. In ATT&CK v20 the activity maps to T1195.001 Compromise Software Dependencies and Development Tools, with follow-on T1059 Command and Scripting Interpreter and T1552.001 Credentials In Files.
Metrics:
- Proxy coverage: share of builds resolving all packages through the internal repository.
- Lockfile and hash coverage: share of production repositories using
npm ci, hashed requirements or equivalent. - Namespace protection: internal package names that are scoped or reserved, against the total.
- Secret-free install jobs: share of pipelines whose install step has no secrets or federation permissions.
- Time to block: hours from a public malicious package advisory to the proxy denying it.
Mapping to other frameworks
| Framework | Relevant items | How CICD-SEC-3 maps |
|---|---|---|
| OpenSSF S2C2F | ING-1, ING-2, ING-3, SCA-4, INV-1, AUD-2, AUD-3, ENF-1, ENF-2 | Trusted package managers, binary repository manager, deny list, malware scanning, inventory, auditing the ingestion path, integrity validation, secure package source files, curated feed |
| NIST SP 800-53 Rev. 5 | SR-3, SR-4, SR-5, SR-11, SI-7, SI-7(15), CM-7(5), CM-8 | Supply chain controls, provenance, acquisition, component authenticity, integrity and code authentication, allow-by-exception software, component inventory |
| NIST CSF 2.0 | GV.SC-05, GV.SC-07, ID.AM-02, PR.PS-02, DE.CM-09 | Supply chain requirements and supplier risk, software inventory, software maintenance, monitoring of software and runtime environments |
| CIS Controls v8.1 | 2.5, 16.4, 16.5 | Allowlist authorised software, inventory of third-party components, up-to-date and trusted components |
| ISO/IEC 27001:2022 Annex A | 5.19, 5.21, 8.8, 8.25 | Supplier relationships, ICT supply chain, technical vulnerabilities, secure development life cycle |
| MITRE ATT&CK v20 | T1195.001, T1195.002, T1059, T1552.001 | Compromised dependencies and software supply chain, execution and credential theft that follow |
For provenance and signing, see the SLSA and Build Provenance Guide; for the wider programme, see the Software Supply Chain Security Guide.
Common pitfalls
- Using
--extra-index-urlfor private packages. pip treats both indexes as equal and installs the highest version, the exact behaviour Birsan exploited. - A proxy that is optional. If runners can still reach public registries, one repository with a missing
.npmrcbypasses every control at the proxy. - Running installs in the deploy job. The ChainDrop and Shai-Hulud payloads harvested secrets available at install time; anything in the job environment or runner memory is in scope.
- Treating provenance as a trust verdict. Valid provenance from a hijacked maintainer account still describes a malicious build.
- Vulnerability scanning mistaken for this control. SCA finds known CVEs; it does not stop a brand-new malicious package with no advisory from executing at install.
- Hashing only some requirements. pip's documentation explains that partial hashing is of little use because a malicious dependency can enter through an unhashed line, which is why hash mode is all or nothing.
Practitioner checklist
- Route every package fetch through an internal repository and block runner egress to public registries.
- Commit package manager configuration to every repository and fail builds that resolve elsewhere.
- Move private packages under organisation scopes or reserved names; refuse public packages that collide.
- Use
npm ciwith lockfiles and pip hash-checking mode with pinned versions in CI. - Disable install scripts by default and run installs without secrets or OIDC permissions.
- Pin CI actions and plugins to full-length commit SHAs.
- Move package publishing to trusted publishing and disallow token publishing where supported.
- Delay adoption of freshly published versions where tooling allows.
- Alert on name collisions, first-seen packages and network activity during installs.
- Keep an SBOM per release so exposure can be checked within hours of an advisory.
Standards and references
- OWASP: CICD-SEC-3 Dependency Chain Abuse
- OWASP: Top 10 CI/CD Security Risks
- OpenSSF: Secure Supply Chain Consumption Framework (S2C2F)
- NIST: SP 800-53 Rev. 5
- pip: Secure installs
- npm Docs: npm ci
- npm Docs: Trusted publishing for npm packages
- Alex Birsan: Dependency Confusion
Related NHI Mgmt Group resources: Malicious Package and Dependency Confusion Guide · Top 10 CI/CD Security Risks · OWASP CI/CD Top 10 CICD-SEC-2 Inadequate Identity and Access Management Guide · OWASP CI/CD Top 10 CICD-SEC-9 Improper Artifact Integrity Validation Guide