Update in MacGuard
How the app downloads, verifies and installs the rules.
Normal flow
Drawing the diagram…
sequenceDiagram autonumber participant U as User participant M as MacGuard RuleInstaller participant S as Channel (Pages) participant V as ChannelVerifier and cosign U->>M: update rules (consent) M->>M: flock update.lock, recover interrupted swaps M->>S: latest.json M->>S: tarball, manifest, bundles, proof, checkpoint, log.jsonl (byte caps) M->>M: safe extraction in staging M->>V: manifest signature, file hashes, rules_root M->>V: checkpoint signature, inclusion, consistency, rollback check V-->>M: all checks passed M->>M: save trusted checkpoint, rename live to previous, staging to live M-->>U: rules installed
Diagram description
The user consents, MacGuard takes the update lock, downloads into a private staging folder with size caps, extracts safely, verifies the manifest signature, every hash, rules_root, the checkpoint signature, the inclusion proof and consistency with the trusted checkpoint, saves the new trusted checkpoint and activates the rules with two renames.
Mermaid code
sequenceDiagram
autonumber
participant U as User
participant M as MacGuard RuleInstaller
participant S as Channel (Pages)
participant V as ChannelVerifier and cosign
U->>M: update rules (consent)
M->>M: flock update.lock, recover interrupted swaps
M->>S: latest.json
M->>S: tarball, manifest, bundles, proof, checkpoint, log.jsonl (byte caps)
M->>M: safe extraction in staging
M->>V: manifest signature, file hashes, rules_root
M->>V: checkpoint signature, inclusion, consistency, rollback check
V-->>M: all checks passed
M->>M: save trusted checkpoint, rename live to previous, staging to live
M-->>U: rules installedThe app does not update on its own without consent. It downloads into a private staging folder with per-file size limits, extracts with limits (no links or paths outside the directory) and only activates the rules when every check has passed.
What it checks
- Sigstore signature of the manifest and the checkpoint with the expected identity and issuer.
- SHA-256 of every file against the signed manifest, and that none is extra or missing.
rules_root, the inclusion proof and that the checkpoint matches the log head in the manifest.- Consistency with the saved trusted checkpoint (rollback and fork protection).
Origins
The app downloads from GitHub Pages first (https://macguard.github.io/macguard-yara-channel/) and, only if that origin does not answer, from the Cloudflare Pages mirror (https://macguard-rules.pages.dev/). Each attempt installs from one origin end to end; it never mixes files from the two. Both serve the same signed bytes and the release is verified the same way in both cases. A verification failure does not fall through to the next origin.
YARA engine
MacGuard ships its own YARA engine 4.5.8 (with OpenSSL 3.5.9) as a universal Intel/ARM build. Before using the engine it checks the hashes of its manifest, its code signature, its version, its architecture and a self-test. Every rule file is loaded in its own namespace with the signed dependencies.
LIMIT Developer ID signing and notarization of the app, the engine and the bundled cosign are app release steps that are still pending.
Atomic install
Drawing the diagram…
sequenceDiagram autonumber participant I as RuleInstaller participant FS as Filesystem I->>FS: write journal.json I->>FS: rename community to previous Note over I,FS: a crash here leaves community missing I->>FS: rename staging community to community I->>FS: delete journal.json Note over I,FS: next start, journal found and community missing, recoverIfNeeded renames previous back
Diagram description
Activation writes a journal, renames the live folder to previous and the verified staging folder to live. If the process dies between the two renames, the next start finds the journal, sees that live is missing and moves previous back. A second update at the same time is refused by a file lock.
Mermaid code
sequenceDiagram
autonumber
participant I as RuleInstaller
participant FS as Filesystem
I->>FS: write journal.json
I->>FS: rename community to previous
Note over I,FS: a crash here leaves community missing
I->>FS: rename staging community to community
I->>FS: delete journal.json
Note over I,FS: next start, journal found and community missing, recoverIfNeeded renames previous back