DEV Community

RC
RC

Posted on • Originally published at randomchaos.us

Malicious axios Update Exploits Dependency Trust Model

axios v1.7.5 - Anatomy of a Supply Chain Kill Chain

On March 28, 2024, axios v1.7.5 was published to npm under valid maintainer credentials. The source code was untouched. The payload was in package.json - a devDependency pointing to an external module with no version constraint. Fifty million weekly downloads. One poisoned transitive dependency. That is the blast radius.

How you poison a trusted package

The attack requires no source code modification. No pull request. No code review bypass.

You compromise or coerce valid publishing credentials. You add a single line to package.json - a devDependency referencing a module you control. No version pin. The module resolves at install time from the public registry, executing code through the package installation lifecycle before the consumer's application ever runs.

The original package checksums remain valid. Lockfile integrity checks pass for direct dependencies. The poisoned dependency is transitive - it exists one level below the verification boundary that npm, yarn, and most CI pipelines enforce.

This is not a vulnerability in axios. It is an exploitation of the npm trust model: publish credentials equal package integrity, and integrity verification does not extend to transitive resolution.

What sits in your node_modules

The malicious module executed during installation. The specific execution mechanism - postinstall hook, dynamic require, or runtime code loading - is not confirmed in available reporting.

What is confirmed: the module had access to the installation environment. In a standard Node.js context, that means process.env, filesystem access scoped to user permissions, and network egress unless explicitly blocked.

Whether data exfiltration occurred is not confirmed. Whether persistence was established is not confirmed. Whether a C2 channel was opened is not confirmed. The behavioral intent of the payload remains unverified.

Absence of confirmation is not absence of impact. It means the blast radius is unknown and must be treated as the worst credible case until scoped.

60-second dependency audit

Before your next build pipeline runs:

1. Check if you consumed the poisoned version.
Search your lockfiles - package-lock.json, yarn.lock, pnpm-lock.yaml - for axios 1.7.5. Check CI build logs and container image layers for the same.

2. Inspect transitive dependencies for recent mutations.
Any dependency added or version-bumped in the last 30 days without a corresponding changelog entry is a candidate for review. Focus on packages with install scripts.

3. Audit install-time execution.
Run npm ls to enumerate the full dependency tree. Flag any package with a preinstall, install, or postinstall script using npm explain <package> and inspect the script content.

4. Verify environment variable exposure.
If your build pipeline injects secrets via process.env - API keys, database credentials, CI tokens - assume they were accessible to any code that executed during npm install. Rotate them.

5. Enforce transitive integrity.
Lockfiles verify checksums for direct dependencies. They do not prevent a direct dependency from pulling new transitive packages at resolution time if the constraint is open. Pin constraints. Audit what resolves.

What this means

The npm trust model treats valid credentials as a proxy for package integrity. That assumption failed here. No source code was compromised. No review process was bypassed. The payload entered through a path that existing controls do not cover: transitive dependency resolution under open version constraints, authenticated by inherited trust from the parent package.

Static analysis tools - npm audit, Snyk, Dependabot - are signature-based. Their detection coverage for dynamically loaded, obfuscated payloads introduced through transitive paths is not confirmed for this specific incident.

This is not an axios problem. Every package with open dependency constraints and install-time execution is the same attack surface. The control gap is structural. It exists across the npm ecosystem wherever transitive integrity is assumed but not enforced.

Top comments (0)