Skip to content

[bug]: proxy forwards client x-* headers including x-goog-api-key to the upstream #410

Description

@Atharva-Kanherkar

symptom

switchyard-server 0.2.0 copies inbound non-reserved headers onto the upstream http call. a client x-goog-api-key or any other x-* secret (except the reserved authorization / x-api-key / cookie set) is sent to whatever backend the admin configured.

reproduction

capture rig (no provider keys):

python3 tools/capture_headers.py 9999 cap.jsonl canned-openai.json
switchyard-server --config switchyard-capture.toml --host 127.0.0.1 --port 9000
curl -s localhost:9000/v1/chat/completions \
  -H 'content-type: application/json' \
  -H 'x-goog-api-key: CANARY_X_GOOG_API_KEY' \
  -H 'x-custom-internal-secret: CANARY_X_CUSTOM_SECRET' \
  -H 'authorization: Bearer CANARY_AUTHORIZATION' \
  -d '{"model":"captured-model","messages":[{"role":"user","content":"ping"}]}'

the mock's recorded request includes x-goog-api-key: CANARY_X_GOOG_API_KEY and x-custom-internal-secret: CANARY_X_CUSTOM_SECRET. authorization on the upstream is the deployment value Bearer x, not the client bearer. same headers also appear when the inbound path is post /v1/messages. 5/5.

live gemini (openai-compat base url, api_key_env = GEMINI_API_KEY): the same fake x-goog-api-key still returns http 200 because google prefers the configured bearer. the mock is what shows the header still went out.

expected vs. actual

  • expected: secret-bearing client headers such as x-goog-api-key are dropped unless the admin opts into header forwarding, matching how authorization / x-api-key / cookie are already reserved.
  • actual: those headers are attached to the upstream request by default. litellm only does this when forward_client_headers_to_llm_api is true (off by default).

environment

  • switchyard version (or commit sha): 0.2.0 (commit 2bef154)
  • python version (python --version): n/a (rust switchyard-server)
  • os / arch: macos arm64
  • install path: source build tools/switchyard/target/release/switchyard-server
  • inbound format (chat completions / anthropic messages / responses): chat completions and anthropic messages
  • backend (openai / anthropic / nvidia inference hub / other): openai-compatible mock, plus live gemini 2.5 flash via https://generativelanguage.googleapis.com/v1beta/openai

additional context

this is a confidentiality leak (client secrets leave the proxy), not an auth bypass of the deployment key. no real provider keys appeared in error bodies.

wire captures and replay tests: Atharva-Kanherkar/kairo#3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions