critical
CVE
Not assigned
CWE
CWE-506, CWE-494, CWE-829
Affected Surface
`@immobiliarelabs/backstage-plugin-gitlab`, `@immobiliarelabs/backstage-plugin-gitlab-backend`, `@immobiliarelabs/backstage-plugin-ldap-auth`, and `@immobiliarelabs/backstage-plugin-ldap-auth-backend` at the exact malicious versions listed below, Developer workstations, CI runners, and build containers that ran `npm install`, `pnpm install`, or `yarn install` against those versions on or after 26 June 2026, Backstage environments where install-time access exposed GitLab tokens, LDAP-related configuration, npm publish rights, GitHub Actions secrets, cloud credentials, Kubernetes tokens, SSH material, or AI-assistant configuration files
The newest package-compromise story worth adding to Corgea’s /research section is the 26 June 2026 compromise of four @immobiliarelabs Backstage plugin families. This matters because the attacker did not just poison a leaf library. They poisoned packages that are commonly installed inside internal developer portals and CI-backed monorepos, exactly where GitLab tokens, LDAP configuration, cloud credentials, and release automation secrets tend to coexist.
The attack pattern is also important. The malicious versions did not rely on an obvious preinstall or postinstall script in package.json. Instead, they added a binding.gyp file plus a new root index.js, which means shallow checks that only inspect lifecycle-script fields can miss the real execution edge entirely.
Affected packages
Public reporting from Socket, StepSecurity, and the maintainer issue all agree on the same 22 malicious package-version pairs:
| Package | Malicious version |
|---|---|
@immobiliarelabs/backstage-plugin-gitlab | 1.0.1 |
@immobiliarelabs/backstage-plugin-gitlab | 2.1.2 |
@immobiliarelabs/backstage-plugin-gitlab | 3.0.3 |
@immobiliarelabs/backstage-plugin-gitlab | 4.0.2 |
@immobiliarelabs/backstage-plugin-gitlab | 5.2.1 |
@immobiliarelabs/backstage-plugin-gitlab | 6.13.1 |
@immobiliarelabs/backstage-plugin-gitlab | 7.0.2 |
@immobiliarelabs/backstage-plugin-gitlab-backend | 3.0.3 |
@immobiliarelabs/backstage-plugin-gitlab-backend | 4.0.2 |
@immobiliarelabs/backstage-plugin-gitlab-backend | 5.2.1 |
@immobiliarelabs/backstage-plugin-gitlab-backend | 6.13.1 |
@immobiliarelabs/backstage-plugin-gitlab-backend | 7.0.2 |
@immobiliarelabs/backstage-plugin-ldap-auth | 1.1.4 |
@immobiliarelabs/backstage-plugin-ldap-auth | 2.0.5 |
@immobiliarelabs/backstage-plugin-ldap-auth | 3.0.2 |
@immobiliarelabs/backstage-plugin-ldap-auth | 4.3.2 |
@immobiliarelabs/backstage-plugin-ldap-auth | 5.2.1 |
@immobiliarelabs/backstage-plugin-ldap-auth-backend | 1.1.3 |
@immobiliarelabs/backstage-plugin-ldap-auth-backend | 2.0.5 |
@immobiliarelabs/backstage-plugin-ldap-auth-backend | 3.0.2 |
@immobiliarelabs/backstage-plugin-ldap-auth-backend | 4.3.2 |
@immobiliarelabs/backstage-plugin-ldap-auth-backend | 5.2.1 |
If any of those exact versions were installed, treat the installing host as having executed attacker-controlled code.
Why these packages are high-value targets
The affected packages are not generic utility modules. They are Backstage plugins used to connect an internal developer portal to identity and source-control systems:
- the GitLab packages expose project, pipeline, merge-request, and contributor data inside Backstage
- the LDAP packages support authentication flows in organizations that still rely on LDAP or Active Directory
That placement changes the blast radius. A successful install-time compromise in a Backstage plugin build often reaches:
- GitLab tokens and repository metadata
- internal CI secrets used to build or release the portal
- cloud credentials available to the build container
- Kubernetes or Vault material used by platform teams
- developer-tool configuration files that enable persistence beyond the original install
Independent registry evidence
The registry metadata looks like a coordinated republish wave, not normal maintenance.
For the main GitLab frontend plugin, the package history shows a clean release, a malicious patch release, and then a same-day cleanup release:
@immobiliarelabs/backstage-plugin-gitlab
2.1.1 2023-01-17T10:36:06.750Z
2.1.2 2026-06-26T15:01:18.590Z malicious
2.1.3 2026-06-26T17:38:32.875Z clean replacement
6.13.0 2025-09-08T10:11:50.986Z
6.13.1 2026-06-26T15:01:38.398Z malicious
6.13.2 2026-06-26T17:32:52.634Z clean replacement
7.0.1 2026-04-01T11:40:04.322Z
7.0.2 2026-06-26T15:01:43.052Z malicious
7.0.3 2026-06-26T17:24:26.954Z clean replacement
The current npm dist-tags now point latest to 2.1.3, which is consistent with the maintainer’s emergency cleanup effort. But the malicious versions still matter for incident response because the compromise happened before that cleanup landed.
The tarball shape also changed in a way ordinary patch releases do not:
@immobiliarelabs/backstage-plugin-gitlab@2.1.1
unpacked size: 228,219 bytes
file count: 15
@immobiliarelabs/backstage-plugin-gitlab@2.1.2
unpacked size: 5,220,231 bytes
file count: 20
A small Backstage plugin does not normally grow by roughly five megabytes and five extra root files for a patch release. StepSecurity’s tarball diff explains the delta: the malicious versions gained a new binding.gyp execution trigger and a new 5 MB root index.js loader that was absent from prior releases.
The vulnerable edge is install time, not runtime
The most important code in this incident is the newly added binding.gyp:
{
"targets": [
{
"target_name": "Setup",
"type": "none",
"sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
}
]
}
That single line is enough to turn npm install into code execution:
npm install @immobiliarelabs/backstage-plugin-gitlab@2.1.2
-> node-gyp rebuild
-> /bin/sh -c "node index.js > /dev/null 2>&1 && echo stub.c"
-> node index.js
-> decrypt / stage payload
-> steal secrets and attempt persistence
This is the same Phantom Gyp technique that kept resurfacing across June’s Miasma waves. It matters because many defenses still answer the wrong question:
- wrong question: “does
package.jsoncontainpreinstallorpostinstall?” - right question: “does the published tarball contain any build or startup surface that will execute code during install?”
In the ImmobiliareLabs case, the second question is the one that catches the attack.
The declared entrypoint is not the malicious entrypoint
Socket’s analysis highlights another subtle but important detail: the normal compiled Backstage plugin output under dist/ still looked legitimate enough to mislead casual review. The real malicious execution path sat in the package root:
dist/index.cjs.js expected library entrypoint
index.js new 5 MB malicious loader
binding.gyp new install-time execution trigger
That lets the attacker preserve normal-looking application code while moving execution into a place many teams never inspect.
What the payload does
StepSecurity’s static analysis of @immobiliarelabs/backstage-plugin-gitlab@2.1.2 and Socket’s broader campaign clustering show the same operational pattern seen in adjacent Miasma incidents:
- a Caesar-shift wrapper obscures the outer JavaScript layer
- AES-128-GCM decrypts embedded blobs
- a Bun bootstrap downloads
bunfrom GitHub releases if needed - the main stage runs under Bun rather than Node.js
- the payload harvests credentials and attempts follow-on propagation
StepSecurity specifically reports these functional markers in the decoded payload:
infectHost
squatPackage
updateTarball
handleNpmTokens
handlePypiTokens
Those names matter because they move the incident from “credential stealer” to “possible worm operator toolkit.” The public analysis indicates the payload was built to:
- read GitHub tokens, GitHub App material, and GitHub Actions runtime secrets
- dump masked secrets from
Runner.Workermemory on Linux runners - collect AWS, GCP, Azure, Kubernetes, and Vault credentials
- steal npm, PyPI, RubyGems, and JFrog Artifactory tokens
- read password-manager and SSH material
- modify AI-coding-assistant and IDE configuration files for persistence
- reuse harvested registry credentials to publish additional poisoned artifacts
Why Backstage amplifies the risk
Backstage plugin builds are not normal leaf-package installs. They often happen in repositories that already integrate multiple trust boundaries:
integrations:
gitlab:
- host: gitlab.com
token: ${GITLAB_TOKEN}
and:
'/gitlabci':
target: '${GITLAB_URL}/api/v4'
headers:
PRIVATE-TOKEN: '${GITLAB_TOKEN}'
That configuration pattern, shown in the package’s own setup documentation, explains why a malicious Backstage plugin is high leverage. A compromised install host can sit next to:
- GitLab API tokens
- service-catalog build secrets
- release automation credentials
- internal portal deployment keys
The attacker never needed a GitLab or LDAP memory-corruption bug. They only needed code to execute in the environment where these integrations were built.
Maintainer response and what it means
The maintainer thread is useful because it confirms two operational facts:
- clean patch versions were pushed the same day to steer automated updaters away from the compromised artifacts
- at least some malicious frontend GitLab plugin versions were still awaiting npm-side cleanup after the emergency response began
That combination means defenders should not assume “the package is fixed now” ends the investigation. The real question is whether any machine in your environment installed one of the malicious versions before the clean replacements arrived.
Detection and scoping
Start by inventorying the exact affected packages:
npm ls @immobiliarelabs/backstage-plugin-gitlab \
@immobiliarelabs/backstage-plugin-gitlab-backend \
@immobiliarelabs/backstage-plugin-ldap-auth \
@immobiliarelabs/backstage-plugin-ldap-auth-backend
Then search lockfiles for the exact malicious versions:
rg -n \
"@immobiliarelabs/backstage-plugin-gitlab@(1\\.0\\.1|2\\.1\\.2|3\\.0\\.3|4\\.0\\.2|5\\.2\\.1|6\\.13\\.1|7\\.0\\.2)|@immobiliarelabs/backstage-plugin-gitlab-backend@(3\\.0\\.3|4\\.0\\.2|5\\.2\\.1|6\\.13\\.1|7\\.0\\.2)|@immobiliarelabs/backstage-plugin-ldap-auth@(1\\.1\\.4|2\\.0\\.5|3\\.0\\.2|4\\.3\\.2|5\\.2\\.1)|@immobiliarelabs/backstage-plugin-ldap-auth-backend@(1\\.1\\.3|2\\.0\\.5|3\\.0\\.2|4\\.3\\.2|5\\.2\\.1)" \
package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json
Inspect installed packages or caches for the Phantom Gyp trigger:
rg -n '"<!\(node index\.js > /dev/null 2>&1 && echo stub\.c\)"' node_modules ~/.npm
And review repositories and developer homes for persistence surfaces the public analysis called out:
rg -n \
"\\.claude/settings\\.json|\\.cursor/rules/|\\.vscode/tasks\\.json|\\.github/copilot-instructions\\.md|\\.aider\\.conf\\.yml" \
. "$HOME"
On CI runners, high-signal telemetry includes:
node-gyp rebuild on a package that should be pure JavaScript
node index.js launched from package install
curl or unzip during dependency installation
Bun download or Bun execution during npm install
reads of /proc/<pid>/mem targeting Runner.Worker
unexpected writes to AI-assistant config files
Response guidance
Treat installation of any affected version as host compromise, not as a routine dependency update.
- Isolate affected developer machines, build containers, and CI runners.
- Preserve lockfiles, package caches, install logs, and any affected tarballs.
- Rebuild from known-good versions and clean lockfiles rather than patching
node_modulesin place. - Rotate GitHub, npm, GitLab, cloud, Kubernetes, Vault, SSH, and developer-tool credentials that were reachable from the host.
- Audit repositories and CI workflows that the compromised host could write to.
- Inspect
.claude/,.cursor/,.vscode/,.github/, and similar developer-tool directories for persistence planted after the install.
The central lesson is the same one defenders keep relearning in 2026: a package can look routine at the source level and still turn into malware at the registry-artifact level. In the ImmobiliareLabs compromise, the dangerous code path was not in the documented Backstage integration logic. It was in a binding.gyp file that should never have existed in those packages at all.
References
- StepSecurity: Multiple @immobiliarelabs Backstage Plugins Compromised on npm
- Socket: Miasma Mini Shai-Hulud Hits ImmobiliareLabs npm Packages
- GitHub issue #1052: Malicious npm releases found in @immobiliarelabs scope
- npm registry metadata: @immobiliarelabs/backstage-plugin-gitlab
- npm package page: @immobiliarelabs/backstage-plugin-gitlab
- CWE-506 Embedded Malicious Code
- CWE-494 Download of Code Without Integrity Check
- CWE-829 Inclusion of Functionality from Untrusted Control Sphere