The s1ngularity compromise of the Nx build toolchain in August 2025 did not stop at stealing npm credentials. Its postinstall payload detected whichever AI coding assistant was installed on the host, Claude Code, Gemini, or Amazon Q, armed each with a permission-bypass flag, and asked it to write a list of wallet files, SSH keys, and tokens to disk. An agent installed for developer productivity became a reconnaissance tool its owner never invoked.
What did s1ngularity compromise, and how was the npm token stolen?
s1ngularity was a supply-chain compromise of the Nx monorepo build system (published by Nrwl) in which attackers obtained Nx’s npm publishing token and pushed eight malicious package versions to npm starting late on August 26, 2025. The root cause was a GitHub Actions injection. Nx’s pipeline used a pull_request_target workflow that echoed unsanitized pull-request titles into a command context, and that trigger granted an elevated GITHUB_TOKEN with read/write access to the repository. According to Wiz’s analysis, the attacker used that elevated token to trigger the publish.yml workflow, which publishes to npm using an npm token, and exfiltrated the credential in the process. (Wiz) The injection is the root cause; the AI-CLI reconnaissance is a payload layered on top of it.
The malicious versions spanned two ranges, nx 20.9.0 through 20.12.0 and nx 21.5.0 through 21.8.0, alongside the scoped packages @nx/devkit, @nx/js, @nx/workspace, @nx/node, @nx/eslint, @nx/key, and @nx/enterprise-cloud. How long they stayed live depends on who measured it: StepSecurity clocks the malicious versions at just over five hours live from approximately 10:32 PM UTC on August 26, the AI Incident Database records 5 hours 20 minutes, and Nx’s own postmortem says 4 hours. (StepSecurity, the AI Incident Database, Nx’s postmortem) The victim’s account is the shortest of the three. The package sees over 3.5 million weekly downloads. (The Hacker News)
How did the payload invoke Claude, Gemini, and Amazon Q?
The malicious postinstall hook ran a file named telemetry.js, which on Linux and macOS hosts detected locally-installed AI coding CLIs and invoked each with that tool’s own permission-bypass flag. According to StepSecurity’s teardown, telemetry.js shelled out to three agents in turn: claude with --dangerously-skip-permissions, gemini with --yolo, and q (Amazon Q) with --trust-all-tools. (StepSecurity, Wiz) Each was given a prompt instructing it to walk the filesystem and write the paths of matching secret and wallet files into /tmp/inventory.txt. The agent performed the enumeration; the malware pointed it at a target and collected the result.
| CLI | Vendor | Bypass flag(s) | What it suppresses |
|---|---|---|---|
| Claude Code | Anthropic | --dangerously-skip-permissions | Per-command permission prompts |
| Gemini CLI | --yolo | Tool-call approvals | |
| q | Amazon | --trust-all-tools | Tool-trust checks |
This is the novel step, and it deserves separating from the token theft. Every other operation in the payload, harvesting ~/.npmrc, reading environment variables, or grepping for token prefixes, could be done with shell one-liners. Borrowing the AI CLIs bought the attacker nothing the shell could not already do, with one possible exception: process legitimacy. A process named claude or gemini touching files looks, at a glance, like a developer’s own workflow. This was not a proof of concept that failed in the field: Wiz reports observing the AI-driven activity succeed in hundreds of cases, although AI provider guardrails interceded at times. (Wiz) Whether process legitimacy was the actual goal is not something the sources establish.
What secrets did the payload exfiltrate, and through what channel?
telemetry.js harvested GitHub tokens, npm credentials, SSH private keys, environment variables, .env files, and cryptocurrency wallet keystores, then exfiltrated the lot through public GitHub repositories the attacker created with the stolen token. StepSecurity names SSH keys, npm tokens, and .gitconfig files among the haul; Wiz adds GitHub tokens and cryptocurrency wallets. (StepSecurity, Wiz)
The exfiltration channel was brazen. The payload used the stolen GitHub token to create public repositories with names like s1ngularity-repository, committed double- and triple-base64-encoded dumps, and left them world-readable. By the time GitHub disabled the attacker-controlled repositories around 9 AM UTC on August 27, after an exposure window Wiz puts at roughly eight hours, one later count put the leak at 2,349 GitHub, cloud, and AI credentials. (Wiz, The Hacker News) Collapsing theft and exfiltration into a single credential meant there was no separate command-and-control channel to detect. The defender’s signal was a public repository full of base64, not a suspicious outbound connection.
Why are the permission-bypass flags the load-bearing exploit?
The three flags are the part that makes borrowed-agent reconnaissance work, because each tells its CLI to suppress the confirmation prompts that would otherwise stop a file-reading or shell command and ask for human approval. These flags exist for legitimate reasons: automated pipelines, batch tasks, and developers who don’t want to approve every read. A CLI running with one of them will execute whatever prompt it is handed, including a prompt handed to it by a postinstall script. The bypass is not a bug any of the three vendors shipped. It is a documented, intentionally-shipped affordance that an attacker repurposed.
The defense implication is narrow and worth stating plainly. A permission-bypassed local agent is a general-purpose shell with a model sitting in front of it. Running one of those flags on a machine that also holds production secrets, wallet keystores, or live deploy tokens is the equivalent of sourcing those credentials into an unattended shell. The agent’s guardrails are exactly the thing the flag removes.
What about persistence, sabotage, and the second wave?
Beyond reconnaissance, the payload sabotaged infected hosts, and the attackers staged a follow-on operation on August 28 that reached private repositories the original npm window never touched. For persistence and sabotage, telemetry.js appended sudo shutdown -h 0 to both ~/.bashrc and ~/.zshrc, so every new terminal session attempted an immediate system shutdown. (Wiz)
The August 28 second wave abused long-lived, leaked GitHub CLI OAuth credentials to rename private organization repositories to s1ngularity-repository-{random}, fork them into compromised user accounts, and flip them public. (StepSecurity) Wiz traced the operation to a single-threaded automation running from roughly 4 PM UTC on August 28 to 2 AM UTC on August 29; it reached over 400 users and organizations and published more than 5,500 private repositories. (Wiz) This extended the blast radius past the npm window into organizations whose only connection to the incident was a leaked gh token. A stolen long-lived credential outlives the incident that produced it, so rotation has to be sweeping rather than scoped to the obviously affected packages.
What did Nx harden, and what is the reusable checklist?
Nx’s remediation, documented in its postmortem, is a reusable publishing-hardening playbook for any open-source maintainer whose install volume would amplify a compromise: replace long-lived publish tokens with provenance-based trust, gate publishing behind CI and a human, and treat the workflow itself as the attack surface. The specific changes were to replace npm-token publishing with NPM Trusted Publishers via OIDC, enforce CI-only publishing with manual 2FA, disable pipeline runs for all external contributors (not only first-timers), add provenance verification in Nx and Nx Console, and publish a SECURITY.md. (Nx’s postmortem)
The general lessons reduce to three. Stop using long-lived write tokens in CI. Treat pull_request_target as a sharp edge: echoing untrusted PR content into a run that holds write permissions is the classic footgun. Publish provenance so consumers can verify what they install. Nx was a high-profile victim precisely because several million weekly downloads made it worth the effort, and the same playbook applies to any maintainer whose package propagates into CI runners.
Is every local AI CLI now a recon primitive?
The durable lesson from s1ngularity is not the specific indicators of compromise, which are historical artifacts, but the threat-model shift: an always-on local AI agent with shell access is a scriptable primitive an attacker can invoke without ever prompting it. StepSecurity calls it “the first known case where malware harnessed developer-facing AI CLI tools,” and Wiz describes a campaign that “weaponized installed AI CLI tools” with dangerous flags for filesystem reconnaissance. (StepSecurity, Wiz) Both qualifiers should be read as “first documented,” not “first ever.” StepSecurity and Wiz are security vendors selling detection, so the absence of prior cases in their records is evidence about their catalog, not about history.
One caveat is worth flagging for anyone citing this incident. The AI Incident Database’s “Incident Date” field reads 2025-08-21, while its body states the packages went live August 26-27. The earlier date has a candidate explanation: the vulnerable pull-request validation workflow entered Nx’s repository on August 21, 2025, per the maintainers’ advisory, so the field appears to track the root cause rather than the malicious releases. (the AI Incident Database, The Hacker News)
The architectural response is the part that travels. Scope each agent’s permissions to the minimum it needs for the task at hand. Sandbox its filesystem reach so it cannot read ~/.ssh, wallet directories, or credential files unrelated to the work. Never invoke a bypass flag on a secret-bearing host. Prefer short-lived, narrowly-scoped credentials so a stolen token expires before it can be exfiltrated and replayed. The borrowed-agent loop in s1ngularity worked because three CLIs were sitting on the box, each willing to run unsupervised against the filesystem. Removing any one of those three conditions, the bypass flag, the filesystem reach, or the credential’s longevity, breaks the loop.
Frequently Asked Questions
Did the AI-CLI reconnaissance fire on every infected host?
No. telemetry.js exited its AI-CLI branch early on win32, so the Claude, Gemini, and Q invocations only fired on Linux and macOS. The borrowed-agent recon surface is therefore confined to Unix developer boxes, where those CLIs are most likely to sit alongside the build toolchain; Windows hosts were still exposed to the rest of the payload.
What credentials and wallet types should incident response teams rotate beyond the npm token?
telemetry.js matched gh auth token output against the gho_ and ghp_ prefixes and read ~/.npmrc, id_rsa, .env files, and environment variables. Its wallet sweep targeted MetaMask keystores plus Electrum, Ledger, Trezor, Exodus, Phantom, and Solflare files. Any host that ran a malicious version needs all of these rotated, not just the publishing credentials.
How does the AI Incident Database classify s1ngularity?
It logs the incident as number 1210 under Risk Domain ‘Privacy & Security’ and subdomain 2.2, ‘AI system security vulnerabilities and attacks,’ tagging the entity as AI, timing as post-deployment, and intent as unintentional. That taxonomy pins the AI relevance on the CLI recon layer rather than the npm token theft. Its Incident Date field reads 2025-08-21, which conflicts with the August 26-27 package window its own narrative describes; August 21 is the day the exploitable workflow was added to the repository, per the maintainers’ advisory.
Why did the August 28 follow-on reach organizations that never installed the malicious packages?
It ran on long-lived leaked GitHub CLI OAuth credentials, which outlive the npm publishing token and carry broad repository scope by default. One such token could rename and publicize private repositories in organizations whose only link to the incident was an unrelated credential leak. Containment has to sweep every long-lived GitHub credential on the host, not just the Nx publishing secrets.
What indicators of compromise should defenders grep for on an affected host?
The durable signatures are the telemetry.js postinstall script, the /tmp/inventory.txt output file, public repositories named s1ngularity-repository with optional -0 or -1 suffixes, and triple-base64 dumps committed to a results.b64 file. Nx’s remediation checklist also instructs users to grep ~/.bashrc and ~/.zshrc for the literal string sudo shutdown -h 0.