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.

SetupWhat 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