Skip to content

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.

Tags

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.

Team