惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
博客园 - 【当耐特】
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
D
Docker
GbyAI
GbyAI
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
The Cloudflare Blog
雷峰网
雷峰网
A
About on SuperTechFans
小众软件
小众软件
博客园 - Franky
博客园 - 聂微东
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
MongoDB | Blog
MongoDB | Blog
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
V2EX
Engineering at Meta
Engineering at Meta
宝玉的分享
宝玉的分享
aimingoo的专栏
aimingoo的专栏
量子位
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
罗磊的独立博客
Martin Fowler
Martin Fowler
D
DataBreaches.Net
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Secure Thoughts
Project Zero
Project Zero
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tailwind CSS Blog
S
Schneier on Security
Blog — PlanetScale
Blog — PlanetScale
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Security Latest
Security Latest
NISL@THU
NISL@THU
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
CXSECURITY Database RSS Feed - CXSecurity.com
J
Java Code Geeks

Nx Blog

Sharing Tailwind CSS Styles Across Apps in a Monorepo | Nx Blog How SiriusXM Stays Competitive by Iterating and Getting to Market Fast | Nx Blog Agentic Experience Is the New Developer Experience | Nx Blog Nx Joins the Linux Foundation and the Agentic AI Foundation | Nx Blog A Monorepo Is NOT a Monolith | Nx Blog Why we deleted (most of) our MCP tools | Nx Blog Teach Your AI Agent How to Work in a Monorepo | Nx Blog How Broadcom stays efficient and nimble with monorepos | Nx Blog Why Monorepos are King in the Age of AI | Nx Blog Nx 2026 Roadmap: Expanding Agent Autonomy, Improving Performance, Better Polyglot and More | Nx Blog End to End Autonomous AI Agent Workflows with Nx | Nx Blog Autonomous Agents at Scale | Nx Blog Scaling 700+ Projects: How Nx Became a 'No-Brainer' for Caseware | Nx Blog Configure Tailwind v4 with Angular in an Nx Monorepo | Nx Blog The Missing Multiplier for AI Agent Productivity | Nx Blog A Year of Nx Webinars | Nx Blog Wrapping Up 2025 | Nx Blog Nx 22.3 Release: Angular 21 Support, tsgo Compiler, and Prettier v3 | Nx Blog Nx Cloud Release: Agent Resource Usage | Nx Blog Nx Platform Outperforms DIY Cache by 5x | Nx Blog An Nx Carol: Past, Present, and Future of Your Monorepo | Nx Blog Nx 22.1 Release: Terminal UI on Windows, Storybook 10, Vitest 4, and more! | Nx Blog The Compounding Effect: How Nx Features Multiply Performance Gains | Nx Blog 10 Monorepo Myths Debunked: Separating Fact from Fiction | Nx Blog Nx Cloud Release: Enterprise Task Analytics | Nx Blog Watch and Rebuild Storybook Dependencies with Nx | Nx Blog Book - React for Enterprise: Timeless Architecture for Enterprise Apps | Nx Blog Beyond Remote Cache: Unlock 70% More CI Performance | Nx Blog Nx 22 Release: Expanding the build platform | Nx Blog What's the Point of Generating All This Code If You Can't Merge It? | Nx Blog What's New in Nx Self-Healing CI | Nx Blog Nx Highlights: Smarter AI integration, all-new graph UI, and big new versions of your favorite tools | Nx Blog Making the Case for Smarter Monorepos, and How to Not Get Fooled by Myths | Nx Blog Integrating Biome in 20 Minutes | Nx Blog S1ngularity - What Happened, How We Responded, What We Learned | Nx Blog Stop Babysitting Your PRs: Self-Healing CI Cuts Time to Green by 50% | Nx Blog UKG Unifies Their Codebase and Eliminates CI Overhead to Focus on Customer Value | Nx Blog How Git Worktrees Changed My AI Agent Workflow | Nx Blog Nx Cloud Workspace Graph: See Your Organization's Code Structure Like Never Before | Nx Blog Seamless Java Deployment in Nx Using Docker | Nx Blog Getting Mobile Into Your Monorepo: Android + Nx | Nx Blog Polyglot Projects Made Easy: Integrating Spring Boot into an Nx Workspace | Nx Blog The Journey of the Nx Plugin for Gradle: From Prototype to Production | Nx Blog Combining Predictability and Intelligence With Nx Generators and AI | Nx Blog A New UI For The Humble Terminal | Nx Blog Continuous tasks are a huge DX improvement | Nx Blog New and Improved Module Federation Experience with Nx | Nx Blog A New UI for Nx Migration | Nx Blog Custom Task Runners and Self-Hosted Caching Changes | Nx Blog Enterprise Angular Monorepo Patterns | Nx Blog Using Rspack with Angular | Nx Blog Angular Architecture Guide To Building Maintainable Applications at Scale | Nx Blog Modern Angular Testing with Nx | Nx Blog Nx Update: 20.5 | Nx Blog Are Monorepos the Answer to Better AI-Assisted Development? | Nx Blog Making Cursor Smarter with an MCP Server For Nx Monorepos | Nx Blog React Development for 2025 | Nx Blog Using Apollo GraphQL in an Nx Workspace | Nx Blog Angular State Management for 2025 | Nx Blog Tailoring Nx for Your Organization | Nx Blog Nx Cloud Pipelines Come To Nx Console | Nx Blog Define the relationship with monorepos | Nx Blog See your affected project graph in Nx Cloud | Nx Blog Handling CORS In Your Workspace | Nx Blog Improve your architecture and CI pipeline times with Nx projects | Nx Blog Announcing Nx 20 | Nx Blog Introducing Nx Powerpack | Nx Blog Nx 19.5 is here! Stackblitz, Bun, Incremental Builds for Vite, Gradle Test Atomizer | Nx Blog Introducing Explain with AI | Nx Blog Nx Enterprise Podcast Episode 2: Tine Kondo | Nx Blog Monorepos and CI can be a Mess - Here's How Nx and Nx Cloud Fixed It | Nx Blog Nx Enterprise Podcast Episode 1: Hicham El Hammouchi | Nx Blog Nx 19.0 Release!! | Nx Blog Manage Your Gradle Project using Nx | Nx Blog Making the Argument for Monorepos | Nx Blog Reliable CI. A new execution model fixing both flakiness and slowness | Nx Blog Monorepos - Why Speed Matters | Nx Blog Nx Agents Walkthrough: Effortlessly Fast CI Built for Monorepos | Nx Blog Launch Nx Week Recap | Nx Blog Versioning and Releasing Packages in a Monorepo | Nx Blog Fast, Effortless CI | Nx Blog Introducing @nx/nuxt Enhanced Nuxt.js Support in Nx | Nx Blog What if Nx Plugins Were More Like VSCode Extensions | Nx Blog Monorepos: the Benefits, Challenges, and Importance of Tooling Support | Nx Blog Nx — Highlights of 2023 | Nx Blog Nx 17.2 Update | Nx Blog Unit Testing Expo Apps With Jest | Nx Blog Nx Docs AI Assistant | Nx Blog State Management Nx React Native/Expo Apps with TanStack Query and Redux | Nx Blog Nx 17 has Landed | Nx Blog Nx Conf 2023 — Recap | Nx Blog Nx Raises $16M Series A | Nx Blog Introducing Playwright Support for Nx | Nx Blog Nx 16.8 Release!!! | Nx Blog Step-by-Step Guide to Creating an Expo Monorepo with Nx | Nx Blog Qwikify your Development with Nx | Nx Blog Create Your Own create-react-app CLI | Nx Blog Storybook Interaction Tests in Nx | Nx Blog Evergreen Tooling — More than Just CodeMods | Nx Blog A Practical Guide on Effective AI Use - AI as Your Peer Programmer | Nx Blog
Postmortem: Nx Console v18.95.0 supply-chain compromise | Nx Blog
Jack Hsu · 2026-05-21 · via Nx Blog

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 routine pnpm install seven days earlier, part of the broader TanStack @tanstack/* supply-chain compromise of 2026-05-11.
  • The Nx CLI (nx npm package), all official @nx/* plugins, and Nx Cloud were not affected. This compromise was scoped entirely to the Nx Console VSCode 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

PackageAffected versionPatched version
Nx Console (VSCode extension)18.95.0 exactly18.100.0

VSCode and its forks (e.g. Cursor) are open to the attack. No earlier or later versions are affected.

Install numbers

SourceNumber
Visual Studio Marketplace install count (Microsoft telemetry)28
Open VSX downloads in the 36-minute window41 (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 VSCode (or a fork e.g. Cursor) 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 VSCode (or a fork e.g. Cursor). It:

  • Wrote persistence artifacts to disk:
    • macOS/Linux:
      • ~/.local/share/kitty/cat.py (Python backdoor)
      • ~/Library/LaunchAgents/com.user.kitty-monitor.plist (LaunchAgent for reboot persistence)
      • /tmp/kitty-* (staging)
      • /var/tmp/.gh_update_state (state)
    • Linux:
      • Attempted modification of /etc/sudoers for privilege persistence
    • Windows:
      • %USERPROFILE%\.local\share\kitty\cat.py (Python backdoor)
      • %TEMP%\kitty-* (staging)
      • %TEMP%\.gh_update_state (state)
      • %USERPROFILE%\.bun\bin\bun.exe (persistence)
  • Harvested credentials from common locations:
    • Vault: ~/.vault-token, /etc/vault/token, Kubernetes service-account tokens, AWS IAM auth tokens
    • npm: .npmrc tokens, 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 op CLI session if one was active at execution time
    • Filesystem: SSH private keys, .env files, GCP application-default credentials, Docker config
  • 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

TimeEvent
2026-05-11 19:20-19:26An 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:01A 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:51The 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:56First 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:36The 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:49GHSA-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:05The 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:07Last 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

TimeEvent
2026-05-18 12:30Visual 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:30Open VSX security scan begins for v18.95.0.
2026-05-18 12:33Open VSX scan completes and v18.95.0 goes live.
2026-05-18 12:36A 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:47Maintainer 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:48Visual Studio Marketplace registers the unpublish.
2026-05-18 13:09Open 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

TimeEvent
2026-05-18 12:36Initial detection. The publisher-notification email from Visual Studio Marketplace reaches a maintainer who was not expecting a release.
2026-05-18 13:58Internal 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:01GHSA-c9j4-9m59-847w published. Coordinated communications via X, Discord, and direct outreach to affected users begin.
2026-05-18 18:47Token-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:53Microsoft confirms via existing publisher contact channels that they are working on the response and provides initial install telemetry.
2026-05-19 11:14Nx 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:46Public advisory updated to acknowledge that internal analytics suggest user impact may be significantly higher than the install count from Microsoft.
2026-05-21This postmortem published.

Response timing

All times UTC. Detection here means the publisher-notification email reaching a maintainer (12:36 UTC).

MilestoneUTC timeTime from detection
Visual Studio Marketplace unpublished12:47~11 min
Open VSX unpublished13:09~33 min
War room convened~13:58~1h 22m
GitHub Security Advisory published17: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:

  • Malicious version of Nx Console if on your machine:
    code --list-extensions --show-versions | grep angular-console
  • Files on disk (any of these):
    • macOS/Linux:
      • ~/.local/share/kitty/cat.py
      • ~/Library/LaunchAgents/com.user.kitty-monitor.plist
      • /var/tmp/.gh_update_state
      • /tmp/kitty-*
      # Check for Python C2 backdoor
      ls -la ~/.local/share/kitty/cat.py
      
      # Check for macOS LaunchAgent
      ls -la ~/Library/LaunchAgents/com.user.kitty-monitor.plist
      
      # Check for anti-replay state
      ls -la /var/tmp/.gh_update_state
      
      # Check for staging directories
      ls -d /tmp/kitty-* 2>/dev/null
    • Windows:
      • %USERPROFILE%\.local\share\kitty\cat.py
      • %TEMP%\kitty-*
      • %TEMP%\.gh_update_state
      • %USERPROFILE%\.bun\bin\bun.exe
      # Powershell commands
      
      # Check for Python C2 backdoor
      Test-Path "$env:USERPROFILE\.local\share\kitty\cat.py"
      
      # Check for staging directories
      Get-ChildItem "$env:TEMP\kitty-*" -ErrorAction SilentlyContinue
      
      # Check for anti-replay state
      Test-Path "$env:TEMP\.gh_update_state"
      
      # Check for Bun runtime installed for persistence
      Test-Path "$env:USERPROFILE\.bun\bin\bun.exe"
      
      # Check npm cache for evidence of malicious package fetch
      Get-ChildItem "$env:APPDATA\npm-cache" -Recurse -Filter "*.json" |
        Select-String "558b09d7" -List
      
      # Check for daemonized or persistence processes
      Get-CimInstance Win32_Process |
        Where-Object { $_.CommandLine -match "kitty|cat\.py|558b09d7|__DAEMONIZED" } |
        Select-Object ProcessId, CommandLine
      
      # Check Scheduled Tasks for persistence
      schtasks /query /fo LIST /v | Select-String -Pattern "kitty|cat\.py|bun" -Context 3
      
      # Check Registry Run keys for persistence
      Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -ErrorAction SilentlyContinue
      Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" -ErrorAction SilentlyContinue
      
      # Check Startup folder
      Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
  • Running processes:
    • python running cat.py
    • any process with environment variable __DAEMONIZED=1
  • Linux only: unexpected entries in /etc/sudoers or /etc/sudoers.d/
  • Affected version: Nx Console 18.95.0 exactly (patched in 18.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.

Thanks to StepSecurity for their information above, and the commands for users to check their machines.

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 gh was not enforced. Wrapping gh invocations through a secret manager (op plugin run -- gh or 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 VSCode 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

Changelog

  • 2026-05-22: Added Windows indicators of compromise.
  • 2026-05-21: Initial publication.