Security
Why a heso receipt is worth more than a screenshot.
A receipt is only evidence if you can check it without trusting the system that made it. heso is built so anyone — an auditor, a counterparty, a regulator with zero heso account — can re-verify a receipt from the bytes alone. Here is exactly how that works, and exactly where it stops.
The trust path
Content never leaves your box.
The wire carries a commitment, not your action body. Each step is something a verifier can re-check — not a claim you take on faith.
Classify · gate · redact · sign
Raw action content stays here. heso classifies by effect, runs your policy, strips secrets before hashing, and signs the receipt with your operator key.
→ commitment, not contentVerify before append
Receives a commitment — fingerprint, metadata, chain head, signatures. Verifies it fail-closed, then stores it. It never holds the action body and never holds a signing key.
→ signed receipt + proofsRe-verify offline
A relying party checks signatures, the hash chain, and inclusion from the bytes alone — in the browser or with the open Rust verifier. No network in the trust path.
The honest limits — read these first
What heso does not prove.
We lead with the gaps on purpose. A trust system you can’t poke holes in is one that’s hiding them. Here is where the guarantees stop — stated plainly, before the parts that work.
The operator holds the signing key.
An untrusted operator can author a fresh, valid-looking receipt. A signature proves control of a key — not real-world identity, and not that the key-holder is honest. The only thing that closes this gap is a witness outside the operator, and that is not live yet (below).
The live external witness service is not running yet.
The verifier enforces any declared witness requirement and reports an honest "not witnessed" when no cosignature is present — it never calls that a pass. What is not live yet is an independently operated external witness service whose key you could pin. Until that runs, the log is tamper-evident and publicly anchored (Rekor), but not independently witnessed.
AWS and Stripe are roots we depend on — and they are not equal.
AWS CloudTrail signs its digest chain asymmetrically, so a third party can re-derive the rail facts on a receipt offline. Stripe’s webhook signature is an HMAC over a shared secret. It authenticates a message to whoever holds the secret, but is not third-party-verifiable. Where a rail fact cannot be re-derived offline, the verifier discloses that plainly rather than claiming a pass.
It proves the attempt, not the outcome.
A receipt proves how an action was classified, which policy gate fired, who co-signed it, and that none of it was tampered with. It does not prove the side effect actually executed downstream, that the agent was correct, or that redacted content is honest. heso never claims to have observed the outcome itself. Absence of a receipt is silence, not an accusation.
Four properties
What carries the weight.
Strip away the diagrams and four concrete mechanisms do the work — each one a thing a verifier can re-check from the bytes.
Operator-signed receipts
Each action becomes a record signed with the operator’s Ed25519 key over RFC-8785 canonical bytes, with a BLAKE3 content hash under frozen domain-separation tags. Flip one byte and verification fails.
Trust levels derived, never declared
L0 is one operator signature; L1 adds an approver co-signature over the same body. The level is re-derived from the signatures present — never read from a field a producer could set. Higher levels are reserved, not built.
Pre-sign redaction
Secrets are stripped before hashing, so they never enter the signed bytes. Destructive redaction is irrecoverable; commit-and-reveal keeps a salted BLAKE3 commitment so a value can be disclosed later from an off-wire sidecar.
Raw content stays in your network
The wire ships a commitment — a BLAKE3 fingerprint, primitive-indexed metadata, chain head and signatures — not the action body. The cloud verifies the commitment before storing it. The content never leaves your network.
Pre-sign redaction
Secrets never enter the signed bytes.
Redaction runs before hashing, so a value a verifier never needs to see is gone from the record by construction — not blacked out after the fact.
# secrets are removed BEFORE the bytes are hashed and signed
content.api_key → [redacted] # destructive · irrecoverable
content.account_no → blake3(salt ‖ value) # commit-and-reveal
+ merkle root in receipt
+ salt sealed in off-wire sidecar
# a verifier sees the commitment, never the secret.
# reveal later by producing the sidecar — non-redacted fields
# stay visible to any holder.Key custody
Seal the key with a passphrase or your KMS.
A backward-compatible upgrade path from a plaintext seed to passphrase- or KMS-sealed custody. Remember the limit above: whoever holds the key can sign.
# legacy: bare 32-byte Ed25519 seed (gitignored, tightened perms)
# keystore: a self-describing sealed container
0x01 passphrase-sealed # sealed by a secret you hold
0x02 KMS-wrapped envelope # the seed is wrapped by your cloud KMS
# the browser surface is verify-only by design:
# no sign, no key generation — signing never reaches a wasm target.One kernel · cross-language conformance
“Anyone can verify” is demonstrated, not asserted.
All canonicalization, hashing and signature checking live in one Rust kernel. Python, Node and the browser each call it through a binding — there is no second crypto implementation to drift. An independent open verifier cross-checks the reference byte-for-byte on every release. The browser surface is verify-only by design — no signing, no key generation ever reaches a wasm target.
# one kernel — every binding calls it, none re-implements crypto
heso-verify (Rust) RFC-8785 (JCS) · BLAKE3 · Ed25519 verify_strict
├─ Python _core wheel (PyO3) → calls the kernel
├─ Node napi addon → calls the kernel
└─ Browser WASM (verify-only) → calls the kernel
# the load-bearing conformance check, run on every release:
# the open Python verifier (rfc8785) and the Rust reference (serde_jcs)
# produce byte-identical JCS — and each verifies the other's signatures.
# vectors are generated from the reference, never hand-edited.Talk through the trust model.
We can walk through what is live now, what is not, and where heso fits in your agent workflow.
