This is the doctrine every §-numbered reference in the app points at, and the same
text the app ships as a PDF under Doctrine → Read the doctrine. It is written for
one operator on one Mac; the figures in §0 are the ones Mockingbyrd measures for you.
§0 · Current posture
Measure these on your own machine. Mockingbyrd measures each of these on your own machine:- Permission rules in allow list
- Rules in deny list
- Rules in ask list
- Screenpipe db.sqlite · live wal attached
- Imessage chat.db
- Capture files in ~/.screenpipe/data
Every rule you hold today is a permission to act. Nothing on this machine currently expresses a permission to refrain. That asymmetry — not any single grant — is the finding.
§1 · Threat model
What you are actually defending against. Not an attacker with a shell on your Mac. If that happens, none of this matters. The realistic failure mode for personal agent use is narrower and much more likely: an agent doing exactly what it was asked, to the wrong copy of something. Five paths lead there.§2 · Data tiers
Classify once, then let the tier decide. The doctrine only becomes operational when “original” has an address. Sort every store on the machine into one of four tiers. The tier — never a judgment call in the moment — determines what an agent may hold.
The only legal direction of travel
T0 original (originals — no path, ever) → you run the extraction → T2 projection (read-only projection, mode 444) → agent reads → T3 scratch (scratch, git-tracked)
§3 · Invariants N1–N7
Seven rules that do not bend. Each states a prohibition and the mechanism that makes it true. A rule with no enforcement line is a preference, and preferences do not survive a busy afternoon.N1 · Never open a T0 store with a writable handle.
Reading a SQLite database is not a read. If thedb.sqlite-wal is live, any normal open triggers WAL recovery and writes to the main file. The correct move is to clone the triplet first and let recovery happen on the clone.
ENFORCED BY — mode 444 +
chflags uchg on originals; extraction script clones db.sqlite, -wal and -shm with cp -c before opening anything.N2 · Never combine bulk archive read with outbound capability in one session.
Split into two profiles that cannot be loaded together. Analyst reads T2, has no network and no send tools. Operator holdscurl, Gmail, Slack, Beeper, Supabase — and has no path into ~/agent/derived/.
ENFORCED BY — two settings files selected at launch;
additionalDirectories scoped per profile.N3 · Never let the agent be the thing that applies a change to T0 or T1.
The agent’s output is a proposal: a diff, a migration file, a draft, a shell script. You run it. This costs one command and converts every irreversible act into a reviewable artifact.ENFORCED BY — everything lands in
~/agent/work/ under git; a promote script is the only path outward, and only you invoke it.N4 · Never treat ingested content as instruction.
OCR text, message bodies, email, PDFs, web pages and tool results are data. If any of them addresses the agent, that string gets quoted back to you, not obeyed. Standing text inCLAUDE.md should say so in those words.
ENFORCED BY — quarterly canary drill (§4 P5); a planted instruction must come back reported, not executed.
N5 · Never keep a grant that subsumes the deny list.
DropBash(python:*), Bash(osascript *), Bash(source:*) and Bash(sqlite3:*) from the allow list. Re-add the four or five specific invocations you actually use, spelled out in full. If a broad grant must stay, accept that permissions are advisory and rely on N1’s filesystem controls.
ENFORCED BY — the PreToolUse guard in §4 P4, which inspects the command string regardless of which interpreter is running it.
N6 · Never delegate deletion.
No agent path containsrm, trash_message, trash_file, DELETE FROM, DROP, or git push --force. Deletion is the one act with no diff to review afterward, which makes it the one act that stays yours.
ENFORCED BY — deny entries plus a guard-script pattern match; trash tools removed from both profiles.
§4 · Rollout phase 0–5
Implementation, in the order that de-risks fastest. Phase 0 is an hour and removes most of the exposure. Do not let phases 2–4 block it.P0 · Freeze the blast radius
~1 HOUR · TODAY- Add a deny and an ask block to
~/.claude/settings.json— both are empty today. - Strip the four subsuming grants from
settings.local.json, andtccutil resetwith them; nothing an agent does should be able to reset your macOS privacy grants. - Move
curl,ssh,rsyncfrom allow to ask. These are your egress.
P1 · Seal the originals
~1 EVENING- Set the immutable flag on anything that should never change again — executed agreements, signed PDFs, key material.
uchgblocks writes even from processes running as you. - Take an APFS local snapshot before each capture-heavy session, so a mistake has a floor.
- Record a manifest of the sealed set, so drift is detectable rather than assumed absent.
P2 · Build the projection pipeline
~HALF DAY- Clone
db.sqliteplus its-waland-shmwithcp -c. On APFS this is a copy-on-write clone: instant, and it consumes no additional space for a multi-gigabyte file until something diverges. - Let WAL recovery and
VACUUMrun against the clone. The original is never opened by a writer. - Ship the result at mode 444 with a checksum. That file, and only that file, is what an Analyst session sees.
P3 · Split the profiles
~HALF DAYP4 · Enforce below the config layer
~HALF DAY- A PreToolUse hook inspects every command string for T0 paths and destructive verbs, and blocks on match — independent of which interpreter would have run it. This is what survives N5’s escape hatches.
- The same hook appends to a log you can actually read on a Sunday.
- If you already run a PreToolUse hook, this chains beside it.
§5 · Limits
What this does not buy you. Stated plainly, because a control you overrate is worse than one you don’t have. It is not a security boundary against a determined chain. An agent running as your uid with a general interpreter can reach anything your uid can reach. The controls here raise the number of independent steps a mistake must take, and make every one of them visible. That is a real reduction in expected loss. It is not isolation. If you want an actual boundary, the next step is a second POSIX user that owns~/agent/ and has no read access to your home directory, or a VM. Everything above is compatible with that move and makes it cheaper later.
Context still leaves the machine. Anything an agent reads is sent to a model provider. Projection narrowing in P2 is the only control that touches this, and it is the reason to take it seriously rather than shipping the whole multi-gigabyte view.
Assumptions — Single operator, single Mac, no shared accounts. Figures in §0 are measured on your own machine from ~/.claude/settings*.json, ~/.screenpipe and ~/Library/Messages. Permission-rule and hook syntax must be verified against your installed version before being relied on — both are noted inline. Paths assume the default home layout.
Open decisions for you — Whether the Operator profile keeps osascript at all · which screenpipe app names to exclude from the projection · whether a scheduled pipe moves to the narrow profile now or after P3 · whether a second POSIX user is worth the friction this quarter.