# GH Actions - npm Supply Chain Abuse

{{#include ../../../banners/hacktricks-training.md}}

## 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.

Typical credential sources:

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

## 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.

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

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

### `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`.

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.
- Treat a sudden `binding.gyp` addition as an execution primitive, especially if defenders rely on lifecycle-hook monitoring or `--ignore-scripts`.
- Review release jobs that run `npm install`, `npm rebuild`, or dependency build steps after restoring untrusted artifacts/caches.

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

Useful enumeration from a compromised npm identity:

```bash
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.

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

High-signal review points:

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

## Hardening

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

## References

- [What the Miasma campaign reveals about the new supply chain threat model and the underground market for developer credentials](https://www.tenable.com/blog/what-the-miasma-campaign-reveals-about-the-new-supply-chain-threat-model-and-the-underground)
- [Trusted publishing for npm packages | npm Docs](https://docs.npmjs.com/trusted-publishers/)
- [Staged publishing for npm packages | npm Docs](https://docs.npmjs.com/staged-publishing/)
- [npm orgs | npm Docs](https://docs.npmjs.com/using-npm/orgs.html)
- [node-gyp README](https://github.com/nodejs/node-gyp)

{{#include ../../../banners/hacktricks-training.md}}
