Skip to content

Parameter Disclosure

Every Agent Receipt commits to its tool-call parameters via parameters_hash — a SHA-256 digest that proves what happened without retaining the raw payload. That is the privacy-preserving baseline and the default for every emitter.

Parameter disclosure is the opt-in mechanism that lets an operator also make the raw parameters recoverable — but only by the holder of a specific private key, and only on demand. The parameters are encrypted inside the signed receipt body using HPKE (Hybrid Public Key Encryption). The daemon, the SDKs, every storage adapter, and every downstream sink all see opaque ciphertext. Plaintext never hits the wire.

Out of the box, with no disclosure configuration, the daemon stores only parameters_hash:

"action": {
"type": "system.command.execute",
"risk_level": "high",
"parameters_hash": "sha256:9c84a8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1"
}

The hash is authoritative for integrity — it binds the signed receipt to the exact parameters object. parameters_disclosure is always additive; enabling disclosure never replaces the hash.

When disclosure is enabled and the daemon is given a forensic X25519 public key, it encrypts each qualifying parameters object and attaches the result as parameters_disclosure:

Parameters JSONRFC 8785 canonicalHPKE encryptX25519 forensic pubkeyparameters_disclosureopaque ciphertextSigned ReceiptEd25519 proofplaintext (transient)daemon only; not storedstored; recoverable byprivate-key holder only

The v1 envelope uses HPKE base mode (RFC 9180) with ciphersuite hpke-x25519-hkdf-sha256-aes-256-gcm — KEM = DHKEM(X25519, HKDF-SHA256), KDF = HKDF-SHA256, AEAD = AES-256-GCM. Both info and the AEAD additional-data field are the empty string. There is no nonce field; HPKE single-shot derives it internally from the KEM output.

A concrete envelope as it appears in a receipt:

"action": {
"type": "system.command.execute",
"risk_level": "high",
"parameters_hash": "sha256:9c84a8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
"parameters_disclosure": {
"v": "1",
"alg": "hpke-x25519-hkdf-sha256-aes-256-gcm",
"recipients": [
{
"kid": "sha256:3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4",
"enc": "N_2jVnvb1ijohmjDyNfpfR0SU7bU6m1EwVD3QfG_RDE"
}
],
"ct": "YGn3i4NpiZxHjeZVggTP8lTxb0ZVdLl-2HjW31qsvo28PjQ_Lt_UQgAMidEXjzwhJPHM7OM"
}
}

All binary fields (enc, ct) are unpadded base64url (no =, +, or /). The kid value is the key fingerprint: sha256: followed by the lowercase hex SHA-256 of the raw 32-byte X25519 public key. This lets a forensic tool derive the fingerprint from a held private key and locate matching receipts without any key registry.

The parameters_hash is always present, whether or not disclosure is enabled. The envelope is additive metadata; removing it has no effect on chain verification.

For the full field reference, see the Agent Receipt Schema — parameters_disclosure envelope.

The signing key (Ed25519) and the forensic key (X25519) are different keys with different purposes and different holders:

Signing keyForensic key
AlgorithmEd25519 (signing)X25519 (HPKE)
Held byDaemon (private key never leaves)Public key: daemon config. Private key: forensic responder.
PurposeProves the receipt is authentic and unmodifiedAllows parameters to be recovered on demand
Can read disclosures?NoPrivate-key holder only
Can forge receipts?No (public key only in config)No

The daemon holds only the forensic public key and genuinely cannot read its own past disclosures — the same trust model as the agent not owning its own audit trail (per ADR-0010). Signing-key holders cannot read disclosures; forensic private-key holders cannot forge receipts.

The parameter_disclosure setting controls which actions get an encrypted envelope attached. It is configured on the daemon, which owns disclosure policy under ADR-0010 (daemon process separation). The daemon offers four modes; only the spelling of the allowlist differs by config surface (see the note below):

ModeBehavior
off (false)Hash only — no envelope (default)
all (true)Envelope for every action
high ("high")Envelope for high and critical risk actions only
allowlistEnvelope for the listed action types only

The "high" mode uses the taxonomy’s risk classification. The allowlist is a set of action-type strings; see the Action Taxonomy for the full list.

Disclosure is controlled by operator config — never by agent-supplied input. The daemon reads the forensic public key and the disclosure granularity from its config file, environment, or flags.

The daemon ships a one-shot generator. It writes the raw 32-byte private key (mode 0600) and public key (mode 0644), and prints the fingerprint so you can confirm the key the daemon encrypts to matches the private key you keep for recovery:

Terminal window
agent-receipts-daemon --init-forensic-key \
--forensic-key ~/.local/share/agent-receipts/forensic.key
generated forensic private key: ~/.local/share/agent-receipts/forensic.key (keep this offline; the daemon never reads it)
forensic public key: ~/.local/share/agent-receipts/forensic.key.pub
fingerprint (kid): sha256:c1102522e88e2511…

Keep the private key offline — the daemon never reads it. Only the public key goes into daemon config. Like signing-key generation, the command refuses to overwrite an existing key file.

In TOML, parameter_disclosure accepts a boolean, a keyword string, or an array of action types:

~/.local/share/agent-receipts/daemon.toml
# Disclosure mode. Accepts: false | true | "high"
# | an action-type array ["system.command.execute", "filesystem.file.delete"]
# | a comma-separated string "system.command.execute,filesystem.file.delete"
parameter_disclosure = "high"
# Path to the forensic X25519 public key file (the raw 32-byte key)
forensic_public_key = "/home/me/.local/share/agent-receipts/forensic.key.pub"

The flag and environment variable have no array type, so the allowlist form is a comma-separated string:

Terminal window
# keyword mode
AGENTRECEIPTS_PARAMETER_DISCLOSURE=high \
AGENTRECEIPTS_FORENSIC_PUBLIC_KEY=/path/to/forensic.key.pub \
agent-receipts-daemon
# allowlist mode (comma-separated)
agent-receipts-daemon \
--forensic-public-key /path/to/forensic.key.pub \
--parameter-disclosure system.command.execute,filesystem.file.delete

The daemon will not start when the disclosure policy is enabled (anything other than off/false) and no forensic_public_key is configured — an envelope requires a recipient.

When both are present, the daemon logs the forensic key fingerprint at startup so you can confirm it matches the private key you intend to use for recovery:

Parameter disclosure ACTIVE: policy=high, forensic key sha256:3b4c5d6e… — matching parameters will be HPKE-encrypted to that key (recoverable only with the private key)

See Daemon Setup for the full configuration reference.

If HPKE encryption fails for a given event — for example, due to a malformed key or a transient error — the daemon falls back to hash-only for that receipt and logs a warning. The receipt is still stored and the audit chain stays intact and gap-free. A persistent failure at startup (unreadable key file, wrong key format) is a fatal error; the daemon will not start.

When an operator holds the forensic private key, they can decrypt the parameters_disclosure envelope for any matching receipt.

A solo operator generates a forensic keypair, configures the daemon with the public key, and later loads their private key into the dashboard or a CLI tool. The workflow:

  1. Generate a forensic keypair (X25519) with agent-receipts-daemon --init-forensic-key (see Generate a forensic key pair above).
  2. Configure the daemon with the public key path and the desired disclosure mode.
  3. During an incident or audit, load the private key into the dashboard.
  4. The dashboard derives the key fingerprint (sha256:<hex of raw public key>), queries receipts by kid, and decrypts each matching parameters_disclosure inline.
  5. The operator sees the original parameters in the receipt detail view.

The dashboard feature is the intended forensic-recovery UX for the envelope. CLI-based recovery for automated forensic pipelines is also planned (see ADR-0012 Phase B).

You don’t need the dashboard (or the planned forensic CLI) to read an envelope. All three SDKs ship a decrypt helper that works today — Python decrypt_disclosure, Go DecryptDisclosure, and TypeScript decryptDisclosure. The file --init-forensic-key wrote is the raw 32-byte X25519 private key (no PEM, no decoding); pass it together with the envelope from agent-receipts show <seq> --chain-id <id> --json — found at credentialSubject.action.parameters_disclosure — to your SDK’s helper. The plaintext lives only in your process; nothing is written back to the store.

Enabling disclosure on payloads that contain personal data makes the operator a data controller for that data under GDPR and equivalent regulations. The encrypted ciphertext is still personal data if the corresponding private key is held within the jurisdiction.

The right-to-erasure story is crypto-shredding: destroy the forensic private key and every parameters_disclosure envelope becomes permanently unreadable, while the receipt chain — and its hash-based tamper-evidence — stays fully intact. No rows are deleted; no chain integrity is broken.

Practical checklist before enabling:

  • Inventory which action types produce sensitive parameters in your deployment.
  • Confirm the forensic private key is stored securely, with appropriate access controls and backup.
  • Document a key-rotation and crypto-shredding procedure before enabling disclosure in production.
  • If your jurisdiction has data-residency requirements, confirm the database (and any SIEM fan-out) is located appropriately.

Public keys can rotate freely — configure the daemon with a new forensic public key and new receipts use it. Private keys accumulate: because receipts are immutable, you cannot re-encrypt historical disclosures when the forensic private key rotates. Retain all historical forensic private keys alongside the receipt database or you will permanently lose the ability to recover older disclosures.

ThreatMitigated?How
Agent reads its own parameters from the receipt storeYesDaemon holds only the public key; private key lives with the forensic responder
Storage adapter or SIEM sees plaintext parametersYesOnly ciphertext is stored or forwarded; adapters need no key-management
Attacker modifies the ciphertext in the databaseYesEd25519 signature over the full receipt (including parameters_disclosure) detects any tampering
Forensic responder forges a receiptNo (by design)Signing and forensic keys are separate; the forensic private key has no signing capability
Disclosure gap caused by an encryption failureMitigatedEncrypt-failure falls back to hash-only; the receipt is stored and the chain stays valid
PII leakage via plaintext windowMitigated (daemon mode)Daemon minimises the plaintext window to a single process that doesn’t hold the signing key; SDK-direct mode keeps it in the agent process