Security model
A reviewer will ask these questions. Better that the answers are written down than improvised.
What Heron can prove
- A decision existed, and it said what we claim. Every decision is signed with Ed25519 over the RFC 8785 canonical form of its payload. Anyone with the public JWKS can recompute the bytes and check the signature — offline, without asking us.
- The record was not edited afterwards. Actions are chained, and the chain is recomputed from the stored columns. An edit to any row makes the recomputation diverge from the hash we signed →
BROKEN_CHAIN. - The vendor said what it executed, and cannot disown it.Execution evidence is signed with the vendor’s own key. We hold only the public half.
- The two statements agree, or they do not. Reconciliation pairs them, and every disagreement surfaces as an anomaly — on the evidence page, whether or not the vendor likes it.
What Heron cannot prove — and says so on the evidence page
Self-bypass. An action performed around the hook leaves no trace. The absence of a record is indistinguishable from the absence of an action. Heron sees only what the vendor sends it.
Closing that gap means moving the boundary further: routing every tool call physically through Heron (an MCP gateway), or a Heron-signed capability the tool provider refuses to execute without. Heron enforces the verdict, but an action performed around the hook still leaves no trace — enforcement narrows the gap, it does not close it (stage 5).
Unavailability is the sharpest form of it.Enforcement is cooperative — the vendor’s own SDK honours the verdict — so @theonaai/heron-sdkalready fails closed when Heron is unreachable. But because that behaviour lives in the vendor’s code, “Heron was unavailable” is a universal excuse: it can cloak ignoring any DENY, not only a genuine outage. The fix is an inversion (stage 5): Heron issues a short-lived signed capability with an ALLOW, and the tool provider refuses to execute without a fresh valid one — so absence of Heron is absence of permission by construction, and an outage stops the agent rather than excusing it. Until then this is the vendor’s goodwill, and the page says so.
So the page measures the gap rather than papering over it: the evidence page shows a coverage metric, states that coverage is declared by the vendor, and leaves the reviewer to ask what the missing actions were.
Intent. Heron does not judge whether an action matched what the user asked for. The policy engine classifies an action and enforces it against published rules; a DENY there means the rules matched, not that the agent misbehaved. Whether the action was the one the user wanted is intent alignment, and it is not on this page.
Completeness of the record against Heron itself.Everything above is a gap on the vendor’s side. This one is ours, and it splits in two. Forgery is bounded, and by something we do not hold: an execution statement is signed with the vendor’s key, imported and never fetched at verification time, so Heron cannot invent an execution, alter one, or manufacture a session that did not happen — and the vendor separately holds every decision receipt we ever signed. Deletion is bounded by nothing: a signature proves what is present and says nothing about what was removed, so an operator holding the rows and the signing key could re-derive a shorter chain from genesis, and every published check — the chain recompute, the JWKS, the package manifest and its attestation — would pass on it, because all of them are computed over what we serve. So: Heron can be caught changing the record, and not caught shortening it.
What closes it is a copy we do not control — the buyer holding an exported stream or an earlier evidence package, the vendor keeping its own index of the receipts it received, or a public anchor for the chain head. None of the three is built yet, which is why this limit is stated here rather than on the evidence page: an admission reads better beside the mechanism that answers it, and it moves there when the first of the three ships. Until then, nobody here will tell you that the record cannot be truncated — including by us.
Keys
Ours.The private signing key is held in the service’s secret store and nowhere else — not in the database, not in the repository. The database holds public halves only, so a database dump lets nobody forge a receipt. Rotating means minting a new key id; the old public key stays in the JWKS forever, because removing it would silently invalidate every receipt it signed.
Theirs. Imported from the JWKS the vendor publishes, or pasted for a vendor without one. The import is not a lookup: fetching a key at verification timewould make our evidence depend on the vendor’s uptime, and — worse — would let them swap the key under evidence we had already verified, which is precisely the attack execution evidence exists to prevent. Consequences, enforced in the product:
- A re-import never deletes a key that vanished from the vendor’s JWKS: a rotation must not invalidate evidence signed before it.
- A key that has verified nothing can be deleted — a paste-o costs nothing to remove.
- A key that has verified evidence can only be revoked. The check is re-run inside the server action, not just used to hide the button.
- Revocation is forward-only: new evidence signed with it is flagged, and the verdicts already recorded stand. We do not rewrite what we have already attested to.
- HTTPS only for JWKS import. A key fetched over plaintext HTTP is a key the attacker chose.
Secrets
- API keys are stored as SHA-256 and shown exactly once, in a dialog.
- Nothing secret travels in a URL — query strings survive in browser history,
Refererheaders and access logs. - Login and session tokens are stored as SHA-256 with a server-side pepper.
- Webhook signing secrets are shown masked, with reveal and copy.
Personal data
The evidence page is the artefact a stranger reads, so it carries no user data at all: tool names, hashes, verdicts, timestamps, signatures. A single file defines what it may contain, and a field not selected there cannot leak.
Upstream of that:
- Arguments are redacted before storage. The shape of the call is kept, never its contents. The vendor is expected to redact first; we do not trust that.
principal.refmust be opaque — an email is rejected with a400, not quietly scrubbed.- Hashes are taken by the vendor over the unredacted data, so the data itself never crosses to us.
- Retention applies to the redacted arguments and the redacted original request (default 90 days), enforced by an hourly sweep rather than by the setting alone. Hashes and receipts are kept indefinitely: dropping them would destroy the proof.
- The operator log carries no identity: actors appear under a pseudonym, because those bytes are published.
Multi-tenancy
Tenancy is three levels: organization → project → environment. The project is the enforcement boundary — its own policy, break-glass, vendor keys, redaction and retention, its own audit chain, its own evidence pages. A vendor with two products governs each by its own rules, and a reviewer of one is handed nothing about the other.
The project is derived from the API key (ingest), which resolves to an environment and through it to the project, and from nowhere else — no request can name its own organization or project. The dashboard derives it from the URL pathrather than from the signed-in user or a session cookie, which matters more than it sounds: an “active organization” held in a session is how an operator ends up resolving a finding into a different tenant’s immutable chain, a mistake with no undo.
Every query is scoped by that project, including the negative paths: execution evidence naming an action from another project — even another project of the same tenant — is, from here, evidence for an action that does not exist, and lands as EXECUTION_WITHOUT_DECISION in the caller’s own project. There is a test for exactly that.
Roles are granted per area — viewer, operator, admin, owner, at the organization or at a project — and the role someone holds in an area is the strongest grant covering it. Grants only ever add: access is narrowed by removing the wider grant, never by a grant that subtracts.
Scope
Heron is not an auditor, and does not certify anyone.It is a trust centre and an evidence pipeline: it records what a vendor’s agents did, signs it, and makes it checkable by a third party. An evidence page is a record to examine, not an attestation to rely on — the vendor remains responsible for what its agents do, and independent audit belongs with independent auditors.