# Golem's signed record: the format, and how to check it yourself

Golem's claim is that what it records about an agent cannot be changed afterwards without
the change showing. This page says exactly what is signed, how entries are chained, and
how to check an evidence export without trusting Golem, its console or its servers. The
verifier is [verify.py](https://golem.team/verify.py), the same file every evidence
export carries.

Tamper-evident, not tamper-proof: a check that passes means nothing was changed after it
was signed, by anyone without the signing keys.

## Two chains

- **Session chains.** The Golem agent on each machine signs one entry per thing it
  observes in a session (a tool call, a refusal, a prompt, a result) and chains the
  session's entries together.
- **The workspace evidence chain.** Golem's server signs one entry per security decision
  in a workspace (a flag resolved, a case opened or closed, an export made) and chains
  those.

Both use the same construction.

## An entry

Each entry carries:

| Field | What it is |
|---|---|
| `payload_cbor` | The signed content, as canonical CBOR (RFC 8949 §4.2: sorted map keys, shortest encodings), base64 in an export |
| `previous_hash` | The `entry_hash` of the entry before it; 64 zeros for the first |
| `entry_hash` | `SHA-256(payload_cbor ‖ previous_hash)`, where `previous_hash` is appended as its lowercase hex text, in UTF-8. Lowercase hex. |
| `cose_sign1` | A COSE_Sign1 envelope (RFC 9052) over exactly `payload_cbor` |
| `seq` | The entry's position in its chain |

Changing any payload changes its `entry_hash`, which breaks the `previous_hash` link of
every entry after it. Deleting, inserting or reordering entries breaks a link too.

### The signature

- Algorithm ES256 (ECDSA P-256 with SHA-256): protected header `1` (alg) is `-7`.
- Protected header `4` (kid) names the key: the hex SHA-256 fingerprint of the signing
  public key.
- The signed structure is `["Signature1", protected, h'', payload]`, and the signature is
  the 64-byte `r ‖ s`.
- On a machine with a hardware key store (Apple's Secure Enclave on macOS, a TPM on
  Linux), the Golem agent roots its identity there by default, and each short-lived
  signing key is itself signed by that hardware key, so a signature ties to a specific
  machine. Elsewhere it falls back to a software machine key.

### What a session entry signs

A canonical CBOR map, `bom_version` 2:

| Key | Value |
|---|---|
| `session_id` | The session |
| `machine_id` | The enrolled host |
| `agent_id` | The agent (for example `claude-code-builtin`) |
| `mcp_server_id` | The MCP server, where there is one |
| `event_type` | What was observed (for example `hook_tool_use`, `mcp_tool_call`, `policy_decision`) |
| `tool_name` | The tool |
| `payload_hash` | SHA-256 of the raw payload, which is stored in the customer's own bucket, not in the record |
| `agent_pid` | The agent's process |
| `parent_session_id` | When the session was started by another (a sandboxed child, a sub-agent) |
| `agent_principal_id`, `caller_scope` | A verified agent identity, when one was presented |
| `observed_identity_iss`, `_sub`, `_email` | An identity token Golem saw on the call, recorded as observed |
| `original_payload_hash` | When the payload was redacted before storage, the hash of the original, so the redacted copy still verifies |

The raw payload is not in the record. A copy taken from the bucket checks against
`payload_hash`.

### What an evidence entry signs

A canonical CBOR map, `v` 1: `customer_id`, `seq`, `event_type`, `actor` (the verified
caller), `subject_kind`, `subject_ref`, `detail`, `recorded_at` (RFC 3339, UTC) and
`previous_hash`.

### When it happened

Evidence entries sign the time they were recorded. Session entries are ordered by their
chain, and a deployment computes Merkle roots over every chain's tip and publishes them
write-once; a root bounds when the entries under it existed.

## Merkle roots

Roots follow RFC 6962 / RFC 9162:

- leaf = `SHA-256(0x00 ‖ data)`, where `data` is the chain's id, its tip `entry_hash` and
  the tip's `seq`, joined with the byte `0x1f`;
- node = `SHA-256(0x01 ‖ left ‖ right)`;
- inclusion proofs as in RFC 9162 §2.1.3.

An export carries the root, the leaf and the proof. Compare the root with the copy the
deployment published to write-once storage.

## Checking an export

An evidence export (one case, or a period) is a zip:

| File | What it holds |
|---|---|
| `manifest.json` | Every file's SHA-256, and where the exported evidence chain starts |
| `manifest.cose` | The manifest, signed by the workspace evidence key |
| `keys/evidence.json`, `keys/hosts.json` | The public keys that signed the evidence chain and the session chains |
| `evidence/chain.jsonl` | The workspace evidence chain |
| `export_entry.json` | The evidence entry that recorded this export, committing to the manifest's hash |
| `ledger/*.jsonl` | The session chains the evidence cites, in full |
| `population.csv`, `reconciliation.json` | Every flag in scope, and the totals that must add up |
| `anchor.json` | A Merkle root and the inclusion proof for the evidence chain's tip |
| `verify.py` | The verifier |

Run it with nothing but Python:

```bash
pip install cbor2 cryptography
python3 verify.py golem-evidence-....zip
```

It checks, and prints a line for each: every file against the manifest; the manifest's
signature; the evidence chain (links, recomputed hashes, signatures); that the export's
own entry follows the chain and commits to this manifest; each session chain against the
hosts' keys; that the population reconciles; and the Merkle inclusion proof. It exits 0
only when everything verifies. It is about 260 lines; read it before you trust it.
