feat(instructions-audit): audit loaded instruction files for hidden directives - #40
Merged
Conversation
Verified live that current Claude Code ignores continue:false on InstructionsLoaded (the hook fired and logged, the session still answered), so the halt is now enforced by construction: a poisoned load writes a per-session flag, and the same script registered on UserPromptSubmit and PreToolUse blocks every prompt and tool call for that session until a human fixes the file or deletes the named flag. continue:false is still emitted for builds that honor it. End-to-end verified both ways with a headless session: poisoned CLAUDE.md locks, clean file answers normally. Detection additions: Unicode tag characters (invisible ASCII smuggling, astral-aware scan), invisible math operators, combining grapheme joiner, Mongolian vowel separator, variation-selector runs of 4+, and direction marks at strict. Excerpt sanitizer strips the new ranges too. 17 new tests; badge synced to 1499.
karanb192
force-pushed
the
feat/instructions-audit
branch
from
August 18, 2026 03:33
f2fcfc2 to
645397b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The TrapDoor supply-chain campaign (May 2026) dropped poisoned
CLAUDE.mdand.cursorrulesfiles through npm, PyPI, and crates.io packages. The hostile directives were hidden with zero width Unicode (U+200B, U+200C, U+200D, U+FEFF), so the file looked normal in an editor while the coding agent read and followed instructions the developer never saw: https://phoenix.security/trapdoor-supply-chain-ai-poisoning-npm-pypi-crates/OWASP's GenAI LLM Top 10 2026 tracks this risk class as LLM08:2026 Hidden Context Exposure: https://genai.owasp.org/resource/owasp-genai-llm-top-10-2026/
This PR adds
instructions-audit, which audits every CLAUDE.md /.claude/rules/*.mdfile as Claude Code loads it and locks the session before poisoned instructions get acted on.Enforcement: verified live, not assumed
The docs say
InstructionsLoadedhas no decision control and its exit code is ignored (https://code.claude.com/docs/en/hooks), and a live test showed more: current builds ignore even the universalcontinue: falseon this event. The hook fired, logged, and emitted the halt fields, and a headless session with a poisoned CLAUDE.md still answered normally. So the halt is now enforced by construction instead:~/.claude/hooks-state/instructions-audit/<session>.json). It still emitscontinue: false+stopReasonfor builds that honor the universal fields.claude -p: poisoned CLAUDE.md getsUserPromptSubmit operation blocked by hookwith the lock message; a clean file answers normally.HOOK_AUDIT_WARN_ONLY=truereports without locking.Detections
bidi-controltag-charzero-width-charzero-width-joinervariation-selector-runbase64-execsecret-read-directivesecret-exfilcurl-pipe-shellsettings-tamper.claude/settings.jsonhook-tamperbidi-marksoft-hypheninstruction-file-writeSafety levels work like the other security hooks:
SAFETY_LEVELconstant in the file, plus aHOOK_AUDIT_LEVELenv override per registration.False positive posture
A hook that cries wolf on normal CLAUDE.md content gets uninstalled, so the directive rules only fire on verb plus target:
.env.example/.env.templateare exempt, mirroring protect-secrets' allowlist.session_idis sanitized before becoming a flag filename, so it cannot escape the state dir.Tests
117 new tests in
hook-scripts/tests/instructions-loaded/instructions-audit.test.js(unit, config validation, integration through real stdin/stdout with a hermetic temp HOME, and the full lockdown flow across all three events). Full suite on current main:Also adds an Instructions-Loaded section to the README hook tables and syncs the tests badge.