OrbitalReg
Sign in →

Supply-chain case study

Threat analysis · 28 Sep 2026

TrapDoor: the attack that talks to your AI assistant.

In May 2026, one campaign published 34 packages across npm, PyPI and crates.io whose real payload wasn't aimed at your runtime at all. It was aimed at your coding assistant: instructions hidden in .cursorrules and CLAUDE.md files, written in zero-width Unicode characters no human reviewer can see. The assistant reads them, helpfully "runs a security scan" — and exfiltrates SSH keys, cloud credentials and tokens. Zero CVEs involved, so version-based scanners report a clean bill of health. Here is how it worked — and, honestly, which layer actually stops it.

If your dependencies ever included the TrapDoor packages, act first, read second. Treat SSH keys, AWS/GCP/Azure credentials, GitHub tokens and browser-stored secrets on any machine where an AI assistant ran against the infected checkout as exposed — rotate them. Then grep your repos and node_modules for zero-width characters in assistant rule files (.cursorrules, CLAUDE.md, AGENTS.md): they have no business being there.

What happened

The reviewer sees nothing. The assistant sees orders.

The packages themselves looked plausible — helpers and SDK shims under fresh publisher accounts, spread deliberately across three ecosystems. The novel part sat in the repository files modern development tooling treats as configuration: assistant rule files. Between the visible characters, the attackers embedded instructions encoded in zero-width Unicode (U+200B, U+200C, U+200D, U+FEFF) — text that renders as nothing in an editor, a diff, or a code review.

An AI coding assistant, told by convention to honour .cursorrules or CLAUDE.md, parses exactly those bytes. The hidden instructions told it to run what was framed as a routine security check — in reality a staged payload (trap-core.js) that collected SSH keys, AWS credentials, GitHub personal access tokens, browser profiles and cookies, cryptocurrency keystores and secrets from environment variables, and shipped them to attacker infrastructure hidden behind a GitHub Pages domain. The developer saw an assistant doing assistant things.

The campaign, attributed by Socket to fresh throwaway publisher profiles, spanned 21 npm packages (335 versions), 7 PyPI packages and 6 crates — one shared payload, one XOR key, one campaign marker across all three registries. And crucially: no CVE existed at any point. There was no vulnerable version to detect, because the malice was the product. A scanner that answers "are any of my versions on an advisory list?" answers, truthfully and uselessly: no.

34

malicious packages in one coordinated campaign, 384 poisoned versions

3

registries at once — npm, PyPI and crates.io, one shared payload

0

CVEs involved — version-based scanning had nothing to match against

U+200B

zero-width characters carried the instructions — invisible in any code review

The OrbitalReg defence

What a curated registry actually changes.

✓Deny-by-default curation

Every TrapDoor package came from a fresh, unknown publisher account. Behind a curated proxy, a new dependency from an unvetted publisher doesn't resolve until someone approves it — and "who is asdxzxc and why does this SDK shim ship a .cursorrules file?" is a question an approval step forces before the assistant ever sees the bytes.

✓Quarantine for the freshly published

All 384 versions were new publishes from young accounts. A hold on new upstream versions buys the window in which researchers like Socket do their work — by the time the hold expires, a campaign this loud is already on the malware advisory feeds.

✓One kill switch, three ecosystems

TrapDoor's cross-registry spread is precisely the case for one policy plane over all formats: the moment the packages landed in the OSV/malware feeds, a single policy change returns 403 for every one of them — npm, PyPI and Cargo alike — instead of three separate tools with three separate block lists.

✓Audit trail: who pulled it?

Zero-CVE malice makes the response question harder, not easier: there is no "affected versions" list to diff against. The append-only pull log answers "which builds, which machines, which day" in one query — which is your credential-rotation scope for the first hour.

The honest caveat. A registry cannot read the attacker's mind, and a CVE gate is blind here by definition — there was never an advisory to gate on until researchers published one. The durable fixes sit in how you run assistants: treat rule files from dependencies as untrusted input, not configuration; lint repos and packages for zero-width characters (a two-line CI check); and run agents with the least privilege you'd give any contractor — no ambient SSH keys, no long-lived cloud credentials in the environment they read. We wrote about the governance side in the field guide on governing agent skills, and about AI-era package risks in the slopsquatting case study. What the registry changes is the front door: unknown publishers are an approval, not a default, and the fleet-wide 403 turns "hope every team saw the advisory" into one policy change.

Indicators of compromise

What to check in your checkouts and egress logs.

Indicators compiled from Socket's disclosure and Phoenix Security's analysis of 22–25 May 2026. Treat these as a starting point, not a complete list, and cross-check the sources below.

Sources

Your AI assistants pull dependencies too.

Put one curated front door before npm, PyPI and Cargo.

Rico, the founder, walks through a curation + quarantine + pull-gate layout across your formats — one policy plane, so a cross-ecosystem campaign meets one door instead of three.