Skip to main content

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’s content 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: true was effectively an opt-out from redaction for this content.
  • OpenAI delta.refusal — when a model declines, its explanation streams in refusal, not content, and a refusal routinely quotes back what it is refusing.
Both are now redacted, each in its own hold-back window. See what in a stream is scanned. If you replay Anthropic thinking blocks verbatim on a later turn, note that redacting their text invalidates the block’s 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-supplied mcp-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 inspected choices[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’s kc_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 warnblock 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. Tenant upstream_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 was x-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 v1v2 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’s providers, 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 omitting stream: 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.