mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-09 18:08:31 -10:00
* fix(mcp): scrub the injected AI/ML API key from server responses Zero injects the user's AI/ML API key into requests to mcp.aimlapi.com (#1193) and keeps it out of config, but only an HTTP error status left the server's text out. A JSON-RPC error message, the method of an unexpected message, a tool result (MCP reports a failed tool call as a result with isError, not as an error) and a legacy SSE endpoint that fails to parse all reached the caller as the server wrote them, so a server that echoed the key put it in errors, in tool output sent to the model, and in anything that prints them. A response to a request that carried the injected key now has the key replaced with [REDACTED] in its error text, its method and every string of its result before anything reads it, on streamable HTTP and on the legacy SSE transport. The SSE client scrubs both the key its stream request carried and the one on the POST, since the key is resolved per request. A user Authorization header, OAuth and project-configured servers are unchanged, since nothing was injected. The scrub uses a new redaction.RedactSecretValues: the split-aware known-value pass RedactString applies to ExtraSecretValues, without the shape patterns, which could otherwise rewrite tool output or base64 payloads that never held the key. A result without the key keeps its original bytes. * fix(mcp): scrub every key a call sent, and transport errors too Review found two gaps. The key is resolved again for each redirect hop, but the scrub used only the final hop's key, so a same-origin server answering the last hop could echo an earlier hop's key. And a legacy SSE POST that failed at the transport quoted the endpoint URL the server chose, which can hold the stream's key, with no scrub at all. A collector in the request context now records every key applyAIMLAPIRequestHeaders injects during a call. net/http gives each redirect hop the first request's context, so a later hop's key is recorded too. Both transports scrub the response and every error the call returns with those keys, the stream request's own errors included, and the SSE client adds the keys its stream request sent. A scrubbed error unwraps only to the context sentinel the original matched, so a cancellation still reads as one without the chain that held the key. Reported by @jatmn.