Introduction
A clean npm audit does not mean your dependencies are safe - it only means they have no known vulnerabilities. Malicious packages often carry no CVE or advisory, making traditional vulnerability scanners ineffective against packages deliberately designed to steal credentials or compromise systems.
That is why npm and PyPI supply chain attacks continue to succeed. Attackers can publish malicious packages or compromise trusted updates that appear legitimate and pass conventional security checks. Once installed, these packages inherit the privileges of the developer workstation or CI/CD environment, giving attackers access to valuable assets such as environment variables, cloud credentials, SSH keys, and CI/CD tokens.
With millions of packages across npm and PyPI, the scale and trust placed in these ecosystems make them attractive targets. This article examines five major attack vectors, where traditional defenses fail, and how engineering and security teams can strengthen their software supply chain.
Key Takeaways
- Standard audit tools do not detect intentionally malicious packages. Tools like npm audit and pip-audit focus on known vulnerabilities, not malware behavior. A newly published malicious package with no CVE or advisory can pass these checks undetected.
- The attack surface is trust, not just code. Typosquatting, dependency confusion, and malicious updates exploit package-manager resolution and implicit trust. Developers and CI pipelines may install packages without verifying their provenance or behavior.
- Registry controls are only one layer of defense. Registry scanning, malware detection, and maintainer 2FA reduce risk, but attackers exploit gaps between these controls. Effective protection combines behavioral analysis, dependency pinning, SBOM governance, and build-environment isolation.
What is npm?
npm (Node Package Manager) is the default package manager for Node.js and the world's largest software registry, hosting more than three million reusable JavaScript packages. It consists of three components: an online registry (registry.npmjs.org) where packages are stored and published, a command-line interface (CLI) to install, update, and manage those packages, and a website (npmjs.com) to search and discover them.
npm comes pre-installed with Node.js, making it the de facto standard for dependency management across frontend, backend, and CI/CD environments. Because every npm install pulls and, when lifecycle scripts are present, automatically executes code from the registry, it is a high-value target for software supply chain attacks including typosquatting, dependency confusion, and malicious lifecycle script abuse.
supply chain attacks such as typosquatting, dependency confusion, and lifecycle script abuse, each covered in detail below.
What is PyPI?
PyPI (the Python Package Index) is the official software repository for the Python programming language and one of the largest open-source package registries in the world, hosting over half a million packages. It is the registry that the pip command-line tool queries by default when a developer runs pip install, serving everything from web frameworks to data science and machine learning libraries. Because pip can execute code during installation through mechanisms such as setup.py install hooks, source distributions, and build backends, a malicious PyPI package can run arbitrary code on a developer's machine or CI runner at the moment it is installed. This makes PyPI, like npm, a high-value target for software supply chain attacks including typosquatting, dependency confusion, and malicious build script abuse.
What is a Software Supply Chain Attack?
A software supply chain attack targets the components, tools, or processes that go into building software, rather than the final application itself. Instead of breaching the organization directly, the attacker compromises something the organisation already trusts and installs voluntarily.
In the context of open-source package registries, the attack surface includes:
- The registry itself (npm, PyPI), which developers trust to host legitimate software
- Individual packages, which developers trust to do what the name and documentation claim
- The package manager resolution algorithm, which developers trust to fetch the right package from the right source
- Lifecycle scripts and build hooks, which developers trust to perform only necessary setup tasks
- The maintainer's publishing credentials, which the registry trusts to authenticate legitimate releases
A successful attack does not need to breach all of these layers. It only needs to compromise one layer that the others implicitly trust.
The Trust Chain: Where Attacks Are Injected
- Public Registry (npm / PyPI)
- Package Resolution Logic
- Developer Workstation / CI Runner
- Lifecycle / Build Script Execution
- Credential and Secret Access
- Network Exfiltration
- Cloud / Source Control Access

Each arrow in this chain represents a trust assumption. Supply chain attacks exploit the assumption that the previous layer has already validated what is being passed down.
How npm and PyPI Supply Chain Attacks Work
1. Typosquatting
Typosquatting involves publishing a package whose name is visually or phonetically close to a popular, trusted package. The goal is to be installed accidentally, either through a mistyped command or a copy-pasted snippet from documentation or a forum post.
On PyPI, where the namespace is dense with data science and ML tooling, this technique is particularly effective. Packages installed via quick terminal commands are at higher risk because a one-character difference may not be noticed until after the install completes.
BASH
# Developer intended
pip install requests-oauthlib
# Attacker registered (lowercase L replaced with numeral 1)
pip install requests-oauth1ibThe malicious package typically copies the legitimate package's metadata almost verbatim to pass a surface-level pip show check. The difference is in the install behavior, not the package description.
2. Staged Payload Delivery
Registry security scanning is more intensive on newly uploaded packages than on incremental version updates, and there is typically a window between a new version being published and a full security review completing. Attackers use a two-stage pattern to exploit this gap:
- Stage 1 (Clean version): The initial version of the malicious package contains functional, legitimate-looking code. It passes registry scanning, accumulates download history, and establishes an appearance of legitimacy.
- Stage 2 (Malicious update): A patch or minor version update introduces the payload. Developers and CI pipelines using semver range resolution (for example, ^1.0.0) pull the update automatically without any explicit action.
By the time the malicious version is detected and removed, the installation has already run across many affected environments.
3. Lifecycle and Build Script Abuse
Both ecosystems have packaging and build mechanisms that can result in package-controlled code executing during installation or build operations. These mechanisms are the most direct execution vectors available to an attacker who successfully publishes a package, though the specifics differ meaningfully between npm and Python packaging.
On npm, package.json supports lifecycle hooks that execute automatically during npm install:
JSON
{
"name": "example-utils",
"version": "1.0.1",
"scripts": {
"postinstall": "node ./scripts/setup.js"
}
}The setup.js payload in documented campaigns typically uses encoding layers (hex over base64, or multiple rounds of string transformation) to obscure the malicious logic from static analysis. At runtime, the decoded script enumerates environment variables matching patterns associated with cloud credentials and API tokens, reads credential files from known paths, and exfiltrates the collected data to an attacker-controlled endpoint via an outbound HTTPS request.
On PyPI, packages distributed as source distributions (sdists) can execute code during installation through a number of mechanisms, including overriding the install command class in setup.py:
PYTHON
from setuptools import setup
from setuptools.command.install import install
import subprocess
class PostInstallHook(install):
def run(self):
subprocess.Popen(
["python", "-c", OBFUSCATED_PAYLOAD],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
install.run(self)
setup(
name="example-data-utils",
cmdclass={"install": PostInstallHook},
...
)It is worth noting that this pattern applies specifically to source distributions being installed in editable mode or without building isolation. Modern Python packaging has moved toward PEP 517-based build isolation and wheel distributions, which reduce, but do not eliminate the risk of code execution during installation. Malicious packages have also been documented using build backend hooks, malicious wheels, and other packaging mechanisms as execution paths. The broader point is that any packaging artifact that can run code during installation is a potential execution vector, regardless of the specific mechanism.
4. Dependency Confusion
Dependency confusion exploits the resolution behavior of package managers when both a private and a public registry are configured. The specific behavior differs between npm and pip, so both deserve separate treatment.
npm dependency confusion
When an organization uses a private registry (such as Artifactory or Verdaccio) alongside the public npm registry, the resolution behavior depends on .npmrc configuration. Without explicit scope-to-registry binding, an attacker who knows the name of an internal package can publish a public package with the same name at a higher version number. Depending on the configuration, npm may resolve the public version.
The fix requires explicit binding in .npmrc:
INI
# Bind the internal scope to the private registry only
@your-org:registry=https://your-private-registry.company.com
# Block public registry fallback for this scope
//your-private-registry.company.com/:_authToken=${NPM_TOKEN}Without this explicit binding, the resolver can select the attacker-controlled package without requiring the developer to explicitly approve it.
Python dependency confusion
In pip, the relevant risk arises from combining --extra-index-url with --index-url. When both are configured, pip may install the highest version found across all configured indexes. An attacker who publishes a package with the same name as an internal package at a higher version number can exploit this behavior.
BASH
# Risky configuration: pip checks both indexes and may install from either
pip install internal-package \
--index-url https://private.company.com/simple \
--extra-index-url https://pypi.org/simple
# Safer: use --index-url alone and mirror what you need through the private registry
pip install internal-package \
--index-url https://private.company.com/simple The safer pattern routes all install through a private registry mirror that the team controls, eliminating the public registry as a resolution source for internal packages. Package resolution behavior can vary depending on pip version, resolver configuration, and other factors, so it is worth testing the specific behavior in your environment.
5. Maintainer Account Takeover
A less visible but well-documented vector is a direct compromise of a legitimate package maintainer's account. Phishing, credential stuffing against reused passwords, and session token theft have all been used to gain publishing access to established, widely trusted packages.
This is a particularly high-impact vector because the attacker does not need to convince anyone to install an unfamiliar package. They publish a malicious version of a package that developers already trust and have already pinned in their dependency trees.
Registry controls including mandatory two-factor authentication for high-impact maintainers and granular token permission scopes directly address this vector.
Attack Chain: From Installation to Credential Theft
The following table maps each stage of a successful supply chain attack against the detection opportunity available at that stage. This framing is useful for evaluating where your current controls actually sit.
Documented campaigns have consistently shown how gaps at the installation, enumeration, and exfiltration stages allow malicious packages to complete the full attack chain undetected.

Why Traditional Dependency Security Controls Miss Malicious Packages
npm audit and pip-audit Are Known-Vulnerability Scanners
This is the most widely misunderstood gap in standard open-source security practices.
npm audit queries the npm registry for known security vulnerabilities associated with project dependencies. pip-audit performs a similar function by checking against vulnerability databases including OSV and PyPA advisory records. Both tools are valuable for identifying known CVEs in dependencies.
Neither is designed to determine whether a newly published package is intentionally malicious, or whether that package's installation scripts perform harmful behavior at runtime.
A malicious package that was published this week, has no associated CVE, and has not yet been flagged by a registry security team may pass both tools without producing a single warning:
BASH
$ npm audit
found 0 vulnerabilities
# The postinstall script ran during the install command above.
# Your CI tokens are already in the attacker's hands.The zero-vulnerability result is technically accurate. There are no known vulnerabilities in the installed packages. That is a different question from whether those packages are safe.
Behavioral analysis tools that inspect what a package does during installation, such as scanning for network calls, environment variable access, and obfuscated code patterns, are needed to address what audit tools are not designed to catch.
Semver Range Resolution Creates Silent Auto-Updates
The convention of specifying dependencies with ^ or ~ prefixes is designed for developer convenience: bug fixes and non-breaking updates are pulled automatically. In a supply chain attack, this means a malicious patch version is automatically resolved and installed across every project using that range, with no explicit developer action required.
JSON
"dependencies": {
"some-utility": "^2.4.1"
} f the attacker publishes some-utility@2.4.2 with a malicious payload, a project using ^2.4.1 can resolve the new version on a subsequent dependency installation when the lockfile or dependency state allows the update. Without lockfile enforcement or dependency pinning, this can happen silently across many pipelines.
Lockfile Drift Is Rarely Monitored
Many teams commit package-lock.json or poetry.lock to version control but do not configure alerts for unexpected changes to these files. An unexplained addition to the dependency graph, especially a new transitive dependency introduced by a minor version update, is one of the higher-signal indicators of a supply chain compromise. It goes unreviewed in most CI pipelines.
Registry Scanning Has Coverage Gaps
Registry-side scanning can miss malicious behavior introduced through newly published versions, particularly when the malicious logic is obfuscated, staged to activate only after installation, or relies on dynamic payload retrieval rather than static embedded code. Scanning on incremental version updates may also be less intensive than the review applied to entirely new package submissions, creating a window that attackers have used deliberately in documented campaigns.
Trust Boundaries: Why Registry Security Alone Is Not Enough
A useful way to think about supply chain defense is in terms of trust boundaries rather than individual controls.
The registry is only one trust boundary. A secure registry cannot compensate for unrestricted egress from a build runner, weak dependency resolution configuration, or secrets stored as plaintext environment variables with no access controls. Each trust boundary requires its own control.
Attackers who understand this architecture design their campaigns to operate in the gaps. Staged delivery targets the gap between registry publication and registry scanning. Postinstall scripts target the gap between installation completion and process monitoring. Dependency confusion targets the gap between resolution configuration and scope binding.
Valid provenance on a package confirms that it was built from a specific source in a specific build environment. It does not confirm that the source code is safe or that the maintainer's account was not compromised. PyPI's own documentation on Trusted Publishers explicitly notes that the feature does not assert the safety of the code or the trustworthiness of its authors. Provenance narrows one attack surface. It does not close the others.

How Modern Supply Chain Security Controls Reduce Risk
Behavioral Package Analysis
Static CVE checking needs to be complemented by tools that analyze what a package actually does before it runs. Behavioral analysis approaches flag patterns including:
- Network requests made during package installation
- Access to environment variables, credential files, or SSH key paths within lifecycle scripts
- High-entropy strings that may indicate obfuscated payloads
- Sudden changes in package maintainer identity or metadata
- New lifecycle scripts introduced in a version update where none previously existed
Tools in this category (including Socket.dev, Phylum, and Snyk Open Source, among others) can integrate into CI pipelines as a gate before installation proceeds. The category and capability matter more than any specific vendor choice.
Dependency Pinning and Lockfile Governance
Replacing semver ranges with exact version pins in production manifests eliminates the auto-update vector. Using lockfiles committed to version control and treated as security artifacts (not just build convenience files) establishes a baseline that can be diffed for unexpected changes.
BASH
# npm: enforce lockfile integrity in CI
npm ci # fails if package-lock.json does not match package.json
# pip: require hash verification
pip install --require-hashes -r requirements.txtConfiguring CI to block on any unexpected lockfile diff requires one additional pipeline step. It surfaces dependency graph changes that would otherwise go unreviewed.
Lifecycle Script Controls
npm provides a flag that prevents lifecycle scripts from running during installation. In CI pipelines where package vetting happens separately from installation, this flag is worth evaluating:
BASH
npm install --ignore-scriptsThis is not a universally appropriate default. Some legitimate packages require lifecycle scripts for compilation steps, native module builds, or platform-specific setup. The practical approach is to disable lifecycle scripts where operationally feasible, then explicitly allow packages that have been reviewed and confirmed to require them. That is a more defensible posture than running all scripts by default.
Package Provenance and Sigstore Verification
PyPI's Trusted Publishers feature allows maintainers to bind their releases to specific GitHub Actions workflows via OIDC. A release from a Trusted Publisher carries cryptographic provenance: you can verify it was built from a specific commit in a specific repository by a specific workflow.
npm introduced provenance attestations in npm 9.5+. When a package carries a provenance record, the build environment and source commit that produced it are verifiable.
BASH
# Verify npm provenance
npm audit signatures some-package@1.2.3
# Verify PyPI attestations using the sigstore CLI
pip install sigstore
python -m sigstore verify --cert-identity <publisher-identity> package.whlTwo important caveats: neither mechanism is widely adopted across the full dependency ecosystem yet, and as noted above, valid provenance confirms origin rather than safety. For critical dependencies, provenance verification is a meaningful additional control. It should not be treated as a replacement for behavioral analysis.
Using SBOMs for Software Supply Chain Security
A Software Bill of Materials (SBOM) is a machine-readable inventory of every component in a software artifact, including transitive dependencies. Generating an SBOM on every build and diffing it against the previous baseline surfaces:
- New direct dependencies added without a corresponding code change
- New transitive dependencies introduced by a dependency update
- Version changes anywhere in the dependency tree
Tools such as Syft (from Anchore) and CycloneDX-compatible generators integrate into most CI systems and produce output in SPDX or CycloneDX formats compatible with vulnerability and policy management tooling. Treating unexpected SBOM diffs as a blocking CI condition gives security teams dependency graph visibility that no manual review process can match at scale.
The SLSA Framework as a Baseline for Build Integrity
SLSA (Supply-chain Levels for Software Artifacts) is a security framework developed by Google that defines progressively stronger requirements for build integrity and provenance. At higher SLSA levels, build artifacts are cryptographically linked to their source code and build process, and provenance is generated by the build infrastructure itself rather than by the build script (which could itself be compromised).
SLSA provides a useful framework for increasing supply-chain assurance through provenance, build integrity requirements, and stronger controls around the build process. Like all provenance mechanisms, it narrows the attack surface meaningfully but should be understood as one layer in a broader defense strategy.
For practical guidance on SLSA levels and implementation, the official SLSA specification at slsa.dev is the authoritative reference.
The Ecosystem Response: What Registries Have Done
Registry operators have invested significantly in supply chain security controls. The following reflects publicly documented and announced improvements from npm and PyPI.
npm (via GitHub/Microsoft) has introduced provenance attestation support allowing CI-generated packages to carry verifiable build signatures, expanded automated malware scanning, and has worked toward stronger two-factor authentication requirements for maintainers of widely depended-upon packages. A more granular token permission system reduces the blast radius of a compromised maintainer credential.
PyPI has rolled out Trusted Publishers, which bind releases to specific CI/CD workflows via OIDC. PEP 740 attestations provide a standard for attaching digital signatures to uploaded distributions. PyPI's malware detection and reporting processes are documented in their official security policies.
These represent real improvements. As this analysis of documented attack patterns shows, attackers have also adapted, operating in the gaps between registry-level controls and the installation environment. Both registries encourage reviewing their current security documentation and advisories for the authoritative account of what controls are in place.

Practical Hardening Checklist
Registry and resolution configuration
- Bind all internal package scopes to the private registry explicitly in .npmrc or pip.conf
- Route all installs through a private registry mirror for internal packages; avoid relying on public registry fallback
- Avoid combining-extra-index-url with-index-url in pip for internal packages unless the resolution behavior has been explicitly tested and confirmed
- Configure the registry mirror to block packages flagged by upstream security feeds
Dependency pinning
- Replace semver ranges with exact version pins in production manifests
- Commit and review lockfiles on every pull request
- Treat unexpected lockfile diffs as a security event requiring review before merging
- Use npm ci and pip install-require-hashes in CI to enforce lockfile integrity
Install-time controls
- Evaluate-ignore-scripts for npm installs in CI; maintain an explicit allowlist of packages that require lifecycle scripts
- Audit and periodically review all packages on that allowlist
- Restrict build environment egress to an explicit network allowlist; alert on connections outside the allowlist
Scanning and analysis
- Integrate a behavioral package analysis tool as a CI gate before installation
- Generate and diff SBOMs on every build; block on unexpected dependency graph changes
- Set up alerts on transitive dependency additions that have not been explicitly reviewed
Monitoring
- Monitor build agents for unexpected outbound connections
- Rotate all secrets stored as CI environment variables on a regular schedule and audit access logs
- Review CI workflow files for unexpected modifications, particularly around dependency installation steps

Detection and Monitoring: Closing the Visibility Gap
Detection requires instrumentation at the same stages where attacks operate.
- Dependency resolution: Lockfile diff alerts catch dependency graph changes before installation runs. An SBOM baseline comparison on every CI run catches both direct and transitive dependency additions.
- At installation time: CI pipeline logging of the full install command output, combined with behavioral analysis tooling, surfaces suspicious patterns before the build artifact is produced.
- At execution time in the build environment: Process monitoring for unexpected child processes spawned during npm install or pip install, combined with egress restriction and alerting, catches active exfiltration attempts even when a malicious package has slipped through earlier controls.
- Post-installation: Monitoring for access to known credential file paths (~/.npmrc, ~/.pypirc, ~/.aws/credentials, ~/.ssh/) within build runners, and alerting on reads from those paths by unexpected processes, adds a final detection layer.
- At the secret level: Rotating CI secrets regularly and auditing access logs for anomalous usage of tokens or credentials reduces the window of opportunity even after a successful exfiltration.
No single control is sufficient. The attack chain spans multiple stages, and a defense that closes only one stage may still allow the others to succeed.
Conclusion
The attack patterns covered in this article, including typosquatting, dependency confusion, and lifecycle script abuse, persist not because of technical novelty but because of structural trust built into package manager behavior, CI defaults, and developer workflows. The controls to reduce this risk, including behavioral analysis, dependency pinning, SBOM governance, egress restriction, and provenance verification, are mature and available today. The gap is adoption, not tooling. Every dependency that executes code on install is a trust decision, and organizations that treat it as one are building pipelines that supply chain attacks have a significantly harder time crossing.
FAQs
Q1. What is a software supply chain attack?
A software supply chain attack targets the components, tools, or processes that developers rely on to build software, rather than the final application itself. Attackers may compromise package registries, maintainer accounts, or build pipelines to inject malicious code into widely used open-source libraries. Unlike direct application exploits, supply chain attacks can affect many downstream organizations through a single compromised dependency.
Q2. How do attackers get malicious packages onto npm or PyPI?
Commonly documented vectors include typosquatting (publishing packages with names visually similar to popular ones), dependency confusion (exploiting how package managers resolve private and public registries), maintainer account takeover via phishing or credential stuffing, and staged delivery where an initial clean version is followed by a malicious update. Each vector exploits a different trust boundary in the open-source ecosystem.
Q3. Why do npm audit and pip-audit miss many malicious packages?
npm audit and pip-audit are designed to report known vulnerabilities associated with project dependencies. They are not malware behavior scanners. A newly published malicious package may have no associated vulnerability advisory, which means these tools may return no issues even after installation of a package that actively exfiltrates credentials. Behavioral analysis tools that inspect what a package does during installation are needed to complement standard audit workflows.
Q4. What is the dependency of confusion and how does it work?
Dependency confusion exploits the way package managers resolve packages when both a private and a public registry are configured. In npm, without explicit scope-to-registry binding, some configurations may resolve a public package over a private one of the same name if the public version number is higher. In Python, combining --extra-index-url with --index-url can create similar risks. Explicit registry binding and private registry mirroring are the primary mitigations.
Q5. What is the SLSA framework and how does it help with supply chain security?
SLSA (Supply-chain Levels for Software Artifacts) is a security framework defining progressively stronger requirements for buildinging integrity and provenance. At higher levels, build artifacts are cryptographically linked to their source and build process, making it significantly more difficult to inject malicious code without detection. SLSA increases assurance meaningfully but should be understood as one layer in a broader defense strategy.
Q6. How can development teams detect malicious packages before installation?
Effective strategies include using behavioral package analysis tools that inspect what a package does during installation, verifying package provenance where supported via Sigstore or PyPI Trusted Publishers, pinning dependencies to exact versions, generating SBOMs and diffing them against baselines on every build, and monitoring build environments for unexpected outbound network connections.
Q7. Can npm audit detect malicious packages?
Not reliably. npm audit is designed to surface known security vulnerabilities associated with installed dependencies by querying advisory databases. A malicious package that was published recently and has no associated advisory will pass npm audit without producing any warnings. This is not a flaw in the tool; it is a scope limitation. npm audit answers the question "does this dependency have a known CVE?" It does not answer the question "is this package trying to steal my credentials?" Behavioral analysis tools are needed to address the latter.
Q8. How can organizations prevent dependency confusion attacks?
The most reliable control for npm is explicit scope-to-registry binding in .npmrc, which routes a given package scope exclusively to the private registry and prevents the public registry from being used as a fallback for that scope. For Python, the safer pattern is to avoid combining --extra-index-url with --index-url for internal packages and instead route all installs through a private registry mirror that proxies only approved external packages. Naming all internal packages under a unique organizational prefix also reduces the chance of an attacker registering a matching public name, though it does not eliminate the risk on its own.

Faster Than the Fix: Six KEV Additions, Cross-Platform Exploitation, and the Rise of AI-Driven Attacks
Explore the key security, speed, and performance differences between TLS 1.3 and TLS 1.2
Ready to Find and Fix Your Security Weak Points?
LoginSoft's cybersecurity experts help organizations conduct thorough gap analyses, build prioritized remediation roadmaps, and achieve measurable security maturity improvements.
Schedule a Security Assessment
Hari Charan
A MESSAGE FROM OUR TECHNOLOGY LEADER
The NVD enrichment cutback is not a surprise to us - it’s the inflection point we’ve been preparing for. At Loginsoft, we’ve spent years building the research depth and tooling infrastructure to independently enrich vulnerabilities at scale, with the accuracy and context modern security programs require. LOVI is our answer. Our mission is simple: ensure that no CVE relevant to your environment goes unanalyzed, unscored, or unactioned - regardless of what remains in NIST’s queue.
Get Notified
BLOGS AND RESOURCES


