Skip to content

Silent degradation

Gateways must report when they cannot apply a configured control. A silent fallback can make an accepted request look fully inspected when it was not.

A struct that does not model a new provider field can drop it. A capability the upstream does not support can go unapplied. An oversized body can go uninspected. An async detector can fail to schedule. Each case needs an explicit result or metric.

The sections below describe these cases and show how to verify each one.

The request body is not rebuilt. It is forwarded byte for byte when the client and the provider speak the same dialect, so a field pistra has never heard of reaches the provider intact, and a field the provider adds tomorrow works today.

The alternative, parse into a struct, re-serialise, silently drops everything the struct does not model, which is every field added after the release you are running.

Verify it in Passthrough. That page also names the cases that do require a rewrite.

A lossy route is refused, not taken quietly

Section titled “A lossy route is refused, not taken quietly”

When a candidate would lose a capability, the fidelity guard skips it, rejects the request, or forwards it only when policy allows that loss. The gateway counts which action it took.

pistra_channel_downgrade_total carries that decision:

Fidelity-guard decisions: requests using a capability a resolution candidate loses. skipped passed over a lossy candidate, allowed forwarded lossy by policy, rejected refused the request.

The refusal names every affected capability and cites the catalog claim. See Channels and fidelity.

The routing answer is available without acting on it

Section titled “The routing answer is available without acting on it”

POST /v1/preflight answers the routing and fidelity question without performing the call, and refuses to pretend it answers more than that. POST /admin/v1/preflight asks the same resolver on a named key’s behalf, so reproducing a tenant’s refusal needs neither their credential nor a temporary config edit.

This lets the operator reproduce a refusal without reading gateway logs.

A stream is held exactly as far as a match could reach

Section titled “A stream is held exactly as far as a match could reach”

Streamed output cannot be inspected after it has been delivered, and buffering the whole response to inspect it turns streaming off. The commit horizon is the third option. It holds back exactly as many bytes as a still-possible match could span, computed from the recognizers enabled rather than guessed at with a fixed window.

pistra_guardrail_stream_held_bytes is the high-water mark of text held back on one stream, deliberately not folded into pistra_overhead_seconds, because it is delay you asked for rather than work the gateway did. See The commit horizon.

An uninspected body is counted, never assumed clean

Section titled “An uninspected body is counted, never assumed clean”

There are only a few ways a configured guardrail does not examine a response: the body is past response.max_body_bytes, or it is unparseable. Each is counted by reason in pistra_guardrail_uninspected_total:

The one way a response guardrail does not apply, so it is counted rather than assumed away.

A non-zero value is not necessarily a problem. Not knowing the value is a problem.

Async detectors are scheduled, and scheduling can fail, because max_in_flight is a real ceiling. When it is reached the scan is dropped, and pistra_guardrail_async_total records that:

Async scans by disposition: started, or dropped because async.max_in_flight was reached. Dropped means that request’s text was never seen by the async detectors and will not be in the cache for the next turn, so it is counted rather than assumed to have run.

The detector is also named in the result, so an audit record cannot imply an inspection that never happened. See the audit trail for the distinction.

A redact rule that fired too late is not reported as a redaction

Section titled “A redact rule that fired too late is not reported as a redaction”

Under schedule: overlap the detector runs while the request is already in flight. If a redact rule then fires, the bytes are gone and the rule cannot do what its author wrote. Policy chooses what happens instead, and pistra_guardrail_overlap_redact_total records which:

Redact rules that fired on an overlap pass, after the request was already forwarded and could no longer be rewritten, by what policy did instead: denied the response, or allowed it and recorded the finding. Non-zero means a rule is not doing what its author wrote.

The metric identifies configurations that cannot redact after forwarding. See Schedules.

The gateway measures the cost of these controls. pistra_overhead_seconds is request time minus time blocked on the provider and the client, gateway work, isolated from everything the gateway was waiting on. It is exported by the same accounting that produces the published benchmarks, so the number on your cluster and the number in Performance mean the same thing.

This page describes reporting behavior, not the security boundary. See the threat model for deployment assumptions and security claims.