dyb

TrapDoor: When the Second Hop Is Your AI Assistant

אִם יִרְצֶה הַשֵּׁם

Mini Shai-Hulud put the second hop in the CI runner, where a scanner could at least look. TrapDoor moved it into the file your agent reads on your behalf, encoded so you and the agent see different things. The boundary to defend is no longer the registry or the pipeline. It is the context file, and the question is whether you are reading the same bytes your agent is.

Socket.dev disclosed TrapDoor on May 24, 2026. SlowMist's MistEye system mapped it across three ecosystems. 34 malicious packages, 384 published versions, planted on npm, PyPI, and Crates.io, aimed at developers in crypto, DeFi, Solana, AI, and security research. Install or build one and the code ran on its own. The harvest was routine: AWS keys, GitHub tokens, OpenAI keys, SSH private keys, wallet files, browser login databases. The npm sample added Ethereum keystore cracking and remote command execution.

Two design choices are the story of this post.

Under the Bilar (2009) nth-Order Attack Framework this is a Second-Order attack, the same order as Mini Shai-Hulud. The interesting part is which two layers got subverted.

Hop System Violated assumption
1 Package registry (npm / PyPI / Crates) A published artifact behaves like what its name says. None of the three verifies behavior. Each fires the payload through its own native trigger: import on PyPI, compile on Crates, post-install on npm. Write the logic once, ship it three ways.
2 The AI coding agent's context channel CLAUDE.md and .cursorrules hold instructions the developer authored. The npm sample rewrites them, injecting directives encoded in zero-width characters. The agent reads attacker text as developer text and acts on it in later sessions.

Mini Shai-Hulud's second hop was CI and OIDC machinery, infrastructure a scanner could at least inspect. TrapDoor's second hop is the trust between a developer and their assistant. The shell RC edits and the hourly dev-env-bootstrapper re-infection are persistence on the host, operationalization of Hop 1, not separate hops.

After install, token-usage-tracker rewrites .cursorrules and CLAUDE.md in the victim's project directories. The injected payload rides in zero-width characters: invisible to a person reading the file, fully readable to the model parsing it. The same package wires Git hooks so every git pull or branch switch downloads fresh code, and edits .bashrc and .zshrc to run dev-env-bootstrapper every hour.

A context file exists so the developer can hand the agent standing instructions. The developer trusts it because they wrote it. The agent trusts it because the developer pointed it there. TrapDoor inserts a third author and hides the byline. The agent then runs the attack on the developer's behalf, in the developer's own session, against the developer's own infrastructure.

Why Code Review Misses It

A human auditor and the agent open the same CLAUDE.md and get two different files. The human sees clean instructions. The agent sees clean instructions plus an encoded directive in code points the renderer drops. Review at the surface confirms a property the agent never acts on. You cannot eyeball assurance on a file two readers parse differently by design. The check has to run on the bytes the agent consumes, after Unicode normalization, not the glyphs a human happens to see.

The Four Structural Failures

Semantic Gap. The abstraction: CLAUDE.md is project guidance the developer wrote. The implementation: a byte stream the agent parses in full, zero-width code points included. The two readers see different documents, and the gap is invisible at the layer where review happens.

Micro-State Weaponization. The lever is a handful of zero-width characters with no visible diff. The amplification is the agent's standing authority: it holds the developer's tools and tokens and executes the hidden directive at full privilege.

Trusted Process Subversion. Invoking your own assistant is the trusted, mandatory step. The payload is inert text until the agent reads the context file, and the developer triggers that read in a normal session, after any repo scan has already passed.

Systemic Latent Risk. Context files are ecosystem defaults now. Agents read them automatically, the zero-width trick is cheap, and it ports across all three registries. No targeted adversary required. The propagation logic rides the toolchain itself.

Detection

  • Markers P-2024-001, ddjidd564, dev-env-bootstrapper anywhere in CLAUDE.md, .cursorrules, Git hooks, or shell RC files
  • .cursorrules or CLAUDE.md containing zero-width code points (U+200B, U+200C, U+200D, U+FEFF)
  • Marker file ~/.local/share/.p2024_integrity; /tmp/.cargo_build_log_<pid>.hex from the Rust sample
  • Git hooks fetching raw.githubusercontent.com/ddjidd564/.../scan-bundled.js
  • Shell RC spawning dev-env-bootstrapper on an hourly schedule
  • Egress to ddjidd564.github.io, raw.githubusercontent.com/ddjidd564/..., and webhook.site UUIDs
  • Rust sample XOR key cargo-build-helper-2026

Structural Fixes

Treat agent context files as executable inputs. CLAUDE.md and .cursorrules run with your credentials the moment the agent reads them. Put them under the same change control as CI config, and diff them in normalized form that strips zero-width code points, before review and before the agent loads them.

ignore-scripts=true, and split install from release. Same as the Mini Shai-Hulud fixes: the install phase is adversarial execution. A runner that does npm install holds no publish tokens, no cloud credentials, no PATs.

Pin across all three ecosystems. Exact versions and digest pinning on npm, PyPI, and Crates, not just the one you think you depend on. TrapDoor ships the same logic to whichever registry you pull from.

Clean the known markers now. Remove affected packages, rotate every credential a compromised machine touched, then scrub the markers above from context files, hooks, and shell RC.

The Actual Lesson

npm install worked. The build step worked. CLAUDE.md did exactly what a context file is supposed to do. All of it worked as designed, and the developer's own assistant became the worm.

Mini Shai-Hulud put the second hop in the CI runner, where a scanner could at least look. TrapDoor moved it into the file your agent reads on your behalf, encoded so you and the agent see different things. The boundary to defend is no longer the registry or the pipeline. It is the context file, and the question is whether you are reading the same bytes your agent is.


Motivating source and the SlowMist analysis.

← Previous
Ricky polyglot software developer
Next →