DEV Community

Toni Antunovic
Toni Antunovic

Posted on • Originally published at lucidshark.com

npm Provenance and SLSA: The Supply Chain Hygiene Baseline Every Team Needs in 2026

This article was originally published on LucidShark Blog.


On March 31, 2026, threat actors compromised the npm account of an axios maintainer and published two backdoored versions: axios@1.14.1 and axios@0.30.4. With roughly 83 million weekly downloads and over 2 million dependent packages, it became one of the most consequential supply chain attacks ever recorded against the JavaScript ecosystem.

Here is the part that stings: the axios project had done almost everything right. They had npm OIDC Trusted Publishing configured. They had SLSA provenance attestations on their releases. And none of it mattered, because a legacy "classic" npm token was still active in the environment.

What Actually Happened and Why Provenance Did Not Save Them

npm's OIDC Trusted Publishing works by linking package publication to a specific GitHub Actions workflow run. When you publish with provenance enabled, the registry records a cryptographic attestation: this version of this package was built by this workflow at this commit hash. Consumers can verify the attestation before installing.

The critical flaw is in npm's authentication hierarchy. When both a classic token and OIDC credentials exist for an account, the classic token takes precedence. The attackers did not need to compromise the CI/CD pipeline, defeat the OIDC trust model, or forge attestations. They found and used a legacy token that bypassed all of it.

⚠️ If your npm account has both OIDC Trusted Publishing configured AND a classic token still active, the classic token can override all provenance controls. Audit your tokens now at npmjs.com/settings/tokens.

The SLSA Framework: What Levels Actually Mean

SLSA (Supply chain Levels for Software Artifacts) defines four levels of supply chain security:

  • SLSA 0: No guarantees. Most npm packages today.
  • SLSA 1: Build process documented and scripted. Provenance exists but not authenticated.
  • SLSA 2: Build on hosted CI with authenticated provenance. npm registry supports this natively. This is where you need to be.
  • SLSA 3: Build platform itself is hardened. Achievable but requires deliberate effort.

For most teams publishing to npm, SLSA 2 is the realistic near-term target. It requires running builds on a hosted CI platform (GitHub Actions, GitLab CI, etc.) with provenance generation enabled, and publishing via OIDC rather than classic tokens.

Hardening Your npm Publish Pipeline: The Concrete Steps

Step 1: Enable OIDC Trusted Publishing and Remove Classic Tokens

# .github/workflows/publish.yml
permissions:
  contents: read
  id-token: write  # Required for OIDC provenance

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - run: npm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}  # Use OIDC, not this secret
Enter fullscreen mode Exit fullscreen mode

After enabling OIDC, go to npmjs.com/settings/tokens and revoke all classic publish tokens. Remove NPM_TOKEN from your GitHub secrets. If you need a token for read operations, create a read-only granular access token.

Step 2: Verify Provenance on Install

npm audit signatures
Enter fullscreen mode Exit fullscreen mode

This command verifies that every package in your node_modules has a valid cryptographic signature from the registry. Add it as a required, blocking step in your CI pipeline. It adds less than 10 seconds to most installs and catches packages published without provenance.

Step 3: Lock Your Dependency Graph

npm ci --ignore-scripts
Enter fullscreen mode Exit fullscreen mode

Use npm ci instead of npm install in all CI and production environments. It enforces the lockfile exactly, fails on any deviation, and is faster. The --ignore-scripts flag prevents postinstall hooks from running, which would have prevented the WAVESHAPER.V2 payload from executing in the axios attack.

Note: --ignore-scripts may break packages that require build steps on install. Test in development first. For packages that require postinstall, explicitly allowlist them in .npmrc with ignore-scripts-with-allowlist.

Step 4: Pin GitHub Actions References

# Vulnerable: uses a mutable tag
- uses: actions/checkout@v4

# Hardened: pinned to specific commit SHA
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Enter fullscreen mode Exit fullscreen mode

Tags are mutable. A compromised action repository can push malicious code to an existing tag. SHA pinning ensures you are running exactly the code you reviewed. Use Dependabot with the following config to keep pinned SHAs updated:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
Enter fullscreen mode Exit fullscreen mode

Step 5: Restrict Workflow Permissions

# At the workflow level, deny all permissions by default
permissions: {}

jobs:
  publish:
    permissions:
      contents: read
      id-token: write  # Only what this job actually needs
Enter fullscreen mode Exit fullscreen mode

GitHub Actions workflows default to read permissions on all resources. Explicitly denying permissions at the workflow level and granting only what each job requires limits the blast radius if a step is compromised.

Verifying Third-Party Packages Before You Install

# Check provenance attestation for a specific package
npm audit signatures --json | jq '.[] | select(.name == "axios")'

# View attestation metadata directly
npm view axios@1.14.0 --json | jq '.dist.attestations'
# For the malicious versions: this would return null or an object missing the SLSA predicate
Enter fullscreen mode Exit fullscreen mode

Practical heuristic: if a new version of a major dependency lacks provenance attestations and was published via CLI (visible in the package metadata as npm publish rather than a CI workflow), treat it as suspicious and hold the update until provenance is available.

Where Automated SCA Fits Into This

A meaningful SCA check in 2026 goes beyond CVE lookups:

  1. Provenance verification: Does this package version have a valid SLSA attestation from a trusted CI environment?
  2. Behavioral analysis: Are there new network calls, filesystem access patterns, or postinstall scripts compared to the previous version?
  3. Dependency graph diffing: What changed in the full transitive dependency tree between your current and proposed lockfile?

The ideal SCA check runs locally, before the lockfile change is committed, and before any install happens. This is the only timing that can prevent execution of malicious postinstall code.

The Token Hygiene Audit Checklist

Run this audit now, before the next attack:

# List all active npm tokens
npm token list

# Revoke specific tokens
npm token revoke <token-id>

# Check for NPM_TOKEN in GitHub Actions secrets
# Go to: github.com/{org}/{repo}/settings/secrets/actions
Enter fullscreen mode Exit fullscreen mode

Additional steps:

  • Verify OIDC Trusted Publishing is configured in npmjs.com package settings for all packages you publish
  • Remove NPM_TOKEN GitHub Actions secrets from all repositories
  • Rotate any tokens that were used in CI in the last 90 days
  • Enable npm 2FA enforcement at the organization level (npmjs.com/org/{org}/settings)
  • Review the list of users with publish access to each package

What a Hardened Supply Chain Posture Looks Like in Practice

The teams that caught the axios attack quickly had several things in common. They enforced npm ci --ignore-scripts in all CI environments. They ran npm audit signatures as a blocking CI step. They had alerting configured for new versions of top-100 dependencies published without provenance attestations. And they had no classic npm tokens active on maintainer accounts.

The March 2026 TeamPCP campaign went beyond axios. The same group backdoored the LiteLLM Python package and the Telnyx Node SDK through a cascading trust chain that exploited compromised CI/CD credentials. They also compromised the Trivy security scanner directly, using it to steal cloud credentials from CI pipelines. The attack surface is expanding to include the tools used to audit dependencies.

The response is not to trust less but to verify more explicitly. Cryptographic provenance, behavioral analysis, and token hygiene together create a baseline that makes opportunistic supply chain attacks significantly harder to execute at scale.

Install LucidShark with npx lucidshark init to run SCA scans locally. It integrates directly with Claude Code via MCP, running dependency verification before your agent installs anything. Your manifest stays on your machine.

Top comments (0)