Data handling and retention
What CID222 writes to disk, what it deliberately never writes, what protects each of those records, and how long they are kept.
- Version: 0.4
- Role: admin_user, viewer
CID222 inspects prompts and replies, so it necessarily touches sensitive text. What it keeps is narrower than what it sees, and the difference is deliberate in each case.
The problem
A gateway that logs everything becomes the largest concentration of sensitive data in the organisation — a system bought to reduce exposure that creates a bigger one. A gateway that logs nothing cannot answer an auditor, cannot show a user why their prompt was blocked, and cannot prove a control was operating. The design question is not "log or not"; it is which record answers which question, and what protects it.
What is stored
Conversations
ai_sessions holds one row per conversation. Its context column is the message history the user
sees; ai_context is the possibly summarised history sent to the provider.
Both are stored masked. The gateway persists the masked user message and the masked assistant reply. Where reversible masking is enabled, unmasking is applied to a copy on its way out to the client, so the restored values never re-enter the stored history and never return to the provider on a later turn.
Detections
ai_content_detections holds one row per message that produced a finding. It stores
original_text_hash — a hash, not the text — the detection group, the action taken, and a JSONB
array of the entities found: their offsets, confidence, entity type and action.
The value inside that array is passed through the pseudonymization service before storage, which
replaces it with a display token built from the entity type and an index — <EMAIL_0> — plus a SHA-256 hash of the original
and its character length. The default is on: a filter row with no explicit setting, and an
entity type with no filter row at all, both pseudonymize. An administrator who turns
pseudonymization off for a filter is choosing to store that entity's plaintext.
The reversible-masking map
ai_sessions.pii_map is the one place this product stores original PII at rest. It holds
AES-256-GCM ciphertext of the placeholder-to-value map that lets a masked reply be restored for an
authorised reader, keyed by PII_MAP_ENCRYPTION_KEY (64 hex characters). It is merged each turn
and it dies with the session row.
Note
Without the key, nothing is written and nothing is read, and replies simply stay masked. A wrong key or a tampered row reads as an empty map, with the same result. Masked is the fail-closed state, never an error in a live conversation.
Event logs
ai_event_logs holds the audit trail, categorised as request, performance, security, error or
audit. Detection events are security events.
Extension and inspection telemetry
The browser extension writes one event per inspected request. Captured prompts are stored as masked previews; page URLs are stored for attribution.
What is never stored
- The raw prompt or reply, in the detection record. Only a hash of it.
- Original PII in the conversation history. The history is the masked text.
- Original PII in a detection entity, unless an administrator has turned pseudonymization off for that filter.
- Provider API keys in the clear.
ai_credentials.api_keygoes through a transformer and is stored asv1:-prefixed AES-256-GCM ciphertext. - Gateway API keys.
ai_api_keysstores an unsalted SHA-256 hash. The key is 32 bytes of cryptographic randomness, so there is no low-entropy guess space for a salt to protect. - Passwords. Bcrypt, 10 rounds, never returned in any response.
Warning
Provider-credential encryption derives its key from JWT_SECRET. Rotating that secret makes
existing credential rows unreadable — a failed decrypt yields an empty key and a logged error
rather than an exception, so the symptom is provider calls failing, not the gateway falling over.
Rows written before the transformer existed pass through unchanged, so a long-lived deployment
can still hold a plaintext credential. Check with a query on the column prefix before assuming a
deployment is clean.
Retention
There is one retention policy, over the event log, with a window per category:
| Category | Days kept |
|---|---|
| Request | 30 |
| Performance | 30 |
| Security | 400 |
| Error | 90 |
| Audit | 365 |
The security window is 400 days by design: a twelve-month audit observation window plus roughly five weeks of lead time, which also clears the six-month floor the EU AI Act sets for deployers.
A sweep runs every six hours, and on demand from Event Logs → Run retention sweep.
Danger
The policy does not delete until an operator turns it on. enforced defaults to false and
the sweep runs as a survey: it counts what it would remove, reports it, and removes nothing. A
shipped-on purge would mean an upgrade silently destroying whatever an installation already
holds. Until you enable enforcement, rows older than these windows are still on disk.
The extension's clean-traffic telemetry has a second, independent sweep: allow-verdict rows age out after 30 days by default, while block and redact rows are policy evidence and are never swept.
Tables no policy covers
Four tables hold compliance evidence and are surveyed but never swept:
ai_content_detections, ai_document_analysis, ai_compliance_snapshots and
ai_control_observations. They grow without bound. The survey reports their row count and oldest
row so the absence of a policy is a stated fact rather than an omission, and adding a deletion
path to them is treated as a decision for a person rather than a default.
Limits and known gaps
- No database-level encryption at rest is configured. Column-level protection covers the credential, the PII map, the password and the API-key hash. Everything else — the conversation history, the detection rows, the event log — sits in ordinary PostgreSQL storage. Encrypting the volume or the filesystem is a deployment decision; see hardening the appliance.
- The audit trail is not tamper-proof.
ai_event_logsis an ordinary table with no hash chain and no write-once storage. An administrator with database access can alter it. The compliance module's frozen snapshots and their content hashes are the nearest thing to an immutable record, and they cover periods, not individual events. - There is no right-to-erasure endpoint. Deleting a session cascades to its detections and takes its PII map with it, which is the closest available action, but no route erases one data subject across every table.
- Retention is unenforced out of the box, as above. An appliance that has never had enforcement switched on is retaining everything.
- The storage gap analysis circulated with this product is partly out of date. Written in January 2026, it lists plaintext provider API keys as a critical finding; that has been fixed. Its findings on encryption at rest and audit-log immutability still stand.
Related
- Pseudonymization and masking — how a value is replaced, and when it can be put back.
- Regulatory mapping — which of these records is evidence for which obligation.
- The appliance security model — who can reach the data at all.
Last updated on