diff --git a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md index eebbc190a7..dc59a594b5 100644 --- a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md +++ b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md @@ -47,12 +47,16 @@ Defenders often monitor these scripts, so red-team reviews should also inspect l ### `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]](#references)[[6]](#references)[[11]](#references) +Not every install-time execution path is an explicit `package.json` script. When a package has a root `binding.gyp` but no `install` or `preinstall` script, npm supplies an implicit `install` hook that runs `node-gyp rebuild`. The hook is also suppressed by `"gypfile": false`. The rebuild processes `binding.gyp` during configuration, before native compilation.[[5]](#references)[[27]](#references) + +GYP evaluates `conditions` as Python expressions during configuration, even if the declared target has `type: none`. In the analyzed variant, a condition traversed Python's class tree to find `catch_warnings`, recovered its module's `__builtins__` despite GYP's empty builtins dictionary, and used `__import__` to reach `os.system` and launch a script. Unicode escapes concealed the names and command. A comparison consumed the call's return value, making execution a side effect of evaluating the condition. Unlike earlier `[[26]](#references)[[28]](#references) + +**This is not an automatic bypass of `--ignore-scripts`.** npm's implicit `node-gyp rebuild` is an install lifecycle hook, which `--ignore-scripts` suppresses. An explicit `preinstall` or `install` script also prevents npm from adding that hook. If a package has both a `preinstall` script and a poisoned `binding.gyp`, a separate invocation of `node-gyp` is needed to evaluate the GYP expression; an install with `--ignore-scripts` does not do so by itself.[[27]](#references) 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]](#references)[[11]](#references) -- Treat a sudden `binding.gyp` addition as an execution primitive, especially if defenders rely on lifecycle-hook monitoring or `--ignore-scripts`.[[6]](#references)[[10]](#references)[[11]](#references) +- In `binding.gyp`, inspect `conditions` as well as `[[26]](#references)[[28]](#references) - Review release jobs that run `npm install`, `npm rebuild`, or dependency build steps after restoring untrusted artifacts/caches.[[2]](#references)[[11]](#references) ## Runtime Backdoors at Plaintext Secret Boundaries @@ -258,6 +262,8 @@ Useful behavior-level signals from the same chain include:[[18]](#reference - Review repository-scoped developer automation files such as `.vscode/tasks.json` and `.claude/settings.json` as executable supply-chain inputs, and protect non-default release branches as rigorously as `main`.[[18]](#references) - Monitor unexpected runtime bootstrap downloads and package-manager child processes rather than limiting detections to Node.js internals; block release jobs from fetching unpinned runtimes where the build does not require them.[[18]](#references) +{{#include ../../../banners/hacktricks-training.md}} + ## References - [1] [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) @@ -285,5 +291,6 @@ Useful behavior-level signals from the same chain include:[[18]](#reference - [23] [OVHcloud CLI supply chain compromise analysis](https://opensourcemalware.com/blog/glassworm-hacks-ovh-clouds-cli-tool) - [24] [Major Shai Hulud campaign strikes npm again, affecting keyv and 400+ packages | JFrog](https://research.jfrog.com/post/shai-hulud-is-back-august/) - [25] [Equation of Compromise: Anatomy of a Live npm Supply-Chain Campaign | JFrog](https://research.jfrog.com/post/equation-of-compromise/) - -{{#include ../../../banners/hacktricks-training.md}} +- [26] [Shai-Hulud Trinitite Hits @7nohe/openapi-react-query-codegen | JFrog](https://research.jfrog.com/post/shai-hulud-trinitite/) +- [27] [npm-rebuild | npm Docs](https://docs.npmjs.com/cli/v11/commands/npm-rebuild/) +- [28] [GYP condition evaluation source | node-gyp](https://github.com/nodejs/node-gyp/blob/main/gyp/pylib/gyp/input.py)