Status: All clear ✅.
- The malicious version of Nx Console (v18.95.0) is no longer available from any marketplace.
- The current published version, v18.100.5, and all versions since v18.100.0, are safe.
- Anyone who installed v18.95.0 during the exposure window (2026-05-18, 12:30-13:09 UTC) should treat their machine as compromised and rotate credentials.
- The compromise originated from a single contributor's developer machine, which resolved one malicious package (
@tanstack/zod-adapter@1.166.15) during a routinepnpm installseven days earlier, part of the broader TanStack@tanstack/*supply-chain compromise of 2026-05-11. - The Nx CLI (
nxnpm package), all official@nx/*plugins, and Nx Cloud were not affected. This compromise was scoped entirely to the Nx Console VS Code extension.
GitHub Security Advisory: GHSA-c9j4-9m59-847w
Tracking issue: nrwl/nx-console#3139
TL;DR
On 2026-05-18 between 12:30 and 13:09 UTC, an attacker published a malicious Nx Console v18.95.0 to the Visual Studio Marketplace and the Open VSX registry. The Visual Studio Marketplace package was live ~11 minutes until we patched it, the Open VSX one approximately 36 minutes. The attacker published the malicious version as a legitimate Nx core contributor. A credential-stealing payload that arrived through the TanStack supply-chain compromise had silently exfiltrated that contributor's GitHub CLI OAuth token seven days earlier. Between credential theft on May 11 and the marketplace publish on May 18, the attacker was active in our GitHub repos for seven days without detection.
Visual Studio Marketplace reports 28 installs of v18.95.0. Open VSX reports 41 downloads from 21 unique client IPs. Our own internal analytics indicate the number of affected users may be significantly higher, and we are working with Microsoft to reconcile the figures. Out of caution, anyone who had Nx Console with auto-update enabled during the exposure window should assume compromise.
The publish pipeline gap that allowed it to happen is fixed, and we have extended the same hardening to our other publish-capable repos.
Impact
Versions affected
| Package | Affected version | Patched version |
|---|---|---|
Nx Console (VS Code extension) | 18.95.0 exactly | 18.100.0 |
No earlier or later versions are affected.
Install numbers
| Source | Number |
|---|---|
| Visual Studio Marketplace install count (Microsoft telemetry) | 28 |
| Open VSX downloads in the 36-minute window | 41 (across 21 unique IPs) |
| Nx internal extension-activation count from v18.95.0 | ~6,000 (activations, not installs) |
The discrepancy between the install numbers and our internal activation counts is the subject of ongoing reconciliation with Microsoft. Our telemetry tracks extension activation, while Microsoft track installs, so it is expected to be larger. It is also possible that the installs don't include updates, but we are unsure at this point.
If you had Nx Console installed in VS Code with auto-update enabled at any point during the exposure window (2026-05-18 12:30-13:09 UTC), treat your machine as potentially compromised regardless of which number is accurate.
What the malware does
The payload in v18.95.0 ran on extension activation in VS Code (or any compatible fork). It:
- Wrote persistence artifacts to disk:
- macOS:
~/Library/LaunchAgents/com.user.kitty-monitor.plist(LaunchAgent for reboot persistence) ~/.local/share/kitty/cat.py(Python harvester)/var/tmp/.gh_update_state,/tmp/kitty-*(state and staging)- Linux: Attempted modification of
/etc/sudoersfor privilege persistence - Windows: We don't have evidence of persisted artifacts targetting Windows.
- macOS:
- Harvested credentials from common locations:
- Vault:
~/.vault-token,/etc/vault/token, Kubernetes service-account tokens, AWS IAM auth tokens - npm:
.npmrctokens, OIDC token exchange - AWS: IMDS / ECS metadata service, Secrets Manager, SSM Parameter Store, Web Identity tokens
- GitHub:
ghp_/gho_/ghs_tokens from~/.config/gh/hosts.yml,~/.git-credentials, environment variables, and process memory - 1Password: contents of the
opCLI session if one was active at execution time - Filesystem: SSH private keys,
.envfiles, GCP application-default credentials, Docker config
- Vault:
- Exfiltrated via HTTPS to attacker infrastructure, the GitHub API, and DNS-based covert channels
If you installed v18.95.0, treat anything reachable from your machine as exposed. Rotate every credential that was either on disk or could have been minted by op, gcloud, aws sts, or gh during the exposure window.
Timeline
All times UTC.
Pre-incident: How we lost the credential
| Time | Event |
|---|---|
| 2026-05-11 19:20-19:26 | An attacker publishes 84 malicious versions across 42 @tanstack/* npm packages by combining a pull_request_target "Pwn Request", GitHub Actions cache poisoning, and OIDC token extraction. Full details in the TanStack postmortem. |
| 2026-05-11 20:43:01 | A Nx contributor runs pnpm install in an external repo (not from nrwl GitHub org). The repo's .npmrc specifies minimum-release-age=10080 (7 days), which would have blocked the install, but the project's packageManager field is pinned to a pnpm version that pre-dates that feature. The config was silently ignored, and pnpm resolves the newly-published malicious @tanstack/zod-adapter@1.166.15. The malware runs its prepare script, which fetches and executes a 2.3 MB obfuscated credential harvester. |
| 2026-05-11 20:43:51 | The attacker pushes the first malicious commit using the stolen credentials. The commit adds a workflow designed to exfiltrate organization-scoped GitHub secrets when triggered. The author email is spoofed to make the commit appear to come from a trusted bot. |
| 2026-05-11 20:44:15 - 20:45:56 | First GitHub workflow-tampering sweep. Bulk deletion of CodeQL Analysis workflow runs across several repos. The workflow being named CodeQL Analysis is likely a cover to make the workflow sound legitimate. |
| 2026-05-11 21:34, 21:36 | The malware re-runs locally as the developer's session re-invokes pnpm install. Second workflow deletion sweep at 21:35:38-21:37:25. |
| 2026-05-12 00:12:49 | GHSA-g7cv-rxg3-hmpx is published, disclosing the TanStack compromise. We do not yet realize that we were affected, as our internal repos were all clear. |
| 2026-05-15 23:56:05 | The attacker returns. The same token is used, but now from a python-httpx/0.28.1 client, different tooling than the initial node/octokit, suggesting either credential resale, automation rotation, or a second operator. Deletes a workflow run on one of our repos. |
| 2026-05-16 00:04:07 | Last observed use of the stolen token in our audit logs. The attacker's dwell time on the token is approximately 5 days and 4 hours. |
Incident: Publish to marketplaces
| Time | Event |
|---|---|
| 2026-05-18 12:30 | Visual Studio Marketplace registers Nx Console v18.95.0 extension upload, authenticated as the compromised contributor. The upload passes the automated verification from Microsoft (signing checks, manifest validation, basic malware scans). |
| 2026-05-18 12:30 | Open VSX security scan begins for v18.95.0. |
| 2026-05-18 12:33 | Open VSX scan completes and v18.95.0 goes live. |
| 2026-05-18 12:36 | A maintainer receives the routine publisher-notification email from Visual Studio Marketplace, sent automatically on every extension upload. The maintainer was not expecting a release and recognized it as anomalous. |
| 2026-05-18 12:47 | Maintainer unpublishes v18.95.0 from Visual Studio Marketplace, ~11 minutes after the email arrived and ~17 minutes after the malicious upload registered. |
| 2026-05-18 12:48 | Visual Studio Marketplace registers the unpublish. |
| 2026-05-18 13:09 | Open VSX unpublishes v18.95.0, ~36 minutes after publish. Open VSX has no equivalent publisher-notification email. The maintainer only thought to check Open VSX after handling the Visual Studio Marketplace unpublish. |
Detection and response
| Time | Event |
|---|---|
| 2026-05-18 12:36 | Initial detection. The publisher-notification email from Visual Studio Marketplace reaches a maintainer who was not expecting a release. |
| 2026-05-18 13:58 | Internal incident Slack channel opened. Compromised engineer had their org/repo access immediately revoked to prevent further attacks. Engineering and leadership convene a war room to discuss immediate action and next steps. |
| 2026-05-18 17:01 | GHSA-c9j4-9m59-847w published. Coordinated communications via X, Discord, and direct outreach to affected users begin. |
| 2026-05-18 18:47 | Token-rotation work begins across all repos where the compromised gh CLI token had access. PRs merged to enable GitHub deployment protection rules on release workflows so they require reviewer approval. This was previously done in our main repositories such as Nx CLI, but we missed repos such as Nx Console. |
| 2026-05-18 20:53 | Microsoft confirms via existing publisher contact channels that they are working on the response and provides initial install telemetry. |
| 2026-05-19 11:14 | Nx Console publishing pipeline hardened further to require two-admin approval (the central remediation cited in the public advisory). Other publish-capable repos migrated to npm OIDC trusted publishing with non-triggering-user approval requirements. |
| 2026-05-20 23:46 | Public advisory updated to acknowledge that internal analytics suggest user impact may be significantly higher than the install count from Microsoft. |
| 2026-05-21 | This postmortem published. |
Response timing
All times UTC. Detection here means the publisher-notification email reaching a maintainer (12:36 UTC).
| Milestone | UTC time | Time from detection |
|---|---|---|
| Visual Studio Marketplace unpublished | 12:47 | ~11 min |
| Open VSX unpublished | 13:09 | ~33 min |
| War room convened | ~13:58 | ~1h 22m |
| GitHub Security Advisory published | 17:01 | ~4h 25m |
Root cause
There are four chained factors, ordered from most upstream to most within our control. Removing any one would have broken the chain.
1. The TanStack @tanstack/* compromise (upstream)
A publishing-pipeline vulnerability in the GitHub Actions setup at TanStack allowed attackers to publish 84 malicious versions across 42 packages via the legitimate OIDC trusted-publisher binding at TanStack. To any downstream consumer, those versions carried valid npm provenance and were indistinguishable from legitimate releases. The only consumer-side defense is delay-based: don't install versions published in the last N hours or days. The project our affected contributor used had configured exactly that defense.
2. A silent minimum-release-age no-op
The repo where the install happened had minimum-release-age=10080 (7 days) configured in its root .npmrc. pnpm 10.16 and later support this setting natively. The project's packageManager field, however, pinned pnpm 10.14, which does not support minimum-release-age. pnpm 10.14 silently ignores the unknown config key rather than warning or failing, so the safeguard was never applied.
This is the gap that mattered most for credential exfiltration. Had pnpm been on the right version and enforced the release-age check, the install would have failed and the chain would have broken before any credentials were touched. The malicious version was 77 minutes old at install time, which any non-zero release-age policy would have blocked.
Lesson: for any team using minimum-release-age, this setting is silently no-op'd by pnpm versions older than 10.16, including versions that are otherwise current and supported. Pin your packageManager field to >=10.16, and add a CI guard that fails the build if the pnpm version in use is below that threshold. Do not rely on a config key being honored without verifying the runtime that consumes it.
3. The gh CLI credentials were retrievable by any local process
gh auth login (OAuth device flow) writes its resulting token to ~/.config/gh/hosts.yml. From there, any process running as the user can reach the token: by direct file read, by invoking gh auth token as a subprocess, or by scraping the memory of a running gh process. We have not forensically established which of these paths the malware took. We know only that the malware retrieved the token and exercised it against the GitHub API within 74 seconds of execution.
The defense against all three paths is the same: don't let the credential persist locally in retrievable form. Tools like the 1Password gh plugin (op plugin run -- gh) inject the credential at invocation time only, so there is no file to read, no long-lived gh daemon to scrape, and gh auth token is constrained to the wrapped invocation context.
We had an internal practice of wrapping gh through 1Password, but no organization-wide control verifying that contributor machines were running with the wrapper. The compromised contributor's machine was not.
Lesson: the gh CLI is one of the highest-blast-radius credentials on a developer's machine. Wrapping it through a secret manager is the right control, and our policy now disallows direct use of gh CLI on developer machines altogether.
4. The Nx Console publishing pipeline allowed single-actor releases
Until this incident, any core contributor could publish a new Nx Console version without a second human's approval. There was no required-reviewer rule, no environment gate, and no check that the pending release matched a release decision.
We had previously hardened other publish-capable workflows in our organization to require approval from another person who is not the one that triggered the workflow, but had not yet extended that hardening to Nx Console. The inconsistency was an oversight, not a deliberate scope decision.
Lesson: any pipeline that can publish to a public registry must require approval gating. The friction is small relative to the blast radius of a single-actor publish.
Detection: How we found out
A maintainer received the routine publisher-notification email from Visual Studio Marketplace at 12:36 UTC, the same kind of automated email publishers receive on every extension upload (legitimate or not). The maintainer wasn't expecting a release, recognized the email as anomalous, and unpublished within 11 minutes.
A user independently filed nrwl/nx-console#3139 on GitHub at some point during the exposure window, but we did not see it until well after the unpublish was complete.
Active monitoring of the audit log for suspicious runs and deletions would have helped. GitHub audit-log streaming to a SIEM is enterprise-tier infrastructure that most teams (including us) don't run.
Indicators of compromise
If any of these are present on a machine that had Nx Console installed during the exposure window, treat it as compromised:
- Files on disk (any of these):
~/.local/share/kitty/cat.py~/Library/LaunchAgents/com.user.kitty-monitor.plist/var/tmp/.gh_update_state/tmp/kitty-*
- Running processes:
pythonrunningcat.py- any process with environment variable
__DAEMONIZED=1
- Linux only: unexpected entries in
/etc/sudoersor/etc/sudoers.d/ - Affected version: Nx Console
18.95.0exactly (patched in18.100.0). - Exposure window: 2026-05-18 12:30-13:09 UTC
If you find any of the above, rotate every credential that was on disk or in your shell environment during that window: GitHub tokens, npm tokens, SSH keys, cloud-provider credentials (AWS, GCP, Azure, Kubernetes), Vault tokens, and the contents of any .env file. Treat the machine as potentially still persistent and consider a full rebuild after credential rotation.
Lessons learned
What went well
- A maintainer caught the publisher-notification email within minutes. The email was a routine "your extension was updated" notification, not a security alert, and we could have missed it.
- Rapid coordination once detected. From the publisher email arriving to the Visual Studio Marketplace unpublish was ~11 minutes. From the war-room convening to advisory publish was ~4 hours.
- Microsoft cooperated quickly. The malicious version was promptly removed from marketplaces when contacted, and Microsoft shared telemetry that informed our initial impact assessment.
- The contributor whose machine was compromised did the forensic work to reconstruct the chain. This investigation allowed us to pinpoint the root cause.
What could have been better
- We did not detect this through any system we built. Our detection was a routine publisher-notification email reaching a maintainer who happened to read it within minutes. Active monitoring of our audit log would have caught the suspicious activities more reliably.
- Open VSX has no publisher-notification path. The per-upload email from Visual Studio Marketplace reached us in 6 minutes. Open VSX has no equivalent. We didn't think to check Open VSX until after we'd handled the Visual Studio Marketplace unpublish, which is why the Open VSX window was three times as long.
- The signals sat in our audit log for a week. The attacker was active on our contributor's account for seven days before we noticed. Active monitoring for workflow-run deletions and other suspicious activities in the audit log would have prevented this attack. Other organizations that may have been compromised in the same upstream chain should look back at their audit logs for the same pattern.
- The Nx Console publishing pipeline allowed single-actor releases. We had hardened other publish-capable pipelines in our organization to require approval from another human. We had not extended that hardening to Nx Console.
- Our practice of vault-wrapping
ghwas not enforced. Wrappingghinvocations through a secret manager (op plugin run -- ghor equivalent) was internal practice. We had no tooling to verify contributor machines were running that way.
What we have changed
- An approval is now required to publish Nx Console. This is enforced via GitHub Actions environments with required reviewers. The reviewer cannot be the person who triggered the workflow.
- Monitoring of our GitHub audit log for suspicious events, for example workflow-run deletions.
- Pinned GitHub Action SHAs instead of floating refs (
@v6,@main) across all repos.
Many of these practices, such as release approval, minimum-release-age, and pinning GitHub Action SHAs, were already applied to our main repos (such as Nx CLI). We failed to apply them more broadly to all of our repos.
Acknowledgements
We thank:
- The user who filed nrwl/nx-console#3139. Your documentation of the unexpected install helped us understand user-side experience.
- The Visual Studio Marketplace security team at Microsoft, for prompt removal and telemetry.
- The Open VSX team, for telemetry around download stats.
- GitHub Security, for ongoing investigation support.
- The TanStack team, for the candor and depth of their own postmortem. Their disclosure timeline and IOC documentation made it possible for us to reconstruct the chain that reached our contributor's machine.
- StepSecurity and Socket.dev, for their public analyses of the upstream attack.
Nx CLI, plugins, and Nx Cloud were not affected
Scope of compromise
This compromise was scoped entirely to the Nx Console VS Code extension. The Nx CLI (nx npm package), all official @nx/* plugins, and Nx Cloud were not affected. No remediation is required for the Nx CLI or Nx Cloud.
References
- Nx security advisory: GHSA-c9j4-9m59-847w
- Tracking issue: nrwl/nx-console#3139
- Upstream TanStack advisory: GHSA-g7cv-rxg3-hmpx / CVE-2026-45321
- TanStack postmortem: tanstack.com/blog/npm-supply-chain-compromise-postmortem
- StepSecurity analysis: stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem
- Socket.dev analysis: socket.dev/blog/tanstack-npm-packages-compromised-mini-shai-hulud-supply-chain-attack
Changelog
- 2026-05-21: Initial publication.







