mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
Eliminate P5-49: every Python forwarder and the Rust transparent proxy
now drop internal `x-headroom-*` request headers (`x-headroom-bypass`,
`x-headroom-mode`, `x-headroom-user-id`, `x-headroom-stack`,
`x-headroom-base-url`) before the upstream call. Stops fingerprinting
of the proxy by subscription-revocation enforcers and prevents leakage
of internal user-id / stack / base-url internals to whichever vendor
terminates the request.
Python:
- `_strip_internal_headers(headers)` in `headroom/proxy/helpers.py`
returns a NEW dict with `x-headroom-*` keys removed (case-insensitive
prefix match, no regex). Pure function. Operator opt-in
`HEADROOM_STRIP_INTERNAL_HEADERS=disabled` keeps internal headers in
the upstream-bound dict for diagnostic shadow tracing — explicit, not
a fallback.
- Strip applied at every handler entry capture in `anthropic.py`,
`openai.py`, `batch.py`, `gemini.py` (chat completions, responses,
WebSocket handshake, Copilot passthrough, batch passthroughs, Gemini
generate / stream / countTokens / cloudcode-assist, Anthropic
passthrough + batch results). Inbound reads of x-headroom (bypass
gating, memory user-id) migrated to `request.headers.get(...)` so
they continue working off the original dict.
- `log_outbound_headers` emits `event=outbound_headers forwarder=...
stripped_count=N request_id=...` per call. Never logs header values.
Rust (crates/headroom-proxy):
- `strip_internal_headers(&mut HeaderMap)` and `is_internal_header`
helpers in `src/headers.rs`. `build_forward_request_headers` accepts
a `strip_internal: bool` so the same path serves HTTP and WebSocket.
- `Config::strip_internal_headers: StripInternalHeaders` driven by CLI
flag `--strip-internal-headers` and env var
`HEADROOM_PROXY_STRIP_INTERNAL_HEADERS` (default `enabled`).
- `proxy.rs` and `websocket.rs` call `build_forward_request_headers`
with the resolved policy; structured `tracing::info!` /
`tracing::warn!` line per request describes the strip decision.
Tests: 24 Python (`tests/test_header_isolation.py`) + 4 Rust
integration (`crates/headroom-proxy/tests/integration_headers.rs`) +
4 Rust unit tests in `headers.rs`. Covers every named header
(`bypass`, `mode`, `user-id`, `stack`, `base-url`), case-insensitive
prefix matching, legitimate-headers passthrough, the `disabled`
operator-opt-in mode, and that the inbound bypass-gating read path
is unaffected by the strip.
Acceptance: targeted `pytest -x` suite green (87 tests across
test_header_isolation, test_proxy_byte_faithful_forwarding,
test_proxy_anthropic_cache_stability, test_proxy_system_prompt_immutable,
test_proxy_openai_cache_stability, test_proxy_pipeline_lifecycle).
`cargo test -p headroom-proxy` green (23 tests across all integrations
plus 7 lib unit tests). `cargo clippy -p headroom-proxy -- -D warnings`
clean. `cargo fmt --all -- --check` clean. `cargo test --workspace`
green (~900 tests total).
Per realignment build constraints: configurable (env + CLI), no
hardcodes, no regex (pure `.lower().starts_with()` match), no silent
fallbacks (`disabled` is loud operator opt-in), structured logs
(`event=outbound_headers`).
Remaining `x-headroom-` references in `headroom/proxy/handlers/` are
inbound-read sites only: `request.headers.get("x-headroom-bypass")` /
`x-headroom-mode` for behavior gating, `request.headers.get
("x-headroom-user-id")` for memory user-id resolution, and `ws_headers
.get(...)` on the WebSocket inbound path. Response-side `X-Headroom-*`
injection (e.g. `x-headroom-tokens-saved`) is unrelated to upstream
forwarding and untouched.
|
||
|---|---|---|
| .. | ||
| agno.mdx | ||
| anthropic-sdk.mdx | ||
| api-reference.mdx | ||
| architecture.mdx | ||
| benchmarks.mdx | ||
| cache-optimization.mdx | ||
| ccr.mdx | ||
| code-compression.mdx | ||
| community-savings.mdx | ||
| configuration.mdx | ||
| context-management.mdx | ||
| docker-install.mdx | ||
| errors.mdx | ||
| failure-learning.mdx | ||
| filesystem-contract.mdx | ||
| how-compression-works.mdx | ||
| image-compression.mdx | ||
| index.mdx | ||
| installation.mdx | ||
| langchain.mdx | ||
| limitations.mdx | ||
| litellm.mdx | ||
| mcp.mdx | ||
| memory.mdx | ||
| meta.json | ||
| metrics.mdx | ||
| openai-sdk.mdx | ||
| persistent-installs.mdx | ||
| proxy.mdx | ||
| quickstart.mdx | ||
| releases.mdx | ||
| shared-context.mdx | ||
| simulation.mdx | ||
| smart-crusher.mdx | ||
| strands.mdx | ||
| text-and-logs.mdx | ||
| troubleshooting.mdx | ||
| vercel-ai-sdk.mdx | ||