Skip to content
GracePublic

About

Integrity monitoring for the files your AI coding agent obeys — detects drift, invisible characters, bidi controls, and homoglyphs in CLAUDE.md, .cursorrules, settings.json and friends.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

paladin

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

Why

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.

Install

go install github.com/Grace/paladin@latest

Single static binary, no dependencies, no runtime.

Use

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 watched

Without -global it walks the current directory, so per-repo baselines work too — useful when you clone something that ships its own CLAUDE.md.

Sign the baseline

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 -global

check then fails loudly if the baseline itself was edited.

Wire it into your agent

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.

Exit codes

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.

What it catches

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.

Private overlays and dotfile managers

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.

What it does not catch

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.

Contributing and contact

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.

grace@gracefulco.de

License

MIT.

About

Integrity monitoring for the files your AI coding agent obeys — detects drift, invisible characters, bidi controls, and homoglyphs in CLAUDE.md, .cursorrules, settings.json and friends.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages