From 64229dac05fbab10b216a3bfd6a60206f3798242 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 7 Nov 2026 14:07:13 +0000 Subject: [PATCH 1/2] Add content from: Trinitite: Mini Shai-Hulud Hides an Install-Time Worm in bin... --- .../gh-actions-npm-supply-chain-abuse.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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..5376af7983 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. If a package has a root `binding.gyp` but no `install` or `preinstall` script, npm supplies an implicit `install` hook that runs `node-gyp rebuild`. Its configure step processes the file before any native compilation.[[5]](#references)[[27]](#references) + +A malicious `conditions` expression can execute during that configure step, even when the declared target is a decoy with `type: none`. GYP evaluates condition strings as Python with an empty builtins dictionary. In the observed variant, the expression walked `().__class__.__base__.__subclasses__()` to find `catch_warnings`, reached its module's `__builtins__`, imported `os`, and called `os.system('node 3FWCvzduYZg.js')`. The attacker encoded `catch_warnings`, `__import__`, `os`, and the command with `\U` escapes, so a scan for shell expansions such as `[[26]](#references)[[27]](#references) + +**Do not treat this as an automatic bypass of `--ignore-scripts`.** npm's implicit `node-gyp rebuild` is an install lifecycle hook, and npm documents that `--ignore-scripts` suppresses that build. An explicit `preinstall` or `install` script also prevents npm from adding the implicit hook. A package with both `preinstall` and a poisoned `binding.gyp` therefore needs a separate `node-gyp configure`/`rebuild` invocation to reach the GYP expression; merely installing it with `--ignore-scripts` does not do so.[[26]](#references)[[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 @@ -285,5 +289,8 @@ 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/) +- [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) {{#include ../../../banners/hacktricks-training.md}} From 550de24f0fe37fc2605963431376cb891ff8f404 Mon Sep 17 00:00:00 2001 From: SirBroccoli Date: Sat, 7 Nov 2026 15:16:22 +0100 Subject: [PATCH 2/2] docs: repair PR placement and formatting after editorial review --- .../gh-actions-npm-supply-chain-abuse.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 5376af7983..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,16 +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 is an explicit `package.json` script. If a package has a root `binding.gyp` but no `install` or `preinstall` script, npm supplies an implicit `install` hook that runs `node-gyp rebuild`. Its configure step processes the file before any native compilation.[[5]](#references)[[27]](#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) -A malicious `conditions` expression can execute during that configure step, even when the declared target is a decoy with `type: none`. GYP evaluates condition strings as Python with an empty builtins dictionary. In the observed variant, the expression walked `().__class__.__base__.__subclasses__()` to find `catch_warnings`, reached its module's `__builtins__`, imported `os`, and called `os.system('node 3FWCvzduYZg.js')`. The attacker encoded `catch_warnings`, `__import__`, `os`, and the command with `\U` escapes, so a scan for shell expansions such as `[[26]](#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) -**Do not treat this as an automatic bypass of `--ignore-scripts`.** npm's implicit `node-gyp rebuild` is an install lifecycle hook, and npm documents that `--ignore-scripts` suppresses that build. An explicit `preinstall` or `install` script also prevents npm from adding the implicit hook. A package with both `preinstall` and a poisoned `binding.gyp` therefore needs a separate `node-gyp configure`/`rebuild` invocation to reach the GYP expression; merely installing it with `--ignore-scripts` does not do so.[[26]](#references)[[27]](#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) -- In `binding.gyp`, inspect `conditions` as well as `[[26]](#references)[[28]](#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 @@ -262,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) @@ -292,5 +294,3 @@ Useful behavior-level signals from the same chain include:[[18]](#reference - [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) - -{{#include ../../../banners/hacktricks-training.md}}