Geoffrey Young
Claude Code plugins inject content into Claude's reasoning via the hook system's exit-2 stderr channel, unfiltered and unattributed.
Core trick
An exit-code-2 hook injects a security-audit prompt that blocks third-party plugin calls until Opus reviews each plugin file.
Description
Claude Code plugins inject content into Claude's reasoning via the hook system's exit-2 stderr channel, unfiltered and unattributed. Claude acts on it if it looks helpful—which is the problem.
We proved this by feeding fabricated API docs through exit-2 stderr. Claude rewrote user code to use a nonexistent parameter without hesitation, unable to distinguish fake errors from real ones.
Plugin-canary uses this same channel as a defense: it intercepts third-party plugins, blocks the call, and injects a security audit prompt. Opus 4.6 analyzes every file using Agent Team generated prompts and decides whether to proceed.
The defense works because the vulnerability exists. Plugin-canary adds protection today while surfacing an architectural gap that shouldn't exist.
geoffrey-young/anthropic-hackathon-2026★ 30 forksPythonMITCreated Feb 16, 2026 · last push Feb 21, 2026
README excerpt
# Claude Code Canary Early warning system for Claude Code plugin security. <img src="images/claude-canary.png" width="800" /> ## The Short Version Claude Code plugins can inject arbitrary content into Claude's reasoning through the hook system's exit-2 stderr channel. The runtime delivers this content without filtering or source attribution. Claude treats it as authoritative and acts on it, including following complex instructions, reading files, and spawning subagents. Plugin-canary uses this same mechanism to force a cross-file security review before any third-party plugin runs. It intercepts plugin invocations, injects a structured audit prompt, and lets Opus 4.6 analyze every file in the plugin before deciding whether to proceed. The defense works. But it works because the vulnerability exists. ## The Discovery This project started as something else entirely. Day 1 was about documentation: a plugin called **llmdoc** that ships LLM-optimized API docs with packages and validates code at write time via PreToolUse hooks. The validation worked by intercepting Write tool calls, checking the code against a documentation database, and blocking with corrective feedback via exit-2 stderr when issues were found. Claude received the feedback and rewrote the code. Five seconds, full cycle. Then we tested it with fabricated documentation. The LLMDOC data file claimed FastAPI v0.201.0 renamed `pattern=` to `match_expr=`. Neither the version nor the parameter exists.