DEV Community

Cover image for Scanning 525 Skills and 153 Claude Code & Codex Logs for Prompt-Injection & Repo-Open Traps
Prince Jain
Prince Jain

Posted on

Scanning 525 Skills and 153 Claude Code & Codex Logs for Prompt-Injection & Repo-Open Traps

The discussion on r/ClaudeAI (1xozfcv) about why 98% of the 16,060 open GitHub issues on anthropics/claude-code cannot simply be handed to autonomous triage agents hit on a real engineering problem: untrusted input poisoning.

Whenever you point Claude Code or Codex CLI at an external GitHub issue (gh issue view), a cloned PR branch, or a third-party SKILL.md file, four classes of traps can hijack the agent:

  1. Repo-open config hooks: .claude/settings.json (hooks, apiKeyHelper, statusLine), .mcp.json, or .vscode/tasks.json ("runOn": "folderOpen") inside a cloned repo that run commands on startup.
  2. Invisible Unicode steganography: Unicode Tag characters (U+E0000 to U+E007F), bidirectional overrides (U+202A to U+202E), and standalone zero-width spaces (U+200B).
  3. Spoofed <system-reminder> and </tool_result> delimiters inside untrusted issue bodies or web responses.
  4. Hidden HTML comment instructions & remote shell pipes (hidden HTML comment blocks with override instructions or curl ... | bash inside skill files).

Three false-positive traps and blind spots in my first scanner

When I ran my first scanner script across 525 local skill markdown files, 16 workspace repos, and 153 local Claude Code and Codex CLI transcripts, three things broke:

  1. Claude Code's 1,526 native type: "attachment" <system-reminder> rows: Scanning raw .jsonl lines for <system-reminder> to detect delimiter spoofing flagged 1,526 rows across 125 sessions because Claude Code writes its own internal system reminders as type: "attachment" and type: "api-request-*" records. Filtering out native attachment records (RTR012) isolated untrusted tool_result payloads (RTR008).
  2. Codex CLI's custom_tool_call_output nested object payloads (142 zero-width characters across 5 sessions): Unlike function_call_output (where payload.output is a string), Codex CLI's custom_tool_call_output stores payload.output as a nested JSON object. My initial String(payload.output) call turned the tool output into "[object Object]" and missed 138 of the 142 standalone U+200B and U+200E characters ingested across 15 tool outputs (RTR011).
  3. Composite Emoji U+200D ZWJ vs standalone U+200B (RTR014): A raw zero-width regex flagged composite emojis (1 in notebook-guidance/SKILL.md) until I separated Emoji Zero-Width Joiner sequences (<Emoji> U+200D <Emoji>) from standalone U+200B spaces (2 in signs-of-ai-writing.md, RTR007) and 1 live curl -fsSL https://composio.dev/install | bash remote shell pipe in composio-cli/SKILL.md (RTR005).

Running repotrap

I published the zero-dependency scanner as repotrap (npx repotrap):

npx repotrap
Enter fullscreen mode Exit fullscreen mode

You can also pre-scan a live GitHub issue or PR via the gh-issue option (npx repotrap with gh-issue owner/repo#123) before letting an agent read it, or run emit-hook mode to generate a deterministic PostToolUse sanitizer hook for ~/.claude/settings.json.

Limitations

  • repotrap deterministically catches invisible Unicode (U+E0000 tags, Bidi overrides, standalone U+200B), spoofed XML/system delimiters, hidden HTML comment instructions, curl | sh skill pipes, and .claude/.mcp.json/.vscode repo-open hooks. It cannot prove whether plain natural-language prose without structural markers or override phrases will sway a model.

GitHub: https://github.com/princejain756/repotrap
npm: https://www.npmjs.com/package/repotrap

Top comments (1)

Collapse
 
analista_83 profile image
Sammi De Blas •

An AGENTS.md checked into the repo is a supply chain input, not documentation. Treating agent instructions as untrusted content and pinning the hash of that file in CI would catch the class of attack described here before the agent ever executes it.