AI Gateway Changelog
Newest first. Entries marked ⚠ Behaviour change could alter what a working integration sees; everything else is additive.This changelog was backfilled on 2026-08-25 from the commit history, so entries
before that date are reconstructed rather than written at the time. Dates are the
date the change landed on the integration branch, not the date it reached
production.
2026-08-25
Extended-thinking and refusal text are redacted
⚠ Behaviour change, security. The streaming redactor scanned a model’scontent and nothing else. Two other fields carry model-visible text and were
going out unscanned:
- Anthropic
thinking_delta— with extended thinking enabled, the model’s reasoning streams in its own delta type, and reasoning quotes the prompt constantly. An SSN in a thinking block reached the client verbatim and produced no detection event, so it appeared in no evidence row and fired no canary alert. Because the buffered path scans the whole response body,stream: truewas effectively an opt-out from redaction for this content. - OpenAI
delta.refusal— when a model declines, its explanation streams inrefusal, notcontent, and a refusal routinely quotes back what it is refusing.
signature — re-prompt instead.
Streamed tool-call arguments are still not scanned; that is stated explicitly
on the streaming page rather than left to be discovered.
MCP sessions are scoped to the presenting token
⚠ Behaviour change. The upstream MCP session pool was keyed on the client-suppliedmcp-session-id header. Two different phantom tokens sending the
same value — including the common case of sending none at all — shared one
upstream connection. The pool key now leads with the verified token, so each
token gets its own upstream MCP session.
What you may notice: slightly more upstream connections, and one extra
initialization handshake the first time each token calls. If your upstream MCP
server keeps per-session state, that state is now per-token rather than shared.
A single token holds at most 4 concurrent sessions per server per environment;
opening a fifth closes the least recently used.
server_type: 'collection' MCP servers are refused at creation
Creating an MCP server backed by a route collection was accepted by the API and
then failed on every call, because the feature was never built. It is now refused
at create with a clear message. An existing collection row answers 501 (it
previously produced a generic 502 that pointed you at an upstream nothing had
dialled).
resources/* and prompts/* on the MCP plane are documented as refused
No code change — the gateway governs tools, and has always answered
method-not-found (-32601) for resource and prompt methods. It was not written
down anywhere. See MCP gateway.
2026-08-24
Every content block in a streamed response is redacted
⚠ Behaviour change, security. The OpenAI streaming redactor inspectedchoices[0] only. A request with n > 1 streamed its second and later
completions unredacted — the control was running and looking at the wrong
element. Gemini’s redactor had the same defect one field over
(candidates[0].parts[0]), and additionally missed a candidate’s second text
part.
All completions are now extracted and redacted, each with its own hold-back
window. If you use n > 1 with PII redaction on, expect redaction to appear in
completions where it previously did not.
X-KC-Team is honoured
⚠ Behaviour change. Sending X-KC-Team previously cleared the team on the
usage row — the opposite of what the header is for — so calls that named a cost
centre were the ones attributed to nobody. The header is now resolved against your
directory and lands on attributed_team_id. See
Budgets & FinOps.
Rate limits on all three data planes
/v1/ai, /v1/mcp and /v1/oauth/token register ahead of the proxy’s catch-all
and so had no request-rate ceiling of any kind. All three now enforce
cross-worker sliding windows — see Limits. The
ceilings are generous abuse floors; a normal integration will not meet one.
Provider credentials are actually injected
⚠ Behaviour change, and the big one. The data plane did not perform route secret injection at all: every/v1/ai upstream call went out with no provider
credential, so it 401’d against any real provider. The route’s
inject_headers_json / inject_body_json are now rendered through the same
secret-resolution chokepoint the main proxy uses, on the primary route, every
failover route, the output-schema retry, and the semantic-cache embedder.
If you had worked around this by putting a real provider key in your own client,
stop — the gateway now injects one too and the upstream will see whichever it
resolves.
Live/Test confinement is enforced
⚠ Behaviour change. A token’skc_live_ / kc_test_ segment was parsed and
discarded, so a Test token drove Live routes. The segment is now part of the route
lookup on the primary and every failover route, and a token whose environment
cannot be proven is refused. A kc_test_ token that had been reaching a Live route
will now get a 404 for that agent’s route.
Firewall policies can be created, and block is reachable
No product surface could create a row in the firewall-policy table, so with no
policy the evaluator capped every outcome at warn — block was unreachable for
every tenant while the UI presented it as configurable. Policies are now
creatable from /v1, the admin API and the gateway-creation seeder, and a block
policy blocks. See Firewall.
Tenant PII recognizers are read by the data plane
Custom recognizers and compliance-pack recognizers were written and never read. They now compile into the detector stack on the outbound, streaming and buffered paths. An attached policy that cannot be resolved, or that holds a pattern that will not compile, now refuses the request rather than forwarding unredacted.The MCP control plane exists
/v1/ai-gateway/.../mcp-servers CRUD, tool management, an admin twin and an MCP
tab. Before this, creating an MCP server required a raw database insert.
MCP upstream egress and credentials go through the chokepoints
Security. Tenantupstream_url values are now resolved-and-pinned like every
other tenant-supplied destination, and upstream auth must be a
{{secret_id:…}} reference — a cleartext credential in the auth column is
refused rather than forwarded.
Streaming hold-back leaked at the boundary
⚠ Behaviour change, security. The hold-back FSM deferred a match ending inside its tail but emitted everything before that same index, so an entity straddling the boundary had its head emitted in the clear. On a typical one-word-per-frame stream that was nearly every entity. Separately, patterns that do not match until complete (JWTs, provider API keys) had their heads emitted at the default window. Both are fixed; the minimum hold-back window is now 96 characters, raised from whatever a tenant had configured below it.Error boundary and fail-closed policy loads
A database error inside the pipeline used to reject an async middleware promise that Express 4 ignores — leaving the request open indefinitely. Every path now answers. A firewall or PII policy that cannot be loaded refuses the request (503) instead of proceeding with the control off.Guardrail quality is measured
A labelled corpus and a recall ratchet now run on every build, so a weakened detector fails CI. Published figures live in PII redaction.2026-08-12
Usage rollup is scheduled
ai_gateway_usage_daily had no writer registered anywhere, so it was empty — and
everything reading it (the spend-spike check, console usage) silently saw nothing.
It now runs hourly on a single scheduler process.
2026-08-08
H-31 — the response cache is keyed on the caller, not just the conversation
⚠ Behaviour change, security. The cache’s isolation boundary wasx-kc-conversation-id, a plain client header with no binding to the caller’s
token. Two users in one tenant sending the same body with no conversation
header both scoped to the empty string and could serve each other’s responses.
Stored response headers were replayed wholesale, Set-Cookie included.
The cache key now folds in the phantom token id — the only caller identity the
gateway verifies — and the key prefix moved v1 → v2 so nothing written under
the weaker assumption can ever be matched again. A request the gateway cannot
attribute to a token does not read from or write to the cache at all.
What you may notice: every cache entry written before 2026-08-08 was
abandoned, so hit rates started from zero that day. Two clients that were
(incorrectly) sharing a cache entry no longer do.
H-32 — capability-token scope is enforced
⚠ Behaviour change, security. A token’sproviders, models, tools,
egress_routes, max_cost_per_day_usd and max_tokens_per_day were HMAC-signed
and never checked — only valid_window and ip_cidrs were. The admin UI
rendered the unenforced fields as though they were restrictions.
They are now enforced, after model policy resolves. Three fail-open cases inside
the fix were corrected in the same change: a malformed scope denies rather
than being ignored; a present-but-empty list is malformed rather than
“unrestricted”; and a cost cap refuses a model with no price rather than
metering it as free.
If you minted tokens with scope fields set, expect them to start taking effect —
this is the change most likely to turn a working call into a 403. Check
X-Auth-Failure and the error code on any new refusal before assuming an outage.
H-33 — buffered responses get response-side redaction
⚠ Behaviour change, security. Non-streaming responses bypassed all response-side PII redaction and detokenization, so a client could opt out of redaction simply by omittingstream: true. Both branches now run the same stack.
2026-07-09
Semantic caching, conversation-scoped
Semantic (embedding-similarity) cache matching was re-enabled with a hard conversation scope, so an approximate match can only occur within one conversation. See Response caching.Impossible-travel step-up
geo_velocity_action = 'block' flags a token for step-up on an anomalous
location change rather than revoking it: the token’s next use is refused with
step_up_required until it is re-authenticated or the flag is cleared. See
Tokens & DPoP.
Earlier
The gateway was built as roughly thirty sequential changes through May–July 2026: the phantom-token format and DPoP binding, the PII detector stack and hold-back streaming redactor, the prompt firewall and canary tokens, the pricebook, budgets and FinOps exports, OIDC workload federation at/v1/oauth/token, the response
cache, and the MCP gateway. Those predate this changelog; the pages in this
section describe what they do today rather than the order they arrived in.