Audit logs are newline-delimited JSON records.
Each line contains:
event: audit event payloadsignature: Signature over the encodedevent
Signature algorithm depends on the backend
- If
platform-eccis included signature algorithm always would beEd25519- If ONLY
platform-pqcis included signature algorithm always would beML-DSA-44Ed25519 currently has a higher priority because the present threat model does not yet require post-quantum signatures for every audit event, while ML-DSA signatures would significantly increase audit-log storage and network traffic. The priority can be switched to ML-DSA as the quantum threat becomes more immediate.
The signing key is TKeeper's integrity key. event.integrityKeyVersion tells the verifier which integrity public key version to use.
Example line, formatted for readability:
{
"event": {
"id": "01J9Y3J7F8H4B8N8H5M6Y2K3Q1",
"peerId": 1,
"integrityKeyVersion": 3,
"timestamp": 1760000000000,
"event": "keeper.sign",
"auth": {
"subject": "service:payments-api"
},
"context": {
"sid": "sign-01J9Y3K2H7G9M5N4"
},
"request": {
"method": "POST",
"path": "/v2/keeper/sign",
"remoteAddress": "10.0.12.44"
},
"crypto": {
"algo": "ECDSA",
"kid": "payments-hot",
"generation": 2
},
"digest": {
"purpose": "audit",
"hmacKeyVersion": 4,
"bodyHash": {
"alg": "HMAC_SHA256",
"value64": "3Aq9i3sM1c03eK1d8eAH7Q=="
}
},
"outcome": {
"statusCode": 200
},
"approvers": [
"Jq7P3Zx7T0Jmj5..."
],
"policy": {
"decision": "ALLOW",
"matches": [
{
"id": "small-payment",
"effect": "ALLOW"
}
]
},
"imposters": [],
"dead": []
},
"signature": "MEUCIQD3n7uN..."
}
Some fields depend on the operation. approvers appears only for approved operations. policy appears when a Verdict authority was evaluated. imposters and dead appear when a threshold protocol reports bad or unavailable peers.
auth.subject is the identity authenticated on the current HTTP hop. For a direct public request it is the external principal. For a protected peer request it is the keeper peer that signed the internal request, while optional auth.actor preserves the original external principal. Peer-only protocol rounds omit actor.
The policy object is Verdict's PolicyEvaluation: decision plus matched rules.
Integrity boundary
The signature detects modification of an individual encoded event when the verifier has the correct integrity public key. It does not by itself prove that the log is complete, correctly ordered, retained, or delivered to every configured sink.
For compliance or incident evidence:
- preserve events in an access-controlled external system
- retain integrity public keys for every referenced version
- monitor gaps, duplicate ids, and unexpected time ordering at the collector
- define retention and deletion controls outside TKeeper
- verify records independently instead of trusting only the producer
File sink
keeper.audit {
enabled = true
timeout = 1000
file {
directory = "/var/lib/tkeeper/audit"
extension = "ndjson"
prefix = "audit"
max-file-size-bytes = 67108864
roll-every = 1d
max-files = 10
retention-days = 30
gzip = false
fsync = false
}
}
Socket sink
keeper.audit {
enabled = true
socket {
host = "audit.local"
port = 443
tls {
protocols = ["TLSv1.3", "TLSv1.2"]
verify-hostname = true
trust {
mode = "system"
}
}
}
}
The socket sink marks a line accepted after TKeeper writes and flushes it to the local socket stream. This is not a collector durability acknowledgement. If the local write does not complete before ack-timeout, TKeeper treats that sink as failed.
Verification
Verify one signed audit line:
POST /v1/keeper/audit/verify
Body:
{
"event": {
"id": "01J9Y3J7F8H4B8N8H5M6Y2K3Q1",
"peerId": 1,
"integrityKeyVersion": 3,
"timestamp": 1760000000000,
"event": "keeper.sign",
"auth": null,
"context": null,
"request": null,
"crypto": null,
"digest": null,
"outcome": null,
"approvers": null,
"policy": null,
"imposters": null,
"dead": null
},
"signature": "..."
}
Response:
{ "valid": true }
Verify a batch:
POST /v1/keeper/audit/verify/batch
Body:
{
"logs": [
{
"event": {
"id": "01J9Y3J7F8H4B8N8H5M6Y2K3Q1",
"peerId": 1,
"integrityKeyVersion": 3,
"timestamp": 1760000000000,
"event": "keeper.sign",
"auth": null,
"context": null,
"request": null,
"crypto": null,
"digest": null,
"outcome": null,
"approvers": null,
"policy": null,
"imposters": null,
"dead": null
},
"signature": "..."
}
]
}
Batch response is keyed by event id:
{
"01J9Y3J7F8H4B8N8H5M6Y2K3Q1": {
"valid": true
}
}
Required permission:
tkeeper.audit.log.verify
Rotate the integrity key:
POST /v1/keeper/integrity/rotate
Required permission:
tkeeper.integrity.rotate
Restart every peer after rotating an integrity key. Peer integrity-key pins are process-local in 2.2.0; until restart, callers that previously contacted the rotated peer continue to reject its new signed responses.
Rotation keeps historical public keys for log verification and removes the corresponding historical private keys. Replaying only the current-version pointer fails closed when it no longer matches the stored history. A coordinated same-location replay of the pointer and its matching records can still pass local verification, as can a complete database rollback. Detecting that class of rollback requires an independently protected monotonic checkpoint or external audit anchor.
Audit-HMAC keys are encrypted in record-id-bound envelopes and must decode to exactly 32 bytes. TKeeper migrates older unbound HMAC records during unseal and does not become ready until the bound form validates.
Peers expose their current integrity public key on the internal API:
GET /v1/integrity/publicKey
Response:
{ "data": "base64-public-key" }
Failure behavior
When audit is enabled, TKeeper checks sink availability before protected operations. At least one configured sink must be available.
When an event is written, the operation continues if at least one configured sink accepts the event before the audit timeout. If all configured sinks fail or miss the timeout, the operation fails with AUDIT_FAILED.
Multiple configured sinks are therefore redundant destinations, not an all-sinks durability guarantee. If policy requires delivery to a particular archive, enforce and monitor that requirement at the deployment or collector layer.
Common problems
Audit sink is down
If at least one sink is alive, operations continue. If no sink is available, protected operations fail with AUDIT_NOT_AVAILABLE before the crypto session starts.
Verification fails
Check the line encoding, the Ed25519 signature, and event.integrityKeyVersion.