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.render and c4d.render never reuse an output folder, never save the project, and prove the source hash did not change (source.unchanged in render.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 recipe checks 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/MographJailed and every request is added to a hash chain that audit.verify checks.

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, and curl | zsh runs 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.json receipt and updates a .latest.json pointer.

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.