Integrity monitoring for the files your AI coding agent obeys.
CLAUDE.md, .cursorrules, AGENTS.md, settings.json, skills, subagents, slash commands, MCP server config — these aren't documentation. They're instructions, and an agent reads and follows them every session.
Nothing in a normal toolchain notices when one of them changes. And a human reviewing the diff cannot see a zero-width space, a right-to-left override, or a Cyrillic а wearing a Latin a's face.
$ paladin check -global
DRIFT DETECTED
modified /Users/you/.claude/CLAUDE.md
^ 1 invisible
14:38 U+200B invisible — zero-width space
An agent's instruction files are a persistent prompt injection vector. Anything that can append to them — a malicious dependency's postinstall, a repo you cloned, a plugin from a marketplace, a compromised dotfile sync — gets durable influence over every future session, in a file you will never diff again.
The industry has a name for the control: MITRE ATLAS lists it as AML.M0031 Memory Hardening. Almost nobody implements it.
Hashing is exactly the right tool here, and worth being precise about why: it fails at detecting prompt injection in arbitrary text, because natural language varies infinitely. It works on these files because they're known-good and should be byte-identical until you deliberately change them.
go install github.com/Grace/paladin@latestSingle static binary, no dependencies, no runtime.
paladin init -global # record a baseline of what you trust today
paladin check -global # compare against it
paladin scan -global # hunt hidden characters, no baseline needed
paladin list -global # show what's being watchedWithout -global it walks the current directory, so per-repo baselines work too — useful when you clone something that ships its own CLAUDE.md.
An unsigned manifest sitting next to the files it protects is only tamper-evident to someone who isn't looking. Sign it, and keep the key somewhere the files aren't:
export PALADIN_KEY="$(openssl rand -hex 32)" # store this in your password manager
paladin init -globalcheck then fails loudly if the baseline itself was edited.
Claude Code runs a SessionStart hook before you type anything:
{
"hooks": {
"SessionStart": [{
"hooks": [{ "type": "command", "command": "paladin check -global" }]
}]
}
}Now every session begins by verifying its own instructions.
| Code | Meaning |
|---|---|
0 |
clean |
1 |
drift — files added, removed, or modified |
2 |
hidden characters found |
3 |
error |
Suitable for CI. A pull request that touches CLAUDE.md is a pull request worth failing on.
Drift — any byte change to a watched file, plus files appearing or disappearing.
Invisible characters — zero-width spaces and joiners, word joiners, soft hyphens, BOMs, and other Unicode format characters that render as nothing and read as instructions.
Bidirectional controls — the Trojan Source class, where text is reordered on screen so what you read is not what the file says.
Homoglyphs — Cyrillic and Greek letters impersonating Latin ones.
It knows that compound emoji (👨💻, 🤷♀) are built from zero-width joiners and doesn't flag them. That false positive was found running against a real config directory, not imagined.
If your instruction files are symlinked in from somewhere else — aimd keeps them in a private Git store, and stow, chezmoi and yadm do the same thing for dotfiles generally — paladin follows the link and hashes the file the agent actually reads.
That holds for a symlinked directory too, not just a symlinked file, which is
the case that matters: a ~/.claude that is really a link would otherwise be
walked as an empty directory, and an integrity tool reporting success while
monitoring nothing is worse than no tool at all.
Entries are recorded under the path you know — .claude/skills/deploy.md —
rather than wherever the store happens to live, so a baseline stays readable
and survives moving the store.
The division of labour is clean: aimd decides where the file lives and who can see it, paladin decides whether it still says what you approved. Neither answers the other's question.
A targeted attacker with equal privileges. If someone can write your instruction files, they can usually write your baseline and edit the hook that checks it. Signing raises the bar; it doesn't eliminate this. paladin is built for opportunistic tampering — the malicious dependency, the poisoned repo, the plugin you installed and forgot — which is the overwhelming majority of real exposure.
A compromised host reporting honestly on itself. The strong version puts the baseline off-machine and treats a missing check-in as an alert. That's not built yet.
Semantic attacks. A plainly-worded malicious instruction with no hidden characters is drift, and paladin will tell you the file changed. It will not tell you the new sentence is bad.
Issues and pull requests welcome, and GitHub Discussions is the right place for questions and design arguments.
Using this for something real? I would like to hear about it — what you pointed it at, what broke, and what you needed that is not there.
MIT.