Audit & SIEM
Every tool call, sign-in, and administrative change passes through one place, so the Gateway can record all of it. This page covers what is recorded, where it goes, and what leaves your deployment.
Every setting here lives on your gateway pool in the Britive tenant portal, in the Audit and Integrations groups. Changes reach a running Gateway on its next sync - nothing needs a restart.
Where Events Go
Events are always written to two places, and optionally to two more:
| Sink | Always on? | Contents |
|---|---|---|
| Postgres | Yes - the durable record | Full events, queryable |
| stdout | Yes | Structural line by default; full event optionally |
| SIEM webhook | No | Every recorded event, as JSON |
| Britive tenant audit stream | Yes, by default | A selected subset |
Postgres is the source of truth. The admin console reads it, and every replica sees the same table.
What Is Recorded
Recording is grouped into categories. audit.categories (“Recorded categories”)
selects the ones you want:
| Category | Contents |
|---|---|
access | Tool invocations. The audit trail proper. |
auth | User and administrator sign-in, OAuth token and callback events. |
admin | Operator changes: mount, unmount, publish, endpoints, sync. |
catalog | Which tools a caller can see. |
system | The Gateway’s own bookkeeping - cache hits, sync polls, schema captures, token plumbing. High volume, and not an audit trail. Add it when diagnosing a problem. |
The default is ["access", "auth", "admin", "catalog"].
Denials, failures, and policy bypasses are always recorded, regardless of this setting. Narrowing the categories cannot conceal the events an investigation requires.
Catalogue events are recorded on change
catalog events fire when a caller’s resolved tool set changes, not on every
listing. Clients re-list constantly, and per-listing rows would dominate the table
without adding information. Change events include the resolved tool names.
So that silence still means something, an unchanged catalogue re-affirms itself
once every audit.toolsListHeartbeatSeconds (default 86400, 24 hours). An
absence of events therefore means nothing changed - not that logging stopped.
Retention
| Setting | Portal label | Default | Purpose |
|---|---|---|---|
audit.retentionDays | Retention | 90 | Events older than this are deleted. 0 disables the sweep. |
audit.retentionSweepIntervalMinutes | Retention sweep | 1440 (24h) | How often the maintenance worker wakes. Advanced group. |
Rows are deleted in batches of ten thousand per statement, so a large backlog goes in several short transactions rather than one long one. That batch size is fixed.
The sweep is coordinated across replicas with database advisory locks, so only one container performs each delete.
Set retentionDays to 0 only if a separate process is responsible for the audit
table. If you prefer to handle it yourself:
DELETE FROM audit_events WHERE occurred_at < NOW() - INTERVAL '90 days';The Gateway’s sweep and your own job both no-op safely if the other already ran.
The stdout Line
By default the stdout line is structural only and omits prompts and tool
arguments. Turn on audit.stdoutFull (“Full events to standard output”) to write the
complete event, and only if your log pipeline is inside your trusted perimeter. Full
events always reach Postgres regardless.
There is no JSONL file option. Postgres, stdout, the SIEM webhook, and forwarding to your tenant cover every real destination, and a file path inside a container nobody mounts covered none of them.
Streaming to a SIEM
Every recorded event is POSTed as JSON by a non-blocking background worker.
Everything in audit.categories is sent; your pipeline decides what to keep.
| Setting | Portal label | Default | Purpose |
|---|---|---|---|
audit.webhook.url | SIEM webhook | "" | Endpoint. Setting it enables the webhook. |
audit.webhook.headers | SIEM webhook headers | {} | Additional headers, as a mapping. Stored encrypted. |
audit.webhook.transform | SIEM payload transform | "" | Optional JMESPath expression reshaping each event before sending. |
audit.webhook.verifyTls | Verify the SIEM’s certificate | true | Verify the endpoint’s certificate. |
audit.webhook.timeoutSeconds | SIEM timeout | 5.0 | Per-request timeout. Advanced group. |
audit.webhook.maxRetries | SIEM retries | 3 | Retries per event. Advanced group. |
The buffer holds ten thousand events; if it fills, events are dropped and logged. That size is fixed.
The headers usually carry your SIEM’s credential, so they are treated as a secret: the platform stores them encrypted, never returns them to an administrative reader, and shows only whether a value is set. Submitting the masked placeholder leaves the stored value alone. The Gateway itself receives them, because it is the one reader that has to.
Splunk HEC
| Field | Value |
|---|---|
| SIEM webhook | https://splunk.example.com/services/collector/event |
| SIEM webhook headers | Authorization: Splunk <HEC token> |
| SIEM payload transform | {event: @, sourcetype: 'britive.audit'} |
Datadog Logs
| Field | Value |
|---|---|
| SIEM webhook | https://http-intake.logs.datadoghq.com/api/v2/logs |
| SIEM webhook headers | DD-API-KEY: <api key> |
| SIEM payload transform | {ddsource: 'britive', service: 'mcp-gateway', message: message, attributes: @} |
A generic collector
| Field | Value |
|---|---|
| SIEM webhook | https://your-collector.example.com/events |
| SIEM webhook headers | Authorization: Bearer <token>, X-Source: britive-mcp-gateway, X-Env: prod |
| SIEM payload transform | (leave empty for the raw event) |
If a transform expression fails to evaluate, the original payload is sent and a
warning is logged. Events are never dropped silently.
Streaming to Your Britive Tenant
The Gateway also forwards selected events to your tenant, where they appear in its
own audit history alongside native events, recorded as gateway.<eventType>. This
is on by default, so a Gateway’s activity is visible in the audit history of the
tenant it serves. Authentication uses the gateway pool token.
| Setting | Portal label | Default | Purpose |
|---|---|---|---|
audit.platform.enabled | Send events to Britive | true | Forward to the tenant. Requires a pool token - without one, forwarding disables itself at startup and records why. |
audit.platform.events | Events to forward | see below | Which actions to forward, by name. |
audit.platform.includeContent | Include call content | false | Whether to include prompts, intent, arguments, and captured response bodies. |
Batch size, flush interval, buffer size, and request timeout are fixed. Both ends of this sink are Britive’s, so there is nobody for whom a different flush interval is the right answer.
The default event set is the mutating and security-relevant one:
| Action | What it is |
|---|---|
tools.call | Tool invocations, permitted and denied alike. |
user.login | End-user sign-in through the Gateway. |
admin.login | Admin console sign-in. |
admin.publish-tools | Publishing a backend’s tool catalogue to the platform. |
Read-only listing activity is excluded by default because clients re-list frequently
and those events would outnumber the ones an auditor is looking for. The portal offers
the rest of the vocabulary as a checklist, including tools.list, tools.search,
settings.refresh, the oauth.* actions, and backends.sync.
Inspection verdicts (tools.call.inspect) are not forwarded by default. Add the
action if you want inspection findings in your tenant’s audit history as well as in the
Gateway’s own log and your SIEM.
An unrecognised action name is rejected rather than stored. This is deliberate: a typo would otherwise look configured and forward not one event.
What “Include call content” changes
audit.platform.includeContent: true means prompt text, query text, secret paths, and tool
arguments leave your deployment and render verbatim in the tenant’s audit
interface. The Gateway treats those fields as sensitive everywhere else, so
inclusion is opt-in.
With it disabled, every fact about a call is still forwarded: who called it, which
tool and backend, whether it was permitted, inspection verdicts, timings, and
failure reasons. The _llm_session_id correlation handle is forwarded either way,
because it is an identifier rather than content.
Forwarding needs a tenant whose mcp-manager exposes the audit ingest endpoint. An older tenant rejects the requests and the Gateway logs a warning for each batch - turn Send events to Britive off against such a tenant to stop them.
Reading the Audit Log
The admin console has an Audit Events view with filters for actor, action, backend, tool, decision, outcome, session, and a time range. Expanding a row shows the full event, including any inspection verdict and its findings ordered worst-first.
The same data is available over HTTP:
curl "https://mcp-gateway.example.internal/admin/audit?session_id=<uuid>"Filtering by session_id is the one worth knowing: it pulls back every call the
model made during one chat or coding session as a single episode, rather than
leaving you to correlate timestamps.