Skip to main content

Forward audit logs to your SIEM

The AI Gateway emits structured JSON audit events to pod standard output for request admission, detection decisions, virtual key changes, and authentication outcomes.

Every event carries the fields required by NIST SP 800-53 controls AU-2, AU-3, and AU-8: a unique audit ID, a UTC timestamp, the subject, the component, the outcome, an optional target, and an extra block for forensic correlation.

Enable audit events

Emission is off by default and is enabled per gateway:

aigateway.yaml
apiVersion: ai-gateway.stacklok.dev/v1alpha1
kind: AIGateway
metadata:
name: ai-gateway
spec:
audit:
enabled: true

Enabling audit events captures every supported event without sampling. Filter and route events in your log pipeline.

What gets captured

Event typeFires whenOutcome
aigw.processor.request.blockedA detection blocks a requestdenied
aigw.processor.request.redactedA request body was masked before forwardingsuccess
aigw.processor.response.blockedResponse masking failed, so the client got a synthetic errordenied
aigw.processor.response.redactedA response body was masked before deliverysuccess
aigw.processor.detection.loggedA passive detector fired, nothing mutatedsuccess
aigw.processor.body.truncatedA request or response body exceeded the size limitfailure
aigw.processor.config.reloadedConfiguration was reloadedsuccess/failure
aigw.apikey.key.createdA virtual key was createdsuccess/error
aigw.apikey.key.rotatedA virtual key was rotatedsuccess/error
aigw.apikey.key.revokedA virtual key was revokedsuccess/error
aigw.apikey.key.updatedA key was enabled, disabled, or editedsuccess/error
aigw.apikey.authn.failedToken validation failed on an API callfailure
aigw.apikey.authz.deniedThe caller lacked the required roledenied
aigw.apikey.validation.failedAn invalid or unknown virtual key was presentedfailure
aigw.apikey.ratelimit.exceededA per-key request rate limit was exceededdenied

Event types use aigw.<component>.<resource>.<verb>.

Event schema

{
"audit_id": "b3d4f7a2-8c11-4f6e-9b0a-1c2d3e4f5a6b",
"type": "aigw.processor.request.blocked",
"logged_at": "2026-04-20T14:22:31.418Z",
"outcome": "denied",
"component": "main-processor",
"source": { "type": "network", "value": "10.42.0.17" },
"subjects": { "user": "alice", "virtual_key_id": "a7Hj3kLm" },
"target": {
"type": "llm_request",
"model": "gpt-4o",
"provider": "openai",
"route": "chat-completions"
},
"metadata": {
"extra": {
"request_id": "c0ffee01-2a3b-4c5d-6e7f-8091a2b3c4d5",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"client_ip": "203.0.113.42",
"pod": "ai-gateway-main-processor-7f6b9c-x2k4q",
"detection_types": ["CREDIT_CARD", "US_SSN"],
"action": "block",
"body_bytes": 1842
}
}
}

Subjects come from authenticated tokens on API events or a trusted upstream filter on request-path events. The gateway adds virtual_key_id after key validation.

Targets use {type: llm_request, model, provider, route} on request events and {type: virtual_api_key, key_id, prefix} on key events. Key events omit secret material. Match target.provider against these lowercase values: openai, anthropic, awsbedrock, azureopenai, gcpvertexai, and geminiaistudio.

Common extras are request_id, trace_id and span_id for OpenTelemetry correlation, client_ip, user_agent, pod, and the gateway's name and namespace.

Detection events add detection_types and action. action is block, redact, or log-only. Passive detections use shadow_types for detectors in shadow mode and log_only_types for log-only policy.

Request-path events carry body_bytes; response-path events carry chunk_bytes, which is per-chunk for streaming responses and must not be summed with body_bytes to estimate traffic.

Separate audit records from application logs

Each event is one line of JSON on the pod's standard output. Every audit record carries "logger":"audit" and "msg":"audit_event", so shippers should filter on that pair to separate audit records from ordinary application logs on the same stream.

Ship them

Each example below filters on those same two keys. Adapt hostnames, credentials, and <NAMESPACE> for your environment.

Fluent Bit to Splunk

[INPUT]
Name tail
Path /var/log/containers/*<NAMESPACE>*.log
Parser docker
Tag kube.audit.*
Refresh_Interval 5

[FILTER]
Name grep
Match kube.audit.*
Regex log .*"logger":"audit".*"msg":"audit_event".*

[FILTER]
Name parser
Match kube.audit.*
Key_Name log
Parser json
Reserve_Data On

[OUTPUT]
Name splunk
Match kube.audit.*
Host <SPLUNK_HEC_HOST>
Port 8088
Splunk_Token ${SPLUNK_HEC_TOKEN}
TLS On
event_source aigw-audit
event_index aigw_audit

Fluent Bit to Elastic

Keep the input and both filters from the Splunk example above, which tag records kube.audit.*, and swap the output. The Match value has to be that same tag, or the output receives nothing:

[OUTPUT]
Name es
Match kube.audit.*
Host <ELASTICSEARCH_HOST>
Port 9200
HTTP_User ${ES_USER}
HTTP_Passwd ${ES_PASSWORD}
tls On
Logstash_Format On
Logstash_Prefix aigw-audit
Replace_Dots On

Vector to Kafka

sources:
kubernetes_logs:
type: kubernetes_logs
extra_namespace_label_selector: 'kubernetes.io/metadata.name=<NAMESPACE>'

transforms:
parse_json:
type: remap
inputs: [kubernetes_logs]
source: |
. = parse_json!(.message)
if .logger != "audit" || .msg != "audit_event" { abort }

sinks:
kafka:
type: kafka
inputs: [parse_json]
bootstrap_servers: '<KAFKA_BROKERS>'
topic: aigw-audit
encoding:
codec: json
compression: snappy

Promtail to Loki

scrape_configs:
- job_name: aigw-audit
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
regex: <NAMESPACE>
action: keep
pipeline_stages:
- json:
expressions:
logger: logger
msg: msg
type: type
outcome: outcome
component: component
- match:
selector: '{logger!="audit"}'
action: drop
- labels:
type:
outcome:
component:

CloudWatch and rsyslog

The CloudWatch agent cannot filter on a JSON key natively. Either ingest everything and apply a log-group metric filter:

{ $.logger = "audit" && $.msg = "audit_event" }

or run Fluent Bit alongside it with the cloudwatch_logs output, using the filters above so only audit lines reach the dedicated log group.

For rsyslog, match both markers before forwarding:

if $msg contains '"logger":"audit"' and $msg contains '"msg":"audit_event"' then {
action(type="omfwd"
target="<SIEM_HOST>"
port="514"
protocol="tcp"
template="RSYSLOG_SyslogProtocol23Format")
stop
}

Retention, tamper evidence, and GDPR

Configure retention, tamper evidence, and privacy controls in your ingest and storage systems. Use lifecycle policies and write-once storage where your compliance requirements call for them.

Apply field-level redaction before events reach long-term storage. The gateway does not retain or replay emitted audit events.

The Kubernetes API audit log covers control-plane activity such as kubectl calls and operator reconciles. Configure it through the API server audit policy.

Interaction journaling, configured under spec.journaling, captures prompt and response content. Audit events capture decisions and reasons without the full interaction content.

Next steps