Audit Trail
Hash-chain, TSA anchoring, and crypto-shredding.
Rensei's audit trail provides a tamper-evident, cryptographically verifiable record of every security-relevant event on the platform. Each event is linked into a SHA-256 hash chain, periodically anchored to an RFC 3161 Timestamp Authority (TSA), and delivered to your SIEM in real time.
Hash-chain integrity
Every audit event carries two cryptographic fields:
| Field | Description |
|---|---|
entryHash | SHA-256 of canonicalJson(event) + prevEntryHash |
prevEntryHash | The entryHash of the immediately preceding event for this workspace |
sequenceNumber | Monotonically increasing per-workspace counter |
The first event in a workspace uses prevEntryHash = '0'.repeat(64) as the genesis sentinel. This chaining means that altering any historical event invalidates every subsequent hash - making silent tampering detectable.
Canonical JSON
Before hashing, the event payload is serialized with canonicalJsonStringify - a deterministic JSON serializer that sorts object keys alphabetically. This ensures consistent hashes regardless of key insertion order.
Chain verification
Verify the complete chain for a workspace:
# CLI
rensei audit attestation show --org acme-corp --json
# API
curl -X POST https://rensei.ai/api/audit/verify \
-H "Authorization: Bearer rsk_live_..." \
-H "Content-Type: application/json" \
-d '{ "workspaceId": "org_..." }'
# Returns:
# {
# "valid": true,
# "eventCount": 8432,
# "firstSequence": 1,
# "lastSequence": 8432,
# "brokenAt": null
# }When a break is detected, brokenAt contains the sequenceNumber and entryHash of the first invalid event. The admin chain-repair tool at /admin/audit-chain-repair can diagnose and optionally re-anchor a repaired chain.
Merkle root and TSA anchoring
Periodically (or on demand), the platform computes a Merkle root over all entryHash values for a workspace and anchors it to an external RFC 3161 TSA. This provides an independent proof that the audit chain existed at a specific time - even if the Rensei database were later modified.
How Merkle anchoring works
- All
entryHashvalues are fetched insequenceNumberorder. - A binary Merkle tree is built by iteratively hashing adjacent pairs (odd-length levels duplicate the last leaf).
- The root hash is submitted to the TSA as a
TimeStampReq(RFC 3161 DER format, SHA-256). - The TSA's
TimeStampResptoken is stored in theaudit_anchorstable.
TSA configuration
The TSA endpoint is configured by the platform operator at the deployment level. Deployment-level TSA configuration is covered in the operator docs. The default (development) TSA is https://freetsa.org/tsr; production deployments use a qualified TSA.
Merkle root API
# Compute the current Merkle root
curl -X POST https://rensei.ai/api/audit/merkle-root \
-H "Authorization: Bearer rsk_live_..." \
-H "Content-Type: application/json" \
-d '{ "workspaceId": "org_..." }'
# Returns: { "root": "a3f2...", "leafCount": 8432, "anchoredAt": "2026-06-02T..." }
# Get an inclusion proof for a specific event
curl "https://rensei.ai/api/audit/inclusion-proof?workspaceId=org_...&sequenceNumber=1234" \
-H "Authorization: Bearer rsk_live_..."
# Returns Merkle proof path (array of sibling hashes)Signing keys
Audit entries are signed with a per-workspace Ed25519 key managed by the platform operator. The corresponding public key is published per workspace at /.well-known/audit-keys/{workspace_id} (with a discovery manifest at /.well-known/audit-keys.json) as an RFC 7517 JWKS document, so external verifiers can independently verify event signatures.
# Fetch the public JWKS for a workspace
curl https://rensei.ai/.well-known/audit-keys/{workspace_id}
# Returns an RFC 7517 JWKS (EdDSA / Ed25519):
# { "keys": [{ "kty": "OKP", "crv": "Ed25519", "alg": "EdDSA", "use": "sig",
# "kid": "key_01abc...", "x": "base64url-public-key",
# "rensei:workspace_id": "ws_...", "rensei:created_at": "...", "rensei:revoked_at": null }] }Key rotation emits an audit.key_rotated event into the chain before the old key is retired. See Audit Keys JWKS for the full key-rotation procedure.
Crypto-shredding (GDPR compliance)
When an organization is deleted or a user exercises their right to erasure, the platform performs crypto-shredding: PII fields in historical audit events are encrypted with a per-workspace data-encryption key (DEK), and the DEK is then destroyed. This renders the PII irrecoverable without breaking chain integrity (the event hashes remain valid because they were computed over the encrypted form).
Shredding a workspace
curl -X POST https://rensei.ai/api/audit/shred \
-H "Authorization: Bearer rsk_live_..." \
-H "Content-Type: application/json" \
-d '{ "workspaceId": "org_...", "reason": "GDPR erasure request #4421" }'
# Emits audit.crypto_shred event before destroying the DEKCrypto-shredding is irreversible. The audit.crypto_shred event itself is recorded in the chain (without PII) as proof that shredding occurred and when.
Partitioned tables
The audit_events table uses monthly RANGE partitions. Queries that include a created_at filter will benefit from partition pruning. When writing integrations that read audit events, always pass a time range to avoid full-table scans.
-- Good: partition-pruned
SELECT * FROM audit_events
WHERE workspace_id = 'org_...'
AND created_at >= '2026-06-01'
AND created_at < '2026-07-01'
ORDER BY sequence_number;
-- Bad: full scan across all partitions
SELECT * FROM audit_events WHERE workspace_id = 'org_...';SIEM delivery
Every call to appendEvent() triggers fanOutToSIEM() synchronously before returning. SIEM delivery is fire-and-forget - a destination failure does not block event recording. See Observability for how to configure SIEM destinations.
Retention policy
Retention is configured per-org in platform/src/lib/audit-retention.ts. When retention runs, events older than the configured window are archived (not deleted) to cold storage. An audit.retention_archive_created event is appended to the chain when an archive is produced.
Event type taxonomy
The platform emits 60+ distinct event types. Key categories:
| Category | Examples |
|---|---|
| Agent lifecycle | agent.spawned, agent.completed, agent.failed |
| Identity | user.login, user.logout, user.invited, user.deprovisioned |
| Approval gates | approval.gate_created, approval.decision_submitted, approval.gate_resolved |
| Policy | policy.created, policy.evaluated, policy.archived |
| Audit integrity | audit.chain_break_detected, audit.key_rotated, audit.anchored |
| Access | sso.connection_created, scim.user_provisioned, m2m.token_issued |
| Workflow | workflow.published, workflow.deployed, workflow.instance_started |
| Compliance | compliance.artifact_generated |
Related pages
- Observability - OTel, Sentry, and SIEM destination setup
- BFSI Compliance - how audit events feed regulatory compliance artifacts
- Cedar Policies -
policy.evaluatedevents in the audit trail - Audit API - REST endpoints for chain verification and Merkle proofs