Verificación de reglas, firmas y transparencia
Cómo comprobar tú mismo una release, sin confiar en este sitio: comandos, URLs reales y cómo interpretar los resultados.
A. Qué vas a comprobar
Una release es válida si (1) el manifiesto y el checkpoint están firmados por la identidad del workflow esperado, (2) cada fichero del paquete coincide con el SHA-256 del manifiesto firmado, (3) la raíz Merkle recalculada coincide con rules_root, y (4) el checkpoint contiene la entrada de la release y extiende al anterior que ya conocías.
LÍMITE Una firma y unos hashes correctos prueban quién publicó exactamente esos bytes. No prueban que las reglas sean buenas ni que los ficheros que se escanean sean seguros.
B. Requisitos
cosign(v3.1.1 o posterior), instalado desde su origen oficial.- Python 3 para el verificador y el recálculo Merkle; nada más que la biblioteca estándar.
shasum(macOS) osha256sum(Linux).
C. Artefactos de la última release
Release rules-20261018-39150886414. Los artefactos obligatorios se descargan de esta ruta:
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.jsonOpcionales: release.json, changes.json, CHANGELOG.md y consistency.json (esta última prueba que el registro extiende el de la release anterior). Para descargarlos todos:
mkdir verify && cd verify
gh release download rules-20261018-39150886414 --repo MacGuard/macguard-yara-channelD. Verificar la firma con cosign
Ejemplo mínimo con la identidad y el emisor esperados:
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"Versión completa, que además exige que el certificado nombre este repositorio, para el manifiesto y para el 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"Resultado correcto: Verified OK. Si la identidad o el emisor no coinciden, cosign falla; no ejecutes nada de esa release.
E. Verificar los hashes del paquete
SHA-256 del paquete (debe ser 97304ccc48e2ab69368618c49dbf40f18cd7d0c310dfd4ee736ed8823170a8d0 para esta release) y del manifiesto (f75c214683d6c169716be1a5ac6f90a7c9a74f408c5cd139553a87e5a25370ad):
# macOS
shasum -a 256 rules-release.tar.gz manifest.json
# Linux
sha256sum rules-release.tar.gz manifest.jsonLos hashes del paquete no están firmados por separado; lo que lo ata a la firma es que cada fichero extraído tiene el SHA-256 de manifest.json. El verificador lo hace por ti (sección G).
F. Recalcular rules_root e inclusión
El recálculo manual de rules_root está en Árboles Merkle. La prueba de inclusión (inclusion-proof.json) la comprueba el verificador: lleva la entrada del registro de la release hasta la raíz del checkpoint y comprueba que esa entrada nombra el rules_root firmado.
G. Verificador público
El script verify_public_release.py hace todo lo anterior de una vez (biblioteca estándar de Python y cosign). Descárgalo a un fichero, léelo y ejecútalo; no lo canalices a un intérprete.
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 --latestOpciones: --latest, --tag TAG, --directory DIR (artefactos ya descargados), --json, --previous-checkpoint FILE o --trust-state FILE (protección contra retroceso), --token o la variable GITHUB_TOKEN (opcional). Funciona sin token: la API anónima de GitHub admite 60 peticiones por hora y dirección IP; si se agota (HTTP 403 con X-RateLimit-Remaining: 0) el verificador lo dice con la hora de reinicio y, sin usar la API, resuelve latest con la redirección de github.com y descarga los mismos artefactos (se comprueba Content-Length, no el tamaño de la API) y lo anota en el informe. Un 403 sin cuota agotada (cortafuegos, proxy) se informa como tal. Salida real, abreviada, sobre la última 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 givenACTUAL Sin--previous-checkpointni--trust-stateel verificador lo dice expresamente: es una primera verificación y la protección contra retroceso no queda establecida por esa ejecución.
Códigos de salida
| Código | Significado |
|---|---|
| 0 | Todas las comprobaciones obligatorias hechas y superadas. |
| 1 | Integridad: un hash no coincide. |
| 2 | Firma o identidad inválidas. |
| 3 | Merkle, inclusión o consistencia. |
| 4 | Descarga incompleta o error de red. |
| 5 | Falta una dependencia (cosign). |
| 6 | Formato inválido o conjunto incompleto. |
Si hay varios fallos el informe los lista todos y el código de salida es el primero por esta precedencia: 5, 4, 6, 2, 1, 3.
Dibujando el diagrama…
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:#111111Descripción del diagrama
verify_public_release.py resuelve la release una sola vez y fija la etiqueta, descarga los artefactos obligatorios, verifica la identidad Sigstore del manifiesto y del checkpoint, extrae el paquete con límites, compara cada hash, recalcula rules_root, comprueba cifras, checkpoint, inclusión y consistencia opcional, y termina con un código que indica el tipo de fallo.
Código Mermaid
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. Cómo saber que el verificador es el correcto
El verificador es código que ejecutas tú: léelo (unas 400 líneas) y compara el commit en GitHub. Descargarlo del mismo sitio que aloja las reglas no es una raíz de confianza independiente.
PROPUESTA Firmarverify_public_release.pycon Sigstore enpublish.ymly adjuntarlo a cada release es una mejora propuesta: no existe todavía, así que las releases actuales no llevan una copia firmada del verificador.
I. Qué hacer si falla
- No instales ni uses las reglas de esa release.
- Repite con
--output-dir DIRy compara los artefactos con los de GitHub Releases (archivo canónico). - Abre una incidencia en el repositorio con la salida
--jsoncompleta; no contiene secretos.