The short answer
On 26 August 2025, malicious versions of the nx build tool and several @nx/* plugins were published to npm. They were live for about four hours.2 A postinstall script scanned the file system for text files and credentials and posted them, encoded, to a new GitHub repository under the victim’s own account, with s1ngularity-repository in its name.1 Nx’s postmortem adds that the malware “attempted to use local AI tools (like Claude and Gemini).”2
That last detail is why this incident belongs on an agent-safety blog. An AI coding CLI installed on a developer’s machine is a capable tool for anyone who can run a command there, including malware. Settings that block “no prompts” modes by policy make it a worse one.
Deedglass
A tool call meets your rules before it runs
Asks to run
DELETE FROM visits;
Deletes every row in visits
Deedglass checks your rules
- Reading dataAllowed
- Deleting a whole table (DROP, TRUNCATE)Never
- Changing or deleting rows without saying which (WHERE)This one
- Other changesWait for a person
What happened
From Nx’s advisory and postmortem:12
| Item | Detail |
|---|---|
| Packages | nx 21.5.0, 20.9.0, 20.10.0, 21.6.0, 20.11.0, 21.7.0, 21.8.0, 20.12.0; some versions of @nx/devkit, @nx/js, @nx/workspace, @nx/node, @nx/eslint; @nx/key and @nx/enterprise-cloud 3.2.0 |
| Published | 26 August 2025 |
| Live for | About 4 hours before complete removal |
| Advisory | GHSA-cxm3-wv7p-598c, critical |
| Fixed by | Any other version; the malicious ones were deleted from npm |
How the attacker got the npm token
Nx’s advisory explains the chain in its own words:1
- A pull request workflow used the
pull_request_targettrigger, which runs with elevated permissions and a read/writeGITHUB_TOKENfor the target repository. - The same workflow echoed the pull request title inside a shell step,
echo "Validating PR title: ${{ github.event.pull_request.title }}", so a title like$(...)ran as code. - The vulnerable workflow had been reverted on
master, but Nx believes the pull request targeted an outdated branch that still had it. - With the elevated token, the injected code triggered the
publish.ymlworkflow on a malicious commit that sent the npm token to a webhook.
That’s two classic GitHub Actions mistakes together: untrusted text interpolated into a shell, and pull_request_target giving fork pull requests the target repository’s permissions.
What the malware did
Per the advisory:1
- Collected credentials. Scanned for text files, collected paths and credentials, and posted them encoded to a GitHub repository named with
s1ngularity-repositoryunder the user’s account. - Left an inventory. A file at
/tmp/inventory.txtlisting files it probably read. - Changed shell start-up files. Added
sudo shutdown -h 0to.zshrcand.bashrc, so a new terminal would ask for the password and shut down. - Ran wherever packages install. Not only
npm install: the advisory lists transitive dependencies, AI agents, editors and editor extensions as ways a package can be installed without anyone typing the command.
And from the postmortem: it attempted to use local AI tools like Claude and Gemini.2 Nx’s post doesn’t say which tools ran, with what instructions, or how often it worked, and we don’t fill that in from other sources.
Why the AI tool detail matters
Most malware has to bring its own logic for finding secrets. An AI coding CLI already knows how to search a file system, read files, run commands and summarise what it found, and it’s already authenticated. If a script can start it in a mode that never asks for confirmation, the script gets a capable assistant for free.
Two things limit that:
- The CLI refuses unattended “no prompts” modes, because policy has turned them off.
- The CLI can’t reach much, because it runs in a sandbox and credentials aren’t lying around for it to read.
Settings that make your AI CLI a worse tool for malware
| Tool | Setting | Effect |
|---|---|---|
| Claude Code | permissions.disableBypassPermissionsMode: "disable" in managed-settings.json |
Removes the mode where nothing asks3 |
| Gemini CLI | security.disableYoloMode: true in system settings |
Users can’t enter yolo mode4 |
| Codex CLI | Default workspace-write sandbox with network off; never --dangerously-bypass-approvals-and-sandbox |
Commands can’t write outside the workspace or reach the network5 |
Put these in system or managed settings, not user settings, so a script running as the user can’t simply edit them back. Our posts on Claude Code managed settings and agents reading .env files go further on each side.
None of these stops the postinstall script itself; it runs with your permissions regardless. They stop it from borrowing a more capable tool.
The lessons for your own repositories
The way in was a CI workflow, not an AI tool. If you maintain anything published to a registry, the advisory’s attack vector is the part to check today:1
- Search your workflows for
pull_request_target. For each one, ask whether it needs the target repository’s token. Most don’t;pull_requestruns with the fork’s limited permissions. - Never interpolate pull request fields into
run:steps. Titles, branch names and bodies are attacker-controlled text. Pass them through environment variables and quote them. - Check old branches too. Nx believes the attacker targeted an outdated branch that still had the vulnerable workflow. Fixing
masterisn’t enough if other branches can be pull request targets. - Keep publishing credentials out of reach of anything a pull request can trigger. A publish workflow should need a person or a protected environment.
If you ran an affected version
The advisory’s own steps, condensed:1
- Check your GitHub security log for repository creation, and your account for a repository named with
s1ngularity-repository. - Check for
/tmp/inventory.txt. - Run
npm ls nx; reinstall a clean version and clear npm, yarn and pnpm caches, including~/.npm/_npx. - Rotate GitHub tokens (including the
ghCLI’s, by revoking its app authorisation), npm tokens and anything in your environment variables. - Remove unfamiliar lines from
.zshrcand.bashrc. - Purge the versions from internal registries.
What we don’t know
- Which AI tools the malware actually ran, and with what result. Nx says it “attempted”; that’s all the primary source states.2
- How many machines were affected. We cite no count.
For a related case where malicious code shipped inside an AI tool itself, see the Amazon Q extension post. To check a repository’s workflows and agent configs before you trust them, run the Deedglass scan Action on pull requests, paste a config into the check page, or run npx deedglass scan (how the grade works); to try tool-call rules in your browser, open the playground.
Written with AI assistance and checked against our claims register.