Verify GuardSpine evidence bundles offline -- no trust required.
Spec Version: v0.2.0 + v0.2.1 | Package Version: 0.2.1
An offline CLI and Python library for verifying GuardSpine evidence bundles. Given a sealed bundle (JSON or ZIP), it recomputes every hash from scratch and compares against stored values. If anything was tampered with, verification fails.
No network access. No trust assumptions. Fully auditable.
pip install guardspine-verifyOr install from source:
git clone https://github.com/DNYoussef/guardspine-verify.git
cd guardspine-verify
pip install -e .guardspine-kernel>=0.2.0-- canonical JSON serialization (RFC 8785). The verifier delegates all canonical JSON computation to the kernel to guarantee cross-language hash parity.cryptography>=41.0-- signature verification (Ed25519, RSA, ECDSA).click>=8.0-- CLI framework.rich>=13.0-- terminal output formatting.jsonschema>=4.0-- schema validation.
# Verify a JSON bundle
guardspine-verify bundle.json
# Verify a ZIP export
guardspine-verify evidence-bundle-2024-01-15.zip
# Verbose output
guardspine-verify bundle.json --verbose
# Output JSON report
guardspine-verify bundle.json --format json > report.json
# Verify with a public key (cryptographic signature check)
guardspine-verify bundle.json --public-key signer.pubThe verifier runs six checks in order:
-
Version validation -- bundle version must be
0.2.0or0.2.1. -
Hash chain integrity -- each chain entry's
chain_hashis recomputed asSHA-256("sequence|item_id|content_type|content_hash|previous_hash")and compared to the stored value. Previous-hash linkage is verified entry by entry, starting from"genesis". -
Root hash validation -- the Merkle root is recomputed as
SHA-256(concat(all chain_hash values))and compared to the storedroot_hash. -
Content hash validation -- for each item, the content is serialized to canonical JSON (via
guardspine-kernel) and hashed with SHA-256. The result must match the storedcontent_hash. -
Chain-to-items binding -- every item must have exactly one chain entry with matching
item_id,content_hash,content_type, andsequence. Unbound items and orphaned chain entries are both errors. -
Signature verification -- validates signature format (algorithm, base64 encoding). When a PEM public key is provided via
--public-key, performs cryptographic verification. Supports Ed25519, RSA-SHA256, ECDSA-P256, and HMAC-SHA256.
All content hashing uses canonical JSON from guardspine-kernel to guarantee
byte-identical output across Python, TypeScript, and Lean implementations.
| Check | Description |
|---|---|
| Version | Bundle version must be 0.2.0 or 0.2.1 |
| Hash Chain | Each entry's previous_hash matches prior chain_hash; chain_hash recomputed and compared |
| Chain Binding | Chain entries map 1:1 to items (count, item_id, content_hash, content_type, sequence) |
| Root Hash | Computed Merkle root matches stored root |
| Content Hashes | Each item's content_hash matches SHA-256 of canonical JSON content |
| Signatures | Format validation; cryptographic verification with --public-key |
| Sanitization | Optional: --check-sanitized validates redaction metadata and token consistency |
| Code | Meaning |
|---|---|
| 0 | Bundle verified successfully |
| 1 | Verification failed |
| 2 | Invalid input (file not found, parse error, bad public key) |
guardspine-verify BUNDLE_PATH [OPTIONS]
Options:
-v, --verbose Show detailed output
-f, --format [text|json] Output format (default: text)
-k, --public-key PATH PEM public key for cryptographic signature verification
--check-sanitized Validate sanitization attestations and [HIDDEN:*] token consistency
--require-sanitized Fail if sanitization block is missing or invalid
--fail-on-raw-entropy Treat high-entropy survivor candidates as hard failures
--version Show version and exit
from guardspine_verify import verify_bundle, verify_bundle_data, VerificationResult
# Verify from file path
result = verify_bundle("bundle.json")
# Verify from dict
import json
with open("bundle.json") as f:
bundle = json.load(f)
result = verify_bundle_data(bundle)
if result.verified:
print("Bundle verified")
else:
for error in result.errors:
print(f" - {error}")
# With cryptographic signature verification
with open("signer.pub", "rb") as f:
public_key_pem = f.read()
result = verify_bundle("bundle.json", public_key_pem=public_key_pem)
# With sanitization checks
result = verify_bundle_data(
bundle,
check_sanitized=True,
require_sanitized=True,
fail_on_raw_entropy=True,
)@dataclass
class VerificationResult:
verified: bool
status: str # "verified" | "mismatch" | "error"
hash_chain_status: str
root_hash_status: str
content_hash_status: str
signature_status: str
errors: list[str]
warnings: list[str]
verified_at: datetime
details: dict[str, Any]| Function | Purpose |
|---|---|
verify_bundle(path, ...) |
Verify from file path (JSON or ZIP) |
verify_bundle_data(bundle, ...) |
Verify from dict |
verify_hash_chain(bundle) |
Hash chain check only |
verify_root_hash(bundle) |
Root hash check only |
verify_content_hashes(bundle) |
Content hash check only |
verify_signatures(bundle, ...) |
Signature check only |
verify_sanitization(bundle, ...) |
Sanitization check only |
The verifier can optionally delegate hash-chain and content-hash verification to a compiled Lean binary for formal-verification-grade assurance. This is entirely optional -- the Python path is the default.
To enable Lean verification, set the GUARDSPINE_LEAN_BIN environment variable
to the path of the compiled GuardSpine Lean binary:
export GUARDSPINE_LEAN_BIN=/path/to/guardspine-leanThe Lean binary handles: version validation, hash chain verification, root hash verification, content hash verification, and cross-check. Python continues to handle signature verification, sanitization checks, and CLI formatting.
The shim communicates with the Lean binary via JSON over stdin/stdout with a 30-second timeout.
guardspine-verify validates PII-Shield sanitization attestations embedded in evidence bundles.
When bundles are sanitized before sealing (e.g., by codeguard-action), the
sanitization block attests what engine was used, how many redactions were
applied, and what token format was used. The verifier checks:
sanitization.redaction_countmatches actual[HIDDEN:<id>]token count- Required fields:
engine_name,engine_version,method,token_format,status token_formatmatches the canonical[HIDDEN:<id>]declaration- High-entropy strings that survived sanitization (optional hard fail)
Hash fields (content_hash, chain_hash, root_hash, *_hash) and
cryptographic fields (signatures, signature_value, public_key_id,
immutability_proof) are excluded from entropy analysis to avoid false positives.
# Check sanitization attestation (warn on issues)
guardspine-verify bundle.json --check-sanitized
# Require sanitization (fail if missing or invalid)
guardspine-verify bundle.json --require-sanitized
# Treat post-sanitization high-entropy survivors as failures
guardspine-verify bundle.json --require-sanitized --fail-on-raw-entropyZIP bundle loading enforces safety limits to prevent zip bombs and resource exhaustion:
- Maximum ZIP file size: 100 MB
- Maximum entries per ZIP: 1000
- Maximum
bundle.jsonsize: 50 MB - Compression ratio check: rejects ratios above 100x
| Algorithm | Type | Status |
|---|---|---|
| SHA-256 | Hash | Content hashing, chain hashing |
| Ed25519 | Signature | Supported |
| RSA-SHA256 | Signature | Supported |
| ECDSA-P256 | Signature | Supported |
| HMAC-SHA256 | Signature | Supported (requires --hmac-secret or API param) |
| Format | Extension | Description |
|---|---|---|
| JSON | .json |
Single bundle file |
| ZIP | .zip |
Exported bundle package (must contain bundle.json) |
# GitHub Actions
- name: Verify Evidence Bundles
run: |
pip install guardspine-verify
guardspine-verify ./evidence/*.json# GitLab CI
verify-evidence:
script:
- pip install guardspine-verify
- guardspine-verify ./evidence/*.jsonguardspine-verify uses guardspine-kernel (the Python port of the canonical
TypeScript kernel) for all canonical JSON serialization. This guarantees that
bundles sealed by any language implementation (TypeScript, Python, Lean) produce
byte-identical hashes and can be verified by any other implementation.
Golden vector tests validate hash parity against
guardspine-spec/fixtures/golden-vectors/v0.2.0.json.
guardspine_verify/
__init__.py Public API exports
cli.py Click CLI (text + JSON output via Rich)
verifier.py Core verification logic
lean_shim.py Optional Lean binary bridge (GUARDSPINE_LEAN_BIN)
Key design decisions:
- No duplicated code. Canonical JSON delegates to
guardspine-kernel. Previously this module carried its own 40-line implementation; that has been removed in favor of the single canonical source. - Lean is optional. The
lean_shim.pymodule discovers the Lean binary exclusively via theGUARDSPINE_LEAN_BINenvironment variable. No hard-coded paths, no fallback search. - Signatures are Python-only. Even when using the Lean backend, signature
verification and sanitization checks run in Python (via
cryptography).
| Project | Description |
|---|---|
| guardspine-kernel | Canonical TypeScript kernel |
| guardspine-kernel-py | Python port (canonical JSON, hashing) |
| guardspine-spec | Bundle specification |
| GuardSpine | Full governance platform |
| codeguard-action | GitHub Action for code review governance |
See CONTRIBUTING.md.
Apache 2.0 -- See LICENSE.