Audit Gateway and Evidence Service: security model
This page lists the guarantees checked by the kit section 11 tests (services/control-plane/tests/test_evidence_gateway.py, services/control-plane/tests/test_evidence_config_redaction.py). Operations are in ../../operations/en/siem-audit.md.
Guarantees
| Guarantee | Mechanism | Test evidence |
|---|---|---|
| No event lost | event and eai.audit_outbox row written in the transaction of the change; rollback = neither change nor event | test_rolled_back_change_leaves_no_event_and_committed_one_always_has_it |
| Delivery without sampling, recovery after an outage | audit_gateway job, exponential backoff, sequence order, idempotency key, receiver side dedup | test_gateway_redelivers_after_target_outage_without_loss_or_duplicate, test_gateway_publishes_to_real_jetstream_with_dedup (real nats-server 2.15.2) |
| Sequence gaps detected | outbox contiguity check, sequence_gap alert | test_gateway_detects_outbox_sequence_gap |
| Verifiable sealing | RFC 8785 canonical JSON, sha256 Merkle root, previous seal hash, Ed25519 signature, key id | test_seal_signature_verifies_online_and_offline, test_forged_seal_signature_is_rejected |
| Tampering detected | audit_integrity monitor over chain, seals and timestamps | test_monitor_detects_sql_tampering_of_a_sealed_event |
| Distinct timestamps | event_time < ingested_at < sealed_at, bounded skew tolerance | test_event_time_ingested_at_sealed_at_are_distinct_and_ordered |
| Payload access separation | Tink envelope, key outside the database, decryption for auditor and security_officer only, refused to platform_admin | test_payload_encrypted_and_only_auditor_decrypts_with_audited_reason |
| Audited access | every read, search, verification, export writes its event | test_reads_searches_and_exports_are_audited |
| Legal hold | blocks purge and erasure, released by two distinct approvers, purge evidence without contents | test_legal_hold_blocks_purge_and_release_needs_two_distinct_approvers |
| No secret or personal data in logs | masking in eai.logs (message, fields, exceptions); credentials masked in audit metadata | test_secrets_and_pii_never_reach_logs, test_audit_metadata_has_no_credentials_and_pii_goes_to_the_encrypted_payload |
| Backup and restore | chain, seals, legal hold and purge evidence verified after pg_backup.sh and pg_restore.sh | test_restored_backup_keeps_verifiable_chain_seals_and_hold_state |
Roles and access
| Operation | Route | Roles |
|---|---|---|
| List seals, verify a seal or the whole chain | GET /v1/evidence/seals, GET /v1/evidence/seals/{seal_id}/verify, GET /v1/evidence/verify | auditor, security_officer, tenant_admin |
| Seal now | POST /v1/evidence/seals | security_officer, auditor |
| Export the offline verifiable bundle | GET /v1/evidence/bundle | auditor, security_officer |
| Decrypt the payload of an event, with a reason | POST /v1/evidence/events/{event_id}/payload | auditor, security_officer only (explicit check, platform_admin refused) |
| Access log | GET /v1/evidence/access-log | auditor, security_officer |
| Integrity alerts, gateway status | GET /v1/evidence/alerts, GET /v1/evidence/gateway | auditor, security_officer, tenant_admin |
| Set, list, release a legal hold | POST /v1/evidence/holds, GET /v1/evidence/holds, POST /v1/evidence/holds/{hold_id}/release | auditor, security_officer, tenant_admin |
| Run the payload purge | POST /v1/evidence/retention/run | security_officer, tenant_admin |
| Read purge evidence | GET /v1/evidence/retention/evidence | auditor, security_officer, tenant_admin |
Releasing a legal hold takes two calls by two distinct approvers: the hold is released at the second approval; the same approver twice gets 409 audit_hold_same_approver.
Settings required in production
The startup guard (eai.security.startup) refuses in production:
EAI_AUDIT_SEAL_SIGNING_KEY_REFmissing (the Ed25519 seal key must come from OpenBao,EAI_AUDIT_SEAL_SIGNING_KEYas a PEM is for development and test only);EAI_AUDIT_PAYLOAD_KEKother thanopenbao-transit;EAI_AUDIT_SIGNINGset tohmac, orEAI_OPENBAO_URLandEAI_OPENBAO_TOKENmissing (WORM export signature);EAI_AUDIT_CLOCK_SKEW_SECONDSoutside 0 to 3600.
The full list of Audit Gateway settings (intervals, batches, attempts, NATS subject, HTTP collector) is in ../../operations/en/siem-audit.md.
Verify evidence offline
The bundle of GET /v1/evidence/bundle carries a manifest signed by the seal key. It is verified without access to the platform with the eai.domains.evidence.verify module:
cd services/control-plane
.venv/bin/python -m eai.domains.evidence.verify <bundle.json>
<bundle.json>: the file returned by GET /v1/evidence/bundle. Result: a JSON report (valid, chain, seals, clock), exit code 0 when the bundle is valid, 1 otherwise.
A wrong signature or an event modified after sealing is reported (test_seal_signature_verifies_online_and_offline, test_forged_seal_signature_is_rejected).
See also: risk-compliance.md (evidence and control catalogue), retention-privacy.md (legal holds and erasure).
Limits
- The database owner role can modify rows: tampering is detected (chain, signed seals), not prevented. Storage immutability belongs to the WORM export, qualified only with a storage profile whose object lock was verified.
- The OPA bundle revision is recorded only when the decision reports it.