Notice when .claude/settings.json moves
npx owlwarden seal # record it
npx owlwarden seal --verify # …and notice when it moves
Your dependencies have a lockfile. Your agent's execution surface does not.
package-lock.json does not judge whether a package is malicious.
It records what was resolved and makes a change loud, and it works because the
diff is reviewable even when the content is not.
Nothing does that for the set of files an agent loads and executes out of your
working tree. A SessionStart hook running
node ./scripts/warm-cache.mjs is indistinguishable from a
legitimate one by inspection. What is not ambiguous is that it was not
there yesterday.
The diff is in the surface's own vocabulary
Hooks, MCP servers, and permissions are extracted, not merely hashed. A file digest tells you something changed. This tells you what.
◉ᴥ◉ surface drift / 2 changes
+ hook claude-code SessionStart node .claude/setup.mjs / not present in the seal
.claude/settings.json:4
~ mcp server claude-code docs pin: exact npx -y some-mcp@2.4.1 → unpinned npx -y some-mcp
.claude/settings.json:31
seal taken 2026-08-27T09:14:02Z by engine 1.2.0 / no signature beside the seal
surface.lock changed is a line people learn to re-run past. A
SessionStart hook was added is not.
Reformatting does not break it
Every file carries two digests: one over the bytes, one over the parsed and
canonicalised structure. Comparison uses the second, so running
prettier over your dot-directory is reported and is not drift.
Changing one character of a hook command is.
Instruction files are the exception and seal byte-for-byte. In a file whose
whole purpose is to be read by a model, whitespace is content: a reordered
paragraph in CLAUDE.md is a different instruction.
What it does not protect against
This is a detection and review control, not a containment control, and the difference decides whether you deploy it correctly.
| Setup | What it stops |
|---|---|
| Unsigned seal | Accident, drift, and opportunistic malware. Not an
attacker with code execution who can run owlwarden seal --yes
before you next look. |
| Signed, key outside the repository | Raises the bar substantially: CI verifies a signature a process writing files in your working tree cannot forge. A targeted attacker who compromises the signing key defeats it, as they defeat every signing scheme. |
Sealing is never unattended - without a terminal it refuses unless
--yes is passed. That raises the cost for whatever wrote the
drift. It does not close the hole, and this page says so rather than
footnoting it.
It also says nothing about whether your configuration is safe. It says whether it is the configuration you sealed. The ten agent rules answer the other question, and both are needed. How often those rules are wrong is its own page.
In the gate and in CI
# Drift at session start asks; mid-session it always denies, because
# configuration that changes while an agent is running was written by
# something in the session.
npx owlwarden gate --host claude-code --seal advisory
# In CI: exit 1 on drift, and refuse a seal nobody you trust vouched for.
npx owlwarden seal --verify --require-signed-seal --trust ./trust.json
What is on the surface / Claude Code setup / Wiring it into CI
Keep reading
- owlwarden turnWhich of these did I just do?
- RulesTrigger, confidence, and framework-specific fix.
- Agent configHooks, MCP servers, instructions, and editor tasks.
- owlwarden vetCheck a repository before you open it.
- owlwarden sealNotice when your agent's execution surface moves.
- ExposureWhich of these findings do I fix first?
- RuntimesDoes this fix run on Bun, Deno, or Workers?
- CoverageMapped rules and categories static analysis cannot cover.