Supply Chain Security

Package Manager Security: npm, pip, and Maven Compared

Each package manager has its own security model, attack surface, and best practices. This guide compares npm, pip, and Maven from a supply chain security perspective.

Yukti Singhal
Security Research Lead
8 min read

Package managers are the plumbing of modern software development. They resolve dependencies, download code from public registries, and execute install scripts -- all with a level of implicit trust that should terrify anyone thinking about supply chain security. But not all package managers are created equal. Their security models, attack surfaces, and available mitigations differ significantly.

This guide compares the three most widely used package ecosystems from a security perspective: npm (JavaScript/Node.js), pip (Python), and Maven (Java/JVM).

The Trust Model Problem

Every package manager operates on a fundamental trust assumption: the code you download from the registry is what you intended to install, and it is safe to execute. This assumption breaks down in multiple ways, and each ecosystem handles the failure modes differently.

The core attack vectors across all package managers include:

  • Typosquatting: Publishing packages with names similar to popular libraries
  • Dependency confusion: Exploiting the resolution order between public and private registries
  • Account takeover: Compromising a maintainer's account to publish malicious updates
  • Malicious install scripts: Running arbitrary code during package installation
  • Transitive dependency attacks: Compromising a deep transitive dependency

How each ecosystem mitigates (or fails to mitigate) these attacks is where the differences matter.

npm (JavaScript/Node.js)

Registry: npmjs.com

npm is the largest package registry in the world, with over 2 million packages. The JavaScript ecosystem's culture of small, single-purpose modules means that a typical application can have hundreds or thousands of transitive dependencies. This massively expands the attack surface.

Security Model

Lock files: package-lock.json pins exact dependency versions and includes integrity hashes (SHA-512). When present, npm ci will only install the exact versions specified in the lock file and verify integrity hashes. This provides strong reproducibility guarantees.

Install scripts: npm packages can define preinstall, install, and postinstall scripts that execute arbitrary code during installation. This is the single largest attack surface in the npm ecosystem. Malicious packages frequently use install scripts to exfiltrate environment variables, install backdoors, or download second-stage payloads.

You can disable install scripts globally with npm install --ignore-scripts, but many legitimate packages depend on them (native module compilation, for example). The --ignore-scripts flag breaks too many packages to be used universally.

Two-factor authentication: npm supports 2FA for publishing, and since 2022, requires it for high-impact packages (those with more than 1 million weekly downloads or 500+ dependents). This significantly reduces account takeover risk for popular packages.

Provenance: npm introduced package provenance in 2023, linking published packages to their source repository and build environment via Sigstore. Packages built with provenance show a verified badge on npmjs.com. This helps verify that a package was built from the source code it claims.

npm audit: Built-in vulnerability scanning that checks your dependency tree against the GitHub Advisory Database. It is convenient but only catches known CVEs, not malicious packages or zero-day supply chain attacks.

Key Risks

The sheer size of dependency trees in JavaScript applications is the primary risk multiplier. It is not unusual for a React application to have 1,500+ transitive dependencies. Each one is an entity you are trusting to not be compromised.

The culture of micro-packages (left-pad being the canonical example) means that critical functionality often depends on packages maintained by a single individual with no organizational backing. The colors.js / faker.js incident demonstrated how maintainer burnout can directly impact downstream applications.

pip (Python)

Registry: PyPI (Python Package Index)

PyPI hosts over 400,000 packages. The Python ecosystem tends toward larger, more comprehensive libraries compared to npm, which somewhat limits transitive dependency depth, but the security model has historically been weaker.

Security Model

Lock files: pip does not have a native lock file format. requirements.txt can pin versions but does not include integrity hashes by default. You can add hashes manually with --require-hashes, but this is uncommon in practice.

Tools like pip-tools (which generates requirements.txt with hashes from a requirements.in file), pipenv (with Pipfile.lock), and poetry (with poetry.lock) add lock file functionality, but there is no ecosystem-wide standard. This fragmentation means many Python projects do not have reproducible dependency resolution.

Install scripts: Python packages using setup.py execute arbitrary Python code during installation. This is the equivalent of npm's install scripts and carries the same risks. The newer pyproject.toml and wheel-based installation avoid executing setup.py, but many packages still rely on source distributions.

Two-factor authentication: PyPI supports 2FA and has been progressively requiring it for critical projects. As of 2023, all projects with a significant download count require 2FA for maintainers.

Trusted Publishers: PyPI introduced Trusted Publishers in 2023, allowing packages to be published directly from CI/CD platforms (GitHub Actions, GitLab CI) without using API tokens. This reduces the risk of token theft and provides a verifiable link between the package and its source repository.

Namespace squatting: Unlike npm, PyPI package names are globally unique with no scope mechanism. There is no @organization/package pattern. This makes typosquatting and namespace confusion more viable attacks.

Key Risks

The lack of a universal lock file standard is Python's biggest supply chain security gap. Without integrity hashes, there is no cryptographic guarantee that the package you install today is the same one you installed yesterday, even if the version number has not changed (a re-upload scenario).

The setup.py execution model also means that simply installing a package to evaluate it can compromise your system. This is particularly dangerous in CI/CD environments where pip install commands run with elevated privileges.

Maven (Java/JVM)

Registry: Maven Central

Maven Central hosts over 500,000 artifacts. The Java ecosystem's enterprise heritage means that the security model is more mature in some ways, but it also has unique risks.

Security Model

Lock files: Maven does not use lock files in the traditional sense. Dependencies are specified in pom.xml with version numbers, and Maven resolves the dependency tree at build time. The Maven Dependency Plugin can produce a resolved dependency tree, and the Maven Wrapper can lock the Maven version itself, but there is no built-in hash verification for downloaded artifacts.

Gradle (the other major JVM build tool) added dependency verification in version 6.2, which supports SHA-256 and PGP signature verification. If you are on the JVM, Gradle's verification is significantly stronger than Maven's default behavior.

PGP signatures: Maven Central requires all published artifacts to be signed with PGP keys. This is a stronger guarantee than what npm or PyPI provide by default, but it only verifies that the artifact was published by the key owner, not that the key owner is trustworthy or that the source code matches.

No install scripts: Maven and Gradle do not execute arbitrary code during dependency resolution. Build plugins can execute code during the build lifecycle, but this is explicitly configured in the build file and visible to code review. This is a significant security advantage over npm and pip.

Namespace model: Maven uses a group ID system (typically reverse domain names like org.apache.commons) that provides organizational namespacing. This makes typosquatting harder because the attacker would need to register a confusingly similar group ID. However, dependency confusion attacks are still possible when organizations use private Maven repositories alongside Maven Central.

Reproducible builds: The Java ecosystem has strong support for reproducible builds through the Reproducible Build Maven Plugin and Gradle's built-in reproducible archive support. This allows verification that a published artifact was built from a specific source commit.

Key Risks

Maven's biggest supply chain risk is the dependency resolution model. Maven uses a "nearest wins" strategy for version conflicts, which can lead to unexpected version selection. An attacker who can introduce a malicious dependency at a nearer point in the dependency tree can shadow a legitimate dependency.

The Java ecosystem also has a history of complex transitive dependency trees, though not as extreme as npm. A typical Spring Boot application may have 200-400 transitive dependencies.

Cross-Ecosystem Comparison

| Feature | npm | pip | Maven | |---------|-----|-----|-------| | Lock file | Yes (package-lock.json) | Third-party (poetry, pipenv) | Partial (Gradle verification) | | Integrity hashes | Yes (SHA-512) | Optional (--require-hashes) | PGP signatures | | Install scripts | Yes (major risk) | Yes (setup.py, major risk) | No (significant advantage) | | 2FA for publishing | Required for popular pkgs | Required for critical pkgs | PGP key required | | Namespace scoping | Yes (@scope/pkg) | No | Yes (groupId) | | Provenance | Sigstore (2023+) | Trusted Publishers (2023+) | PGP signatures |

Practical Recommendations

  1. Always use lock files with integrity verification. For pip, adopt poetry or pip-tools.
  2. Audit install scripts before installing new packages. In npm, use npm pack to inspect the package contents.
  3. Use private registries with upstream caching (Artifactory, Nexus) to control what packages are available to your builds.
  4. Enable 2FA on all package registry accounts. No exceptions.
  5. Pin transitive dependencies and review updates before adopting them.
  6. Monitor for typosquatting by maintaining an allowlist of approved packages.

How Safeguard.sh Helps

Safeguard.sh analyzes SBOMs from all three ecosystems and correlates dependencies against vulnerability databases, license restrictions, and known-malicious package lists. It normalizes package identifiers across ecosystems using Package URLs (purls), giving you a consistent view of your supply chain risk regardless of whether your application is built with npm, pip, Maven, or a combination.

The policy gate feature lets you enforce security standards across ecosystems -- block packages with known vulnerabilities, flag packages without provenance, and require lock file integrity verification -- all from a single platform.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.