security: harden the admin API and the MCP proxy - #72
Merged
Conversation
A targeted review of the admin plane and /mcp/{server} against the
recent LiteLLM advisories. Fixed here:
- MCP replies the proxy must buffer are capped (max_reply_bytes), the
client follows no redirects, and upstream or token-endpoint errors
reach the caller as a generic 502 with the detail in the log.
- resources/read and prompts/get results are reviewed like tools/call;
multi-line SSE data and unparsable replies fail closed; a mask covers
structuredContent; a reviewed tenant gets no Last-Event-ID resumption;
a denial replaces the whole reply with one JSON-RPC error.
- Mcp-Session-Id is bound to the key that first received it; a foreign
session answers 404; tool calls are audited before the upstream call;
a tools/call without a string params.name is a 400; an unentitled
server answers like an unknown one.
- Realtime sessions and MCP listen streams are capped per key
(max_live_streams_per_key, default 64) through a guard released with
the stream.
- In-process monthly counters are swept to the current and previous
month at the daily reset; x-gw-user is capped at 256 bytes.
- A tenant admin token can no longer lift a ban or change an abuse
suspension; admin listings cap `limit` at 10000; denial messages
carry the key fingerprint instead of the key.
- The fallback chain skips requests that pin signed thinking to the
model that produced it, and is taken on gateway 502/503 as well as
vendor 5xx/429.
docs/security.md records the trust boundaries, the failure postures and
the review ledger, fixed and accepted alike.
…n plane An adversarial review round over the same surface turned up further gaps: - MCP result review no longer depends on the reply's HTTP status and now covers a JSON-RPC error's own message, so a non-2xx or error-shaped reply cannot carry unreviewed prose past a reviewed tenant. - Result review reads prose under every key except base64 binary (`blob`, `data`), so identifiers no longer shield text and an image is never rewritten. - A tenant under `security.moderate` may not open an MCP listen stream, whose server-pushed content the proxy cannot review. - A tenant admin can no longer lift a ban by re-creating the key: a platform ban or suspension on an existing key survives a tenant re-create. - The 256-byte attribution cap applies on the realtime subprotocol path and to the request-body and batch-item user fields, not only the header. - The model fallback chain is disabled only for a request replaying reasoning output, not for any request that merely engages reasoning. - An upstream connection failure's cause is logged again while the client still gets a generic 502. - max_reply_bytes must be greater than 0; a token endpoint reporting expires_in 0 no longer defeats the token cache; one server's slow token endpoint no longer stalls another server's first token fetch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A targeted security review of the admin API and the MCP proxy, run against the attack surface of the recent LiteLLM advisories (tenant-scoped tokens reaching foreign or global state; the proxy relaying results, redirects and errors it never inspected). Everything the review found reachable under the deployment contract is fixed here; what is accepted as is, and why, is written down in the new
docs/security.md.MCP proxy
tools/list, reviewed results) are capped bymcp_servers[].max_reply_bytes(default 16 MiB); a larger reply is a 502, never held.resources/readandprompts/getare reviewed liketools/call; multi-line SSEdatais assembled before review; a reply the proxy cannot parse fails closed; a mask coversstructuredContent; a denial replaces the whole reply with one JSON-RPC error (-32001).Last-Event-IDresumption, so a result cannot replay past the review.Mcp-Session-Idis bound to the key that first received it; another key's use of it is a 404. Tool calls are audited before the upstream call. Atools/callwithout a stringparams.nameis a 400. An unentitled server answers like an unknown one.Admin API and shared surfaces
limitat 10 000.ak_id), not the key.x-gw-useris capped at 256 bytes, since it keys governance counters; in-process monthly counters are swept to the current and previous month at the daily reset.max_live_streams_per_key, default 64, 0 = unlimited); the slot is released with the stream.Docs
docs/security.md(new): trust boundaries, authentication, tenant scoping, content controls, failure postures, operating recommendations, and the review record.api.md,configuration.md,governance.md,deployment.md,multi-instance.md,observability.md,architecture.md,index.mdand the README are aligned.Test plan
tools/call, fail-closed parsing with framing preserved, masked everywhere or blocked whole, no resumption for reviewed tenants, tenant-token sanction refusal, realtime session cap.cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace(withGW_TEST_REDIS_URLon the Linux host).