On May 18, 2026, a compromised build of Nx Console (nrwl.angular-console v18.95.0) sat on the Visual Studio Marketplace for 18 minutes. The extension has 2.2 million installs across VS Code, Cursor, and JetBrains. It fetched a 498KB obfuscated payload from a dangling orphan commit inside the official nrwl/nx GitHub repository, harvested developer credentials via HTTPS, GitHub API abuse, and DNS tunneling, and carried the logic to forge cryptographically-signed Sigstore provenance for downstream npm packages. GitHub’s security advisory[^1] and the Nx team’s postmortem[^2] describe an attack chain that started a week earlier in an unrelated JavaScript library.
The 18-Minute Window
The malicious extension went live on the VS Code Marketplace at 12
UTC on May 18 and was pulled at 12UTC. On Open VSX it persisted longer: 12to 13UTC, roughly 36 minutes. Microsoft reports 28 installs via the VS Code Marketplace and 41 via Open VSX, from 21 unique IP addresses[^2].Nx’s internal telemetry tells a different story: approximately 6,000 extension activations from VS Code and one from Cursor[^2]. The gap between marketplace install counts and activation telemetry is under active reconciliation with Microsoft. The likely explanation is that auto-updated extensions do not register as new installs in Microsoft’s accounting, which means the 28-figure is almost certainly an undercount.
The Attack Chain: TanStack to Orphan Commit
The credential that published the malicious extension was stolen a week earlier through the TanStack supply-chain compromise on May 11. A malicious version of @tanstack/zod-adapter@1.166.15 exfiltrated a contributor’s gh CLI OAuth token. Between credential theft on May 11 and the marketplace publish on May 18, the attacker was active in the project’s GitHub repositories for seven days without detection[^2].
How the Payload Works
Once installed, the extension fetched and executed a 498KB obfuscated payload from a dangling orphan commit in the nrwl/nx repository[^3]. An orphan commit has no parent and belongs to no branch. This one contained a package.json and the obfuscated index.js. The extension installed Bun, then executed the payload on workspace activation[^4].
According to Corgea’s technical analysis, the payload harvested credentials across a broad surface:
| Credential type | Source |
|---|---|
GitHub tokens (ghp_, gho_, ghs_) | Actions secrets, process memory |
| npm | .npmrc, OIDC exchange |
| AWS | IMDS/ECS metadata, Secrets Manager, SSM |
| HashiCorp Vault | Token extraction |
| Kubernetes | Service account tokens |
| Docker / GCP | Credential files |
| SSH keys | Local filesystem |
| 1Password | op CLI session tokens |
| Claude Code | ~/.claude/ configuration |
Exfiltration used three channels: HTTPS POST, GitHub API abuse (likely committing secrets to attacker-controlled repositories), and DNS tunneling[^4].
On macOS, the payload established persistence via a LaunchAgent (com.user.kitty-monitor.plist) that ran cat.py[^1]. That script polled GitHub Search and downloaded attacker-controlled Python payloads. The malware also ran checks to avoid infecting machines likely located in the Russian/CIS time zones[^4].
The Orphan-Commit Blind Spot
The delivery mechanism is the detail worth paying attention to. The attacker pushed a commit with no parent to a trusted repository, then referenced it by SHA from inside the extension. No branch-based code review catches this. No marketplace scanner catches it, because scanners inspect the extension package, not the GitHub commit graph of every upstream the extension fetches at runtime. The commit is invisible to git log, invisible to branch protection rules, and invisible to any review workflow that assumes commits belong to branches.
This is a structural gap in how registries and repositories reason about trust. The VS Code Marketplace verifies publisher identity. It does not verify the commit graph of every repository an extension might fetch code from during execution. The npx -y github:org/repo#sha pattern is a legitimate npm feature. The attack exploited the gap between “this extension is trusted” and “everything this extension fetches at runtime is trusted.”
The Sigstore Provenance Problem
The payload contained full Sigstore/Fulcio certificate issuance and SLSA provenance generation logic[^4]. Stolen OIDC tokens could be used to publish downstream npm packages with valid, cryptographically-signed provenance attestations. A package with a Sigstore signature and SLSA provenance passes every automated trust check in the ecosystem. The verification infrastructure says “this build is verified.” The build is malicious.
This is the second-order consequence that most coverage of this incident treats as a footnote. If your intake pipeline relies on Sigstore provenance as a trust signal, this attack demonstrates that provenance attests to the build environment’s integrity at publish time, not to the publisher’s intent. A stolen OIDC token plus Sigstore tooling produces a signed, verified, malicious package.
The GitHub Breach Connection
On May 20, GitHub confirmed a breach involving approximately 3,800 internal repositories[^3]. Aikido reported that the entry point was a poisoned VS Code extension on a GitHub employee’s device. GitHub has not publicly named Nx Console as the specific extension.
The connection is plausible but unconfirmed: an extension that steals GitHub tokens from developer machines, one of those machines belonging to a GitHub employee, and a GitHub internal repository breach two days later. The causal chain is circumstantial until GitHub says more.
What This Means for Marketplace Security
Three structural problems surfaced in this incident, and none of them are specific to Nx Console.
Single-token publishing. The VSCE_PAT model uses one token for extension publishing. Compromise that token and the attacker publishes as the maintainer. The same pattern has burned npm (left-pad, event-stream), PyPI, and every registry that conflates “has the credential” with “is authorized.” Nx has responded by fixing the publish pipeline gap and extending the same hardening to their other publish-capable repos[^2]. This raises the bar. It does not solve the problem for the thousands of other extensions on the marketplace.
Auto-update as attack surface. VS Code extensions auto-update by default. An 18-minute window is tight for manual detection but more than enough for auto-update to push a malicious build to every machine running the extension. The defense is not a publisher allowlist (the publisher was legitimate). The defense is either a version-age delay, refusing to install any version published in the last N hours, or extension pinning in enterprise deployments.
Remediation
The patched extension version is 18.100.0 and later[^1]. The publish pipeline gap that allowed the attack has been fixed, and the same hardening has been extended to other Nx publish-capable repositories[^2].
For developers who ran v18.95.0: rotating every credential accessible from a VS Code extension context is the recommended response[^1]. That includes GitHub tokens, npm tokens, AWS credentials, Vault tokens, Kubernetes service accounts, SSH keys, and any CLI session tokens (1Password, Claude Code) active during the exposure window. The macOS LaunchAgent persistence mechanism must be removed manually.
Frequently Asked Questions
Why didn’t the existing minimum-release-age safeguard catch this?
The Nx project had configured minimum-release-age=10080 (a 7-day delay on new dependency versions), but the setting was silently ignored because the project pinned pnpm 10.14, which predates support for that configuration key. A correctly functioning age gate would have blocked the malicious @tanstack/zod-adapter before it could exfiltrate the publish token — meaning the defense was already in place but ineffective due to a tooling version mismatch.
What did the payload do differently on Linux vs macOS?
On Linux, the malware attempted /etc/sudoers injection to escalate privileges and read /proc/*/mem to scrape in-memory secrets from running processes. The macOS variant focused on LaunchAgent persistence, polling GitHub Search for commits matching the keyword ‘firedalazer’ and verifying downloaded payloads with RSA-PSS signatures before execution.
How has the Nx publishing pipeline changed since the incident?
Nx Console publishing now requires two-admin manual approval, replacing the previous single-contributor publish model. Other Nx publish-capable repositories have migrated to npm OIDC trusted publishing with a non-triggering-user approval gate, meaning the person triggering a release cannot also be the person approving it.
What should threat hunters look for in VS Code task logs?
The malicious extension registered a hidden VS Code task named ‘install-mcp-extension’ that ran npx -y github
/nx#558b09d7… as its fetch mechanism. Teams scanning for exposure should search task execution histories for npx invocations referencing github: URLs pinned to full commit SHAs, as well as unexpected Bun installations during the May 18 window.