heso

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.

01Your network

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 content
02heso cloud

Verify 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 + proofs
03Anyone

Re-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.

Key custody

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).

External witness

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.

Trust roots

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.

What a receipt is

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.

redaction
# 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.

custody
# 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.

conformance
# 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.