Verifying rules, signatures and transparency
How to check a release yourself without trusting this site: commands, real URLs and how to read the results.
A. What you will check
A release is valid if (1) the manifest and the checkpoint are signed by the expected workflow identity, (2) every package file matches the SHA-256 in the signed manifest, (3) the recomputed Merkle root equals rules_root, and (4) the checkpoint contains the release entry and extends the one you already knew.
LIMIT A valid signature and matching hashes prove who published exactly those bytes. They do not prove the rules are good or that scanned files are safe.
B. Requirements
cosign(v3.1.1 or later), installed from its official source.- Python 3 for the verifier and the Merkle recomputation; nothing beyond the standard library.
shasum(macOS) orsha256sum(Linux).
C. Artifacts of the latest release
Release rules-20261018-39150886414. The mandatory artifacts download from this path:
https://github.com/MacGuard/macguard-yara-channel/releases/download/rules-20261018-39150886414/rules-release.tar.gz
https://github.com/MacGuard/macguard-yara-channel/releases/download/rules-20261018-39150886414/manifest.json
https://github.com/MacGuard/macguard-yara-channel/releases/download/rules-20261018-39150886414/manifest.sigstore.json
https://github.com/MacGuard/macguard-yara-channel/releases/download/rules-20261018-39150886414/inclusion-proof.json
https://github.com/MacGuard/macguard-yara-channel/releases/download/rules-20261018-39150886414/checkpoint.txt
https://github.com/MacGuard/macguard-yara-channel/releases/download/rules-20261018-39150886414/checkpoint.sigstore.jsonOptional: release.json, changes.json, CHANGELOG.md and consistency.json (the latter proves the log extends the previous release log). To download them all:
mkdir verify && cd verify
gh release download rules-20261018-39150886414 --repo MacGuard/macguard-yara-channelD. Verify the signature with cosign
Minimal example with the expected identity and issuer:
cosign verify-blob manifest.json \
--bundle manifest.sigstore.json \
--certificate-identity "https://github.com/MacGuard/macguard-yara-channel/.github/workflows/publish.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"Full version, which also requires the certificate to name this repository, for the manifest and for the checkpoint:
cosign verify-blob manifest.json \
--bundle manifest.sigstore.json \
--certificate-identity "https://github.com/MacGuard/macguard-yara-channel/.github/workflows/publish.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--certificate-github-workflow-repository "MacGuard/macguard-yara-channel"
cosign verify-blob checkpoint.txt \
--bundle checkpoint.sigstore.json \
--certificate-identity "https://github.com/MacGuard/macguard-yara-channel/.github/workflows/publish.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--certificate-github-workflow-repository "MacGuard/macguard-yara-channel"Correct result: Verified OK. If the identity or issuer do not match, cosign fails; do not run anything from that release.
E. Verify the package hashes
SHA-256 of the package (must be 97304ccc48e2ab69368618c49dbf40f18cd7d0c310dfd4ee736ed8823170a8d0 for this release) and of the manifest (f75c214683d6c169716be1a5ac6f90a7c9a74f408c5cd139553a87e5a25370ad):
# macOS
shasum -a 256 rules-release.tar.gz manifest.json
# Linux
sha256sum rules-release.tar.gz manifest.jsonThe package hash is not signed separately; what ties it to the signature is that every extracted file has the SHA-256 listed in manifest.json. The verifier does it for you (section G).
F. Recompute rules_root and inclusion
The manual recomputation of rules_root is in Merkle trees. The inclusion proof (inclusion-proof.json) is checked by the verifier: it carries the release log entry up to the checkpoint root and checks that this entry names the signed rules_root.
G. Public verifier
The verify_public_release.py script does all of the above in one go (Python standard library and cosign). Download it to a file, read it and run it; do not pipe it into an interpreter.
curl -fsSLo verify_public_release.py https://raw.githubusercontent.com/MacGuard/macguard-yara-channel/main/scripts/verify_public_release.py
less verify_public_release.py
python3 verify_public_release.py --latestOptions: --latest, --tag TAG, --directory DIR (already downloaded artifacts), --json, --previous-checkpoint FILE or --trust-state FILE (rollback protection), --token or the GITHUB_TOKEN variable (optional). It works without a token: the anonymous GitHub API allows 60 requests per hour per IP address; if that runs out (HTTP 403 with X-RateLimit-Remaining: 0) the verifier says so with the reset time and, without using the API, resolves latest through the github.com redirect and downloads the same artifacts (Content-Length is checked, not the API size) and notes it in the report. A 403 with no exhausted quota (firewall, proxy) is reported as such. Real output, abridged, on the latest release:
MacGuard release verification — PASS (exit 0)
Release rules-20261018-39150886414 (latest)
Files 1174 individual YARA rules: 9958
rules_root 16d082395ef1ec53d0ad25d5840d83e7f5f3a75a283ce66f8fa75da106790ec5
[PASS] signature.manifest
[PASS] signature.checkpoint
[PASS] files.sha256
[PASS] merkle.rules_root
[PASS] merkle.inclusion
[n/a ] consistency: no previous checkpoint was givenCURRENT Without--previous-checkpointor--trust-statethe verifier says so explicitly: it is a first verification and rollback protection is NOT established by that run.
Exit codes
| Code | Meaning |
|---|---|
| 0 | All mandatory checks done and passed. |
| 1 | Integrity: a hash does not match. |
| 2 | Invalid signature or identity. |
| 3 | Merkle, inclusion or consistency. |
| 4 | Incomplete download or network error. |
| 5 | A dependency is missing (cosign). |
| 6 | Invalid format or incomplete set. |
If there are several failures the report lists them all and the exit code is the first by this precedence: 5, 4, 6, 2, 1, 3.
Drawing the diagram…
flowchart TD
A["resolve tag once<br/>--latest or --tag"]:::src --> B["download mandatory artifacts<br/>exit 4 if incomplete"]:::src
B --> C["cosign verify-blob<br/>manifest and checkpoint<br/>exit 2 or 5"]:::sec
C --> D["safe extraction<br/>exit 6 if unsafe"]:::val
D --> E["SHA-256 of every file<br/>exit 1 if different"]:::val
E --> F["recompute rules_root<br/>exit 3"]:::sec
F --> G["checkpoint equals manifest log head<br/>inclusion proof<br/>exit 3"]:::sec
G --> H{"previous checkpoint<br/>given?"}:::gate
H -->|yes| I["consistency proof and rollback check<br/>exit 3"]:::sec
H -->|no| J["report: rollback protection not established"]:::qua
I --> K["PASS exit 0"]:::pub
J --> K
classDef src fill:#cfe3ff,stroke:#2f6df0,color:#0b1b33
classDef val fill:#ffe9b8,stroke:#b8860b,color:#2b1d00
classDef sec fill:#cdeedd,stroke:#1b6e3c,color:#08210f
classDef pub fill:#e4d7ff,stroke:#6a3fd1,color:#1b0b3b
classDef qua fill:#ffd4cc,stroke:#c0392b,color:#33100b
classDef gate fill:#ffffff,stroke:#111111,stroke-width:3px,color:#111111Diagram description
verify_public_release.py resolves the release once and pins the tag, downloads the mandatory artifacts, verifies the Sigstore identity of the manifest and the checkpoint, extracts the package with limits, compares every hash, recomputes rules_root, checks counts, checkpoint, inclusion and optional consistency, and exits with a code that names the kind of failure.
Mermaid code
flowchart TD
A["resolve tag once<br/>--latest or --tag"]:::src --> B["download mandatory artifacts<br/>exit 4 if incomplete"]:::src
B --> C["cosign verify-blob<br/>manifest and checkpoint<br/>exit 2 or 5"]:::sec
C --> D["safe extraction<br/>exit 6 if unsafe"]:::val
D --> E["SHA-256 of every file<br/>exit 1 if different"]:::val
E --> F["recompute rules_root<br/>exit 3"]:::sec
F --> G["checkpoint equals manifest log head<br/>inclusion proof<br/>exit 3"]:::sec
G --> H{"previous checkpoint<br/>given?"}:::gate
H -->|yes| I["consistency proof and rollback check<br/>exit 3"]:::sec
H -->|no| J["report: rollback protection not established"]:::qua
I --> K["PASS exit 0"]:::pub
J --> K
classDef src fill:#cfe3ff,stroke:#2f6df0,color:#0b1b33
classDef val fill:#ffe9b8,stroke:#b8860b,color:#2b1d00
classDef sec fill:#cdeedd,stroke:#1b6e3c,color:#08210f
classDef pub fill:#e4d7ff,stroke:#6a3fd1,color:#1b0b3b
classDef qua fill:#ffd4cc,stroke:#c0392b,color:#33100b
classDef gate fill:#ffffff,stroke:#111111,stroke-width:3px,color:#111111H. How to know the verifier itself is the right one
The verifier is code you run: read it (about 400 lines) and compare the commit on GitHub. Downloading it from the same site that hosts the rules is not an independent root of trust.
PROPOSED Signingverify_public_release.pywith Sigstore inpublish.ymland attaching it to every release is a proposed improvement: it does not exist yet, so current releases carry no signed copy of the verifier.
I. What to do if it fails
- Do not install or use the rules from that release.
- Repeat with
--output-dir DIRand compare the artifacts with those on GitHub Releases (the canonical archive). - Open an issue on the repository with the full
--jsonoutput; it contains no secrets.