Opt-in logging

Inference logging is off by default. With it off, the gateway writes no per-request row at all: it enforces your policy, forwards the request, and keeps nothing.

Switching it on gives you one row per proxied request. Metadata only — see zero data retention for why content cannot appear here even in principle.

vulnetix ai-firewall settings --logs      # on
vulnetix ai-firewall settings --no-logs   # off

Or the Usage tab of the dashboard. Requires the Pro plan; see what is free and what is paid. Turning logging off is never gated, so a downgrade is not a trap.

Check the current state:

vulnetix ai-firewall status
# Inference logging: on

Exactly what is recorded

The complete field list. There is nothing else.

FieldWhat it is
providerSlugWhich provider the request went to
modelThe model requested. Empty on a row for a provider refusal, which happens before the body is read
wireWhich request surface it arrived on — chat, responses, or messages
decisionallow, block, redact, or flag
blockedByWhat blocked it, if anything: the name of a guardrail rule, or a policy label — provider_policy (the provider is denied), provider_key_missing (no provider key is stored), model_policy (a model deny or allowlist), or, for a custom provider, upstream_blocked / upstream_redirect
violationsWhich rules fired — names and types, never the matched content
inspectedHow many text segments the guardrails read
statusCodeThe HTTP status returned
promptTokens / completionTokensToken counts, when the provider reports them
streamWhether the response was streamed
latencyMsHow long it took
clientName / clientVersionWhich agent sent it, from its User-Agent and related headers — claude-code, codex-cli, unknown. Self-reported, so attribution rather than proof
contextHashA hash of the capability set the request declared. Joins to the capability inventory; a change between consecutive rows is the moment a developer wired something new in
toolCount / mcpCountHow many tools and MCP servers the request declared
createdAtWhen

A request refused because the provider is denied (or has no stored key) is recorded too, as a block with an empty model, since it is refused before the request body is read. Like every other row it is written only when logging is on and your plan includes it.

What is not recorded

  • The prompt. Any part of it.
  • The completion. Any part of it.
  • The content that tripped a guardrail. A flag records that a rule matched and which one — never what it matched.
  • Headers, tool definitions, tool arguments, or tool results.
Note

Your capability configuration — the names of the tools and MCP servers your agents offer the model — is held separately, and is not affected by this switch. It is policy configuration, not activity: it records that your estate has a mcp__github__* toolset, never that anyone used it for anything. It exists so a tool rule can be written against a real name rather than a guess, which is the same reason the provider and model catalogs exist.

No prompt, completion, tool argument or tool result is read to build it.

The two fields worth understanding

inspected — proof the firewall actually looked

This is the number of text segments the guardrail engine read out of the request.

It exists to make one specific, nasty failure visible: a request that parses cleanly but exposes nothing to inspect is a firewall screening nothing — and without this number it looks exactly like a clean pass. Everything is green, every dashboard says allow, and no rule is running against any text.

An inspected of 0 against a non-empty policy set is a bug, and it is the one you most want to catch. It is the reason the field is there.

wire — which client this actually was

Which surface the request arrived on. Without it, a Codex inference is indistinguishable from a Claude Code one after the fact, and any per-client incident becomes impossible to reason about.

What you can do with it

  • Tune your guardrails. Which rules fire, how often, and on which surface. This is how you run the flag → redact → block ladder with evidence rather than a guess.
  • See which models are actually being used. Frequently, this is not the set anyone expected, and it is the input to a sensible model allowlist.
  • Attribute cost. Token counts per model, per surface. Note: the firewall does no cost accounting itself and applies no markup — this is raw token counts, and your bill still comes from the provider.
  • Investigate a block. blockedBy and violations tell you exactly which rule refused a request and why, without exposing what was in it.

The trade-off, stated plainly

Because violations never record content, you cannot read the prompts your rules caught. You learn a rule fired forty times; you cannot see the forty prompts.

That makes tuning harder. It is a deliberate choice: the alternative is that enabling a guardrail creates a new datastore containing the most sensitive strings your organisation produces, which is precisely the thing you adopted a firewall to prevent.

Tune by reasoning about the pattern, not by reading the traffic. See tuning false positives.

Turning it off

vulnetix ai-firewall settings --no-logs

Takes effect on the next request. New rows stop being written immediately.

In policy as code

spec:
  settings:
    logsEnabled: true

See policy as code.