Safety - NeuralIO444/Mograph_Jailed_OSX_CLI GitHub Wiki
Safety
MographJailed is built for production machines. The safety model isn't a policy document — it's the shape of the code.
The brand promise
It can look at everything and change nothing — unless you tell it to, and then it still won't overwrite.
How it's enforced
Tier 0 can only observe. The four observer operations read projects, lint expressions, audit plugins, and create new versioned copies. None of them edits a project in place, and project.snapshot refuses to overwrite an existing snapshot.
The scraper is proven read-only. scripts/check-scraper-readonly.sh statically rejects any After Effects DOM mutation call in the scraper source — save, remove, add, setValue, duplicate, project close, undo groups, app.executeCommand, and more. CI fails the build if any appear. Run it yourself; it prints SCRAPER_READONLY_OK.
The watcher can only snapshot. The LaunchAgent's fire script emits exactly one command type: project.snapshot. There is no code path from the watcher to renders, builds, or any Tier 1 operation.
Tier 2 doesn't exist. No in-place project mutation, no arbitrary shell, no arbitrary SQL, no network, no sudo — anywhere in the codebase. A bug can't accidentally invoke a code path that isn't there.
Local-only by default. Network volumes and unknown storage fail closed. The Standard Library wraps approved macOS primitives behind stable contracts; there is no generic shell.execute or db.query escape hatch.
The Power CLI follows the same rules
- Renders write only new folders.
ae.renderandc4d.rendernever reuse an output folder, never save the project, and prove the source hash did not change (source.unchangedinrender.json). Hosts are found only in/Applications; a request cannot name a program to run. - Host processes are contained. Closed stdin, a hard timeout, the whole process group killed on timeout, one render at a time. If Cinema 4D asks which licence to use, the run is stopped and reported — MographJailed never answers licensing prompts.
- Network volumes are never touched. Footage paths are classified from the mount table without opening them; network footage is reported as unverified or skipped, never copied or indexed.
- No SQL surface. The local index uses fixed statements with bound parameters; search words are never interpreted as query syntax.
- Recipes are data.
mj recipechecks every operation and argument against the runtime before the first step runs, and never evaluates shell syntax from a recipe file. - A tamper-evident history is available. Create
~/Library/Logs/MographJailedand every request is added to a hash chain thataudit.verifychecks.
Unattended code and downloads
- Snapshot hooks are fenced. An optional
post_snapshot_hook(see mj Command) is the one place a program you wrote runs on its own. It must be a full path to a regular file you own, executable and not writable by anyone else; it runs directly (no shell) with no input and a 30-second limit, and a hook that fails, hangs or is refused can never affect the snapshot, which is already saved. - Downloads are checked. The installer prints the download's SHA-256, checks every unpacked file against a shipped
SHA256SUMS, and can pin a release (MJ_INSTALL_REF,MJ_INSTALL_SHA256). It cannot defend against whoever controls the repository, andcurl | zshruns whatever the server returns: for the strongest assurance, download the installer, read it, then run it. - The scraper is proven read-only in CI, against a guard that rejects mutating calls, assignments to After Effects properties, and dynamic code. Computed member access (
x["set" + "Value"](…)) defeats any text search and is rejected in review.
Receipts for everything
- Every watcher fire appends one line per file to
watcher.log. - Every CLI request returns a structured JSON response.
- Every snapshot writes a
.snapshot.jsonreceipt and updates a.latest.jsonpointer.
If anything ever looks wrong, the paper trail shows exactly what ran.
Uninstall is one delete
Removing the watcher is deleting one plist (tools/watch-uninstall.zsh). No residue, no orphaned processes, and your version history is never touched.