Skip to main content
176 Malicious npm Packages Hijacked CI/CD PipelinesIncident
4 min readFor Security Engineers

176 Malicious npm Packages Hijacked CI/CD Pipelines

What Happened

Sonatype researchers discovered 176 malicious npm packages exploiting dependency confusion to compromise developer environments and CI/CD systems. Attackers published packages with names matching common internal dependencies but assigned them high version numbers, like 99.99.99, to ensure selection over legitimate internal packages during installation.

Each package contained postinstall scripts designed to exfiltrate CI/CD secrets and environment variables, targeting both Windows and Unix-based build systems.

Timeline

While exact dates weren't specified, the campaign's structure suggests a coordinated effort:

  • Initial publication: Attackers uploaded packages with high version numbers to the public npm registry.
  • Installation trigger: Developers or CI/CD systems running npm install without strict scoping pulled the malicious packages.
  • Execution: Postinstall scripts ran automatically, harvesting credentials and environment data.
  • Exfiltration: Stolen data was transmitted to attacker-controlled infrastructure.
  • Detection: Sonatype's automated monitoring flagged the unusual version patterns and malicious behavior.

Which Controls Failed or Were Missing

1. Package Source Prioritization

Organizations failed to configure npm to prioritize internal registries over public ones. Without explicit scoping, a public package at version 99.99.99 will always win over your internal [email protected].

2. Pre-Installation Security Validation

No security checks ran before package installation. The postinstall scripts executed with the same privileges as the build process, often accessing AWS credentials, GitHub tokens, and deployment keys stored in environment variables.

3. Runtime Execution Controls

Build environments lacked restrictions on package capabilities. Legitimate packages might need to compile native modules, but shouldn't require network access to arbitrary external domains or unrestricted access to environment variables.

4. Dependency Integrity Verification

Teams weren't validating package integrity through lock files or checksum verification, allowing substitution attacks where unintended packages could be installed without triggering alerts.

What the Relevant Standards Require

PCI DSS v4.0.1

Requirement 6.3.2 mandates that custom software be developed based on industry standards for secure development. This includes:

  • Verifying the integrity of all components before installation.
  • Restricting automated processes from accessing production credentials.
  • Maintaining an inventory of all third-party components.

NIST 800-53 Rev 5

SA-12 (Supply Chain Protection) requires mechanisms to limit harm from potential adversaries, establish security requirements for developers, and conduct independent verification of security functions.

CM-7 (Least Functionality) demands systems provide only essential capabilities, directly applying to build environments that shouldn't have unrestricted internet access or data exfiltration abilities.

ISO/IEC 27001:2022

Annex A 8.30 (Outsourced development) requires supervision and monitoring of outsourced system development, including third-party code and dependencies.

Annex A 8.31 (Separation of development, test and production environments) mandates separation to reduce unauthorized access or changes, limiting the impact of compromised build systems.

Lessons and Action Items for Your Team

Immediate Actions (This Week)

Configure package scoping in your .npmrc files:

@yourcompany:registry=https://npm.internal.yourcompany.com/
//npm.internal.yourcompany.com/:_authToken=${NPM_TOKEN}

This ensures npm pulls @yourcompany/* packages from your internal registry, regardless of external version numbers.

Audit your CI/CD environment variables. Remove unnecessary credentials and scope remaining ones to specific jobs.

Enable npm audit in your pipeline:

npm audit --audit-level=moderate

Set this to fail your build if vulnerabilities are detected.

Short-Term (This Month)

Implement Sigstore verification for critical dependencies. Configure your package manager to reject unsigned packages for sensitive projects.

Deploy a package firewall. Use tools like Sonatype Nexus or JFrog Artifactory to proxy the public npm registry and apply policies before packages reach developers. Block suspicious version patterns, quarantine new packages for review, and enforce license compliance.

Restrict network egress from build environments. Limit outbound connections to known-good destinations: your package registries, deployment targets, and essential services.

Medium-Term (This Quarter)

Adopt a Software Bill of Materials (SBOM) practice. Generate an SBOM for every build using tools like syft or cyclonedx-cli. Store these with your artifacts to quickly assess impact during a supply chain incident.

Implement runtime application self-protection (RASP) for build processes. Monitor installed packages during installation. If a postinstall script attempts unexpected actions, terminate the process and alert your security team.

Move to a zero-trust model for CI/CD credentials. Use short-lived tokens from your identity provider instead of long-lived secrets in environment variables. AWS supports OIDC federation with GitHub Actions, GitLab CI, and other platforms, allowing builds to authenticate directly without stored credentials.

The Core Principle

Dependency confusion exploits the trust in semantic versioning. Your defense is to explicitly encode trust: specific registries for packages, signatures for artifacts, and capabilities for processes.

The 176-package campaign succeeded by exploiting the gap between developers' trust in dependencies and attackers' view of them as execution vectors. Close that gap.

Topics:Incident

You Might Also Like