Skip to content

Data handling

This is the page a data-protection officer reads. It describes the proxy log and run ledger as a dataset: what it contains, where it lives, how long it is kept, who can see it, and how a person’s data is removed from it. Everything here is enforced in code.

Every governed call — model, tool, skill, agent-to-agent — writes one row to the proxy log. A row has two layers:

Layer Fields Covered by the audit hash chain
Record — who called what, and what was decided timestamp, proxy type, endpoint, model, tool name, actor and subject ids, guardrail and policy decisions, status, cost, run and turn ids Yes
Content — what was said request and response body previews, request and response headers No

The record layer is what assurance is computed from. The content layer is the tenant’s most sensitive data: prompts, tool arguments, model output. The two are separated on purpose so that content can be redacted, removed or erased without touching what the hash chain attests.

Runs are rollups over proxy-log rows and hold counters, outcomes and hashes, not content. Portal thumbs feedback holds an optional free-text comment. Headers are stored with authorization, x-api-key and cookie values replaced by [REDACTED] before they reach the writer.

Each tenant sets a retention policy under Tenant settings → Data retention (or PUT /v1/admin/tenants/{id} with a data_retention object):

data_retention
{
"store_bodies": "redacted",
"body_days": 30,
"log_days": 180,
"run_days": 730
}
store_bodies Behaviour
full Bodies stored as received, capped at 64 KiB each. The default.
redacted Every stored body passes through the local PII redactor at write time — independent of whether a PII guardrail is configured for the request.
none No body or header text is stored. Record-layer fields are unaffected.

The mode is applied in the proxy’s audit writer at flush time, so it holds for every surface — LLM, MCP, skills, A2A — without per-route wiring. The proxy caches the policy for 60 seconds; a change takes effect within a minute.

Whatever the mode, a SHA-256 digest of each body is sealed before it runs and kept on the row, inside the tamper-evident hash. none and redacted therefore still let a recipient who holds the original bytes prove what was sent and returned — the policy decides whether content is stored, never whether what it was can be established. Field by field: The audit row.

How long it is kept — the retention sweeper

Section titled “How long it is kept — the retention sweeper”

The three windows are each optional (null keeps forever; 1–3650 days otherwise) and body_days may not exceed log_days:

Window What happens after it Effect on the audit chain
body_days Bodies and headers removed from the row; bodies_pruned_at set None — the preview text is outside the hash; the content digests (request_content_hash, response_content_hash) stay on the row and inside it
log_days Row deleted Declared: the removed sequence numbers and the hash at the end of every contiguous run are recorded in the same transaction
run_days Run row and its feedback deleted None — runs are not chained

The sweeper runs hourly on the Control Plane and drains at most 5 000 rows per pass per tenant, so a backlog is removed at a bounded rate.

Two permissions, deliberately separate:

Permission Grants
proxy:logs:read The record layer: rows, filters, statistics, traces
proxy:logs:read_bodies The content layer: stored bodies and headers

Without read_bodies, the API omits bodies and reports body_state: withheld; body text is also excluded from free-text search so content cannot be probed through filters. Every body read is an admin-audit event. On upgrade, every role that could already read logs is granted read_bodies so nobody loses access silently — remove it from roles that should not see content. The log viewer shows each row’s body state: available, withheld, pruned, erased, or none.

The ledger is a table in the tenant’s PostgreSQL. In the self-hosted bundle and in customer-hosted deployments that database is yours; the proxy and control plane connect to it and store nothing elsewhere. Brutor does not receive ledger content.

Data residency governs where upstream requests are routed — which provider regions may serve a tenant — and stamps residency_class on each row. It does not move the ledger; the ledger is wherever the database is.

Erase a subject
curl -X POST http://localhost:5050/v1/admin/data-erasure \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email": "a.person@example.com", "dry_run": true }'

Requires the data:erasure permission and takes any of end_user_id, idp_user_id, email, subject_id. With dry_run: true it returns the counts without changing anything.

Removes

  • bodies and headers on every proxy-log row attributed to the subject (erased_at set);
  • direct identifiers on those rows: IdP email and username, gateway username, client IP, user agent;
  • the subject’s feedback rows;
  • the end-user record itself, which cascades to its API keys, memberships and connector identities and nulls its references on usage logs, batch jobs, skill executions and agent ownership.

Keeps

  • the record layer of each row and its pseudonymous keys (gateway_user_id, idp_user_id, subject_id, actor_id). subject_id is covered by the audit hash chain; rewriting it would break the verification the chain exists to provide. Once the end-user record is gone, those keys no longer identify a person (GDPR Recital 26 — pseudonymised data);
  • the append-only inbox trail: the decision that a finding was reviewed is retained; the finding’s content lives in the rows above and is erased with them.

Every request writes an append-only erasure event carrying a SHA-256 fingerprint of the identifiers named (never the identifiers), the requesting admin, and the counts of what each table gave up. GET /v1/admin/data-erasure lists them.

Rows that arrived through the OpenTelemetry inlet (source = otel) carry no bodies, headers or costs: the exporter never sends them and the gateway never had them. Retention, erasure and read permissions apply to those rows exactly as above.

  • Redaction is pattern-based. A redacted store removes the PII categories the local detector recognises; it does not guarantee that free text contains no personal data.
  • Erasure covers the gateway’s own stores. Content that reached an upstream provider is governed by that provider’s terms; content in a knowledge base is governed by the KB’s own lifecycle.
  • Retention windows are enforced by the Control Plane scheduler; if the Control Plane is down, nothing is deleted until it returns — the failure direction that cannot lose evidence.