Skip to main content
Mockingbird is a single SwiftPM executable target, packaged into an .app bundle by scripts/build-app.sh. It has no dependencies, no .xcodeproj and makes no network calls.

How a scan works

AppModel.scan goes through the rule list and evaluates each rule off the main actor, so the progress bar stays accurate. Then it measures the posture numbers once and adds a Scan record. A shell rule’s check is appended to DefaultRules.prelude and run with /bin/bash -c, with a 25-second timeout. The prelude provides helper functions, which keeps each probe short enough to read and edit in the app.

Probe helpers

Exit codes

The probe’s stdout and stderr become the evidence shown in the app.

Detecting blank cheques

mb_subsuming exists because matching the exact strings the doctrine names is not enough. Bash(sudo sqlite3:*), Bash(timeout 30 python3:*) and Bash(/usr/bin/python3 -c ' *) are all blank cheques, and none of them match ^Bash\(sqlite3:\*\)$. The helper splits each entry into tokens and finds the first token whose basename is an interpreter. Then it checks what follows. A bare *, a trailing :*, -c, -e or stdin all count as blank cheques. A spelled-out command does not, because N5 explicitly tells you to keep a few of those. Fixes for these rules use the same detector instead of a regex of their own. An earlier version used a separate pattern, and its sh alternative matched the sh in ./build.sh. That removed a grant that was never a blank cheque. Matching the whole basename keeps build.sh, du -sh and node --version out. Sharing one detector also means a probe can’t report a finding its own fix can’t fix.

Scoring

A rule’s severity is its weight: critical 10, high 6, medium 3, low 1. For each group of rules, possible is the total weight of every enabled rule. earned is the sum of credit × weight, where credit is 1.0 for holding, 0.5 for partial and 0 otherwise. Disabled rules are left out of the total, so turning a rule off lowers the maximum score instead of inflating your score. The same function scores the whole pack, a single invariant or a single phase.

Drift

Each scan records:
  • SHA-256 prefixes of settings.json, settings.local.json, the guard hook and CLAUDE.md
  • The size and modification time of the T0 SQLite file triplet (the database plus its -wal and -shm files)
  • The number of allow, deny and ask entries
Comparing two scans in a row can produce four kinds of event:
  1. A rule that was holding stopped holding.
  2. A config file’s fingerprint changed.
  3. A file disappeared.
  4. The T0 store changed.
The p2.t0-movement-attributable rule goes further. It uses lsof to check whether any process has the original open. That separates growth while the recorder is running from growth with no process attached.

Persistence

Mockingbird stores plain JSON under ~/Library/Application Support/Mockingbird/: Enforcement state is stored separately at ~/.config/mockingbird/enforcement.json, because the guard reads it on every call.
The rule pack is written only once. Defaults go to rules.json on first launch, and Mockingbird never reads them from the app binary again. If you change a rule in Swift, existing installs won’t see the change until you use Settings → Restore built-in pack.

Build

scripts/make-icon.swift finds the accent-colored glyph in the brand logo and removes its white background using the blue channel. A plain crop would include the “D” from the wordmark. The script then renders the iconset and a transparent mark for the sidebar.