Skip to content

AuthBridge reverse proxy corrupts streamed SSE responses (drops event: lines, mishandles gzip) — breaks SSE clients #656

Description

@pdettori

Summary

When the authbridge-proxy reverse proxy re-frames a streaming (text/event-stream) response — which it does whenever a response-side StreamingResponder plugin (e.g. inference-parser) is active — it corrupts the stream in ways that break spec-compliant SSE clients such as the Anthropic SDK. A client reading the proxied stream decodes zero typed events, so an agent loop driving tools/structured-output over the proxied LLM never sees any content and stalls.

Root causes

  1. SSE event: lines are dropped. authlib/listener/internal/sseframe surfaces only the data: payload (it skips event:/id:/retry: by design), and the streaming re-framer in authlib/listener/reverseproxy/server.go re-emits only data: <payload>. Anthropic's SSE (and the Anthropic SDK's parser) type each event from the event: line; without it the parser yields nothing.

    • Observed (raw bytes): upstream event: message_start\ndata: {…} → proxied data: {…} (no event:).
  2. Content-Encoding: gzip returned over re-framed plaintext. The reverse proxy forwards the client's Accept-Encoding: gzip to the backend, receives a gzipped body, but the re-framer emits plaintext while the Content-Encoding: gzip response header is preserved. A client that requested gzip (the Anthropic SDK does; wget does not — which masks the bug) tries to gunzip plaintext and gets an empty/garbage stream.

  3. Non-Authorization inbound header mutations are dropped, and the backend Host is not rewritten (found in the same investigation). The reverse proxy only propagated an Authorization change from the inbound pipeline to the forwarded request, silently dropping any other header a plugin set (e.g. x-api-key), and used httputil.NewSingleHostReverseProxy with no Director, so the backend received the inbound Host rather than the target host — which Cloudflare-fronted backends (api.anthropic.com) reject.

Impact

Any SSE/streaming client behind the reverse proxy with a StreamingResponder plugin active — notably LLM agent loops proxying Anthropic — receives an empty event stream and cannot function. This blocked the RC1 AuthBridge egress PoC (harness→AB1→api.anthropic.com); the fixes are validated end-to-end there.

Fix

See the linked PR:

  • sseframe.Reader captures the event: field (LastEvent()); the re-framer emits event: <type> before data:.
  • The Director strips the client's Accept-Encoding so Go's transport transparently decompresses (no Content-Encoding over plaintext).
  • The reverse proxy forwards the full inbound header diff to the backend, and the Director rewrites Host to the backend target.

Includes Go unit tests for each (sseframe event capture; reverse-proxy event: preservation, Accept-Encoding strip, full-header propagation, Host rewrite).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions