Sponsored

GH Actions - npm Supply Chain Abuse

Overview

After an attacker gets code execution in a GitHub Actions release workflow, maintainer workstation, or package build pipeline, npm publishing becomes a high-impact pivot. The goal is usually to steal publisher identity material, publish malicious versions, and turn downstream installs into more credential-generation nodes.[1]

Typical credential sources:

  • ~/.npmrc, NPM_TOKEN, registry sessions, and npm automation tokens.[1][2]
  • GitHub PATs, GITHUB_TOKEN, release-bot credentials, SSH keys, and .netrc / git credential helpers.[1]
  • GitHub Actions OIDC request material (ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN) in jobs with id-token: write.[8]
  • Cloud credentials, Vault tokens, Kubernetes service account tokens, and .env files present in the release environment.[1]

Install-Time Execution Primitives

Lifecycle hooks

The classic npm route is to publish a malicious package version with preinstall, install, postinstall, or prepare scripts. Any developer workstation or CI job that installs the version executes attacker-controlled code.[6]

{
  "scripts": {
    "postinstall": "node ./scripts/collect.js"
  }
}

Defenders often monitor these scripts, so red-team reviews should also inspect less obvious execution paths.[11]

binding.gyp / node-gyp execution (Phantom Gyp)

Not every install-time execution path lives in package.json lifecycle hooks. node-gyp's configure step looks for a binding.gyp file in the package directory, so a compromised publisher can shift execution into the native build path and bypass controls that only audit preinstall / postinstall.[5][6][11]

Practical checks:

  • Inspect the published tarball, not only the Git repo, for unexpected binding.gyp, node-gyp, or native-addon metadata in packages that should be pure JavaScript.[5][11]
  • Treat a sudden binding.gyp addition as an execution primitive, especially if defenders rely on lifecycle-hook monitoring or --ignore-scripts.[6][10][11]
  • Review release jobs that run npm install, npm rebuild, or dependency build steps after restoring untrusted artifacts/caches.[2][11]

Wormable npm Publishing

Once code runs in a maintainer workstation or release workflow, a single stolen registry identity can be turned into self-propagating package compromise:[1]

  1. Harvest maintainer secrets (~/.npmrc, PATs, OIDC request env vars, cloud creds, SSH keys).[1]
  2. Enumerate packages the compromised identity or team can publish to.[1][7]
  3. Republish malicious versions across each writable package.[1]
  4. Let downstream installs create more credential-generation nodes.[1]

Useful enumeration from a compromised npm identity:[7]

npm whoami
npm access ls-packages
npm access ls-collaborators <scope-or-package>

Attackers usually prefer packages with frequent CI installs, transitive popularity, or release automation that will install the malicious version quickly.

Trusted Publishing and Provenance Limits

Trusted publishing/OIDC removes long-lived static npm tokens, but it does not make a compromised release workflow safe. If the attacker controls code that runs in a job with id-token: write, the malicious release can still receive valid provenance because the legitimate workflow really built and published it.[1][2][8]

Provenance answers which workflow built this artifact, not whether the workflow, source tree, cache, or build steps were clean.[1][2]

High-signal review points:

  • Workflows combining id-token: write with npm publish, pnpm publish, changesets, release bots, or custom publish wrappers.[2][8]
  • Release jobs that restore caches or artifacts from lower-trust workflows before publishing.[2]
  • Jobs that publish without human approval, environment protection rules, or a second reviewer.[2][3]
  • Workflows that request OIDC before all build inputs have been verified.[2][8]

Hardening

  • Use trusted publishing/OIDC instead of static npm tokens, but pair it with protected environments and human approval for sensitive scopes.[2][3]
  • Add staged publishing / human 2FA approval for high-impact packages where possible.[3]
  • Use minimumReleaseAge or equivalent dependency quarantine controls before consuming newly published package versions.[1][9]
  • Separate cache keys by trust boundary and never execute restored cache contents before integrity checks.[1][2]
  • Diff published tarballs against source repositories, and alert on unexpected native build metadata such as binding.gyp.[5][11]
  • Disable or tightly review lifecycle scripts in CI (npm config set ignore-scripts true) where builds do not need them.[1][6][10]
  • Monitor package access (npm access ls-packages) and remove stale maintainers, bots, and teams.[7][12]

References

[!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Browse the full HackTricks Training catalog.

Support HackTricks