Skip to content

feat(headers): per-provider custom upstream and passthrough header overrides#513

Draft
weselben wants to merge 17 commits into
ENTERPILOT:mainfrom
weselben:feat/custom-upstream-headers
Draft

feat(headers): per-provider custom upstream and passthrough header overrides#513
weselben wants to merge 17 commits into
ENTERPILOT:mainfrom
weselben:feat/custom-upstream-headers

Conversation

@weselben

@weselben weselben commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

This branch adds per-provider header configuration: inject static headers on every upstream request, forward selected client headers, or both. The four new provider keys are custom_upstream_headers, passthrough_user_headers, passthrough_user_headers_skip, and passthrough_user_headers_skip_mode. The feature is wired through the shared CompatibleProvider adapter, so every OpenAI-compatible provider inherits it without per-provider changes. A hard-coded security floor always blocks credential headers, transport/hop-by-hop headers, and the internal X-GoModel-User-Path header. The default is closed: passthrough_user_headers is false, so passthrough_user_headers_skip and passthrough_user_headers_skip_mode only matter when passthrough is explicitly enabled.

Files to Review (38 changed, +3,253 / -149)

File Why
internal/providers/headers.go (start here) HeaderOverridesConfig, ApplyHeaderOverrides, IsHeaderBlocked, and all forwarding logic — the core engine.
internal/providers/config.go Env-var overlays for the four header fields (<PROVIDER>_CUSTOM_UPSTREAM_HEADERS, etc.).
internal/server/passthrough_headers.go PassthroughHeaderCapture middleware — captures and filters incoming headers into context.
internal/providers/openai/compatible_provider.go Propagates HeaderOverrides / UserPathAlias into the shared compatible client and wires ApplyHeaderOverrides into the request closure.
internal/providers/factory.go SetUserPathHeader builder option and plumbing from config to provider options.
internal/providers/openrouter/openrouter.go Env identity headers (HTTP-Referer, X-OpenRouter-Title) respect static overrides via a local RequestMutator.
docs/features/custom-headers.mdx Full feature guide with config examples, header hierarchy, hard-block list, and env-var reference.

Why

OpenAI-compatible providers often require headers beyond Authorization to route requests correctly. Client headers such as X-Title or X-Trace-Id can influence provider-side throttling, logging, or model routing, and operators frequently need to inject static identity headers so internal tooling behind GoModel is identified upstream without every client sending them.

The original request surfaced in issue #290 (comment-4707019144). An earlier combined implementation in PR #487 was split: the Kimi Code provider landed in PR #508, and the header-override engine is re-implemented here on top of the PR #486 provider rework.

Design Decisions

  • Shared engine. The header override logic lives in internal/providers/headers.go and is applied through CompatibleProvider's request closure, so DeepSeek, Groq, Ollama, vLLM, xAI, OpenRouter, Kimi Code, and any future OpenAI-compatible provider get the feature automatically.
  • Flat provider keys. The four new fields are direct children of each provider block, consistent with the rest of the provider schema. Env vars follow the existing <PROVIDER>_ prefix convention.
  • Default-closed security floor. passthrough_user_headers defaults to false; when it is false, no user headers are forwarded regardless of passthrough_user_headers_skip/passthrough_user_headers_skip_mode. An operator must explicitly enable passthrough to forward headers. When passthrough is enabled with passthrough_user_headers_skip_mode skip or "" (default) and an empty passthrough_user_headers_skip list, all non-blocked user headers are forwarded; with passthrough_user_headers_skip_mode allow and an empty passthrough_user_headers_skip list, no user headers are forwarded.
  • Hard-coded blocklist plus tagging strip-set. IsHeaderBlocked permanently removes credential headers, X-GoModel-User-Path, the configured user_path alias, and hop-by-hop/transport headers. The passthrough path additionally consults the tagging middleware's TaggingStripHeadersFromContext so headers marked do_not_pass in tagging rules are never forwarded.
  • Composition. Static headers are applied first; passthrough headers win on conflict. A slog.Warn fires at runtime when both are configured and overlap.

How

  1. RawProviderConfig gains four flat header fields; these are packed into HeaderOverridesConfig during resolution. Env-var overlays apply on top of YAML.
  2. ProviderFactory.SetUserPathHeader stores the alias that IsHeaderBlocked checks before providers.Init runs.
  3. PassthroughHeaderCapture middleware captures the incoming request, strips hard-blocked headers, and stashes the filtered copy in context.
  4. CompatibleProvider receives HeaderOverrides and UserPathAlias in its config and calls ApplyHeaderOverrides after the provider-specific SetHeaders hook.
  5. ApplyHeaderOverrides applies static headers first, then passthrough headers if enabled. shouldForward checks the blocklist and strip-set before the skip/allow list.
  6. Every OpenAI-compatible provider inherits through the shared adapter. Native SDK providers (Anthropic, Gemini, Azure, Bedrock, Vertex) are not in scope.

Notes

  • Config keys are flat on the provider block, not nested under a header_overrides key. The env-var and YAML test suites use this flat layout.
  • Default-closed migration impact. Operators upgrading from internal builds that forwarded everything can either enable passthrough and rely on the default skip mode (empty passthrough_user_headers_skip list with passthrough_user_headers_skip_mode skip or ""), or configure an explicit allow list (passthrough_user_headers_skip_mode allow with the desired headers listed).
  • Middleware runs on every request. PassthroughHeaderCapture is registered on the hot path; overhead is small but there is no feature flag.
  • No Kimi provider here. The Kimi Code provider landed separately in feat(provider): add Kimi Code provider #508.
  • OpenRouter note. OpenRouter currently sets its own attribution headers (HTTP-Referer, X-OpenRouter-Title) through a local RequestMutator. With custom_upstream_headers, an operator can override those values.
  • Kimi Code has since removed the header restriction that originally motivated this work, but the feature remains generally useful for any OpenAI-compatible provider that requires custom headers.
  • ** This is an AI-assisted rewrite of PR feat(headers, provider): custom header overrides and Moonshot Kimi provider #487 custom headers implementation**

Tests

  • Unit tests for all headers.go paths: static apply, passthrough apply, skip/allow modes, blocklist, strip-set, default-skip-forward-all, allow-empty-forward-nothing, overlap warning.
  • Config tests: YAML parsing, env-var override priority, skip mode normalization, comma-separated env values.
  • Factory tests: SetUserPathHeader wiring, HeaderOverrides plumbing.
  • Provider tests: DeepSeek, Groq, Ollama, vLLM, xAI, and Kimi Code verify headers wired via the compatible adapter; OpenRouter tests env/static/caller precedence.
  • Server middleware tests: PassthroughHeaderCapture filters blocked headers and context storage.

Run: go test ./internal/providers/... ./internal/server/... ./config/...

Follow-up

  • Native SDK providers (Anthropic, Gemini, Azure, Bedrock, Vertex) use their own request construction and are not covered. Per-provider audits would be needed to wire ApplyHeaderOverrides there.
  • openai.CompatibleProvider already propagates overrides; the other compatible providers inherit without additional code.
  • Proposed: route OpenRouter env identity headers (OPENROUTER_SITE_URL / OPENROUTER_APP_NAME) through the shared header setter instead of the local RequestMutator. Not implemented in this PR (but on request could be added in this or future PR).

Links

  • PR #487 — original combined PR (closed, split)
  • PR #486 — provider rework this PR builds on
  • PR #508 — Kimi Code provider (merged separately)
  • Issue #290 comment — original header passthrough request

Generated with creating-pull-requests Skill

Summary by CodeRabbit

  • New Features
    • Added per-provider upstream header overrides with env-based CUSTOM_UPSTREAM_HEADERS.
    • Enabled client header passthrough per provider, including configurable skip/allow behavior and improved header capture.
    • Wired user-path alias so the correct header is used for upstream requests.
  • Bug Fixes
    • Removed bailian from default passthrough provider routes.
    • Ensured OpenRouter identity headers respect explicit overrides and don’t apply to passthrough flows.
  • Documentation
    • Updated configuration reference and “Custom Headers” docs with precedence and environment variable behavior.
  • Tests
    • Expanded coverage for YAML/env parsing, precedence/overlap, validation, and header mutation across providers.

weselben added 6 commits July 8, 2026 18:06
Introduce a HeaderOverrides block on each provider that controls how
ingress headers are forwarded upstream. Providers can now declare a
blocklist for sensitive headers, explicit add/replace rules, and a list
of headers to remove before the request leaves the gateway.

This commit is schema-only: the runtime engine and middleware wiring are
added in the following commits.
Implement the runtime header override engine and wire it into the
proxy path. The engine applies provider-specific block, add, replace,
and remove rules to ingress headers before forwarding them upstream.

Provider implementations (OpenAI-compatible, OpenRouter, VLLM, and
Ollama) now use the engine, and the server passthrough middleware
exposes the resolved header set to downstream handlers.
Add unit tests for the header override engine, provider config/factory
wiring, and passthrough middleware. Includes coverage for the
OpenAI-compatible, OpenRouter, VLLM, Ollama, DeepSeek, Groq, Kimi Code,
and xAI provider paths as well as server request snapshot handling.
Add tests for provider-level header override configuration parsing in
config/providers_test.go and extend config/config_test.go to cover the
new schema fields end-to-end.
Add a feature guide for per-provider custom upstream headers, including
blocklist, add, replace, and remove examples. Update config.example.yaml,
.env.template, and CLAUDE.md so users can discover and copy the new
configuration.
The overlap warning is already fully exercised by
TestApplyHeaderOverrides_PassthroughMode_LogsOverlappingStaticHeaders via a
dedicated capturingSlogHandler. captureLogger was never called, so remove it
and the now-unused bytes import to satisfy golangci-lint's unused check.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1d6d67c3-1849-4cb1-97f8-6d864d11017a

📥 Commits

Reviewing files that changed from the base of the PR and between bce9ee6 and a7af32c.

📒 Files selected for processing (2)
  • internal/providers/config_test.go
  • internal/providers/init_test.go

📝 Walkthrough

Walkthrough

Adds per-provider header override support, filtered passthrough header capture, user-path header wiring, adapter updates, OpenRouter identity precedence, and related configuration and documentation changes.

Changes

Header Overrides Feature

Layer / File(s) Summary
Core header logic
internal/providers/headers.go, internal/providers/headers_test.go
Introduces header override composition, blocking rules, passthrough context storage, and request filtering helpers with coverage for static, passthrough, and skip/allow behavior.
Config schema and env parsing
config/providers.go, internal/providers/config.go, config/providers_test.go, internal/providers/config_test.go, config/config.example.yaml, config/config_test.go, .env.template, CLAUDE.md, docs/features/custom-headers.mdx
Adds YAML/env support for custom upstream headers and passthrough user headers, validates skip modes, updates examples, and documents the feature.
Factory, init, and app wiring
internal/providers/factory.go, internal/providers/init.go, internal/app/app.go, run/lifecycle_test.go, internal/server/http.go, internal/server/request_snapshot.go, internal/server/passthrough_headers.go, internal/server/*_test.go, internal/app/header_overrides_test.go
Threads header override settings and user-path header configuration through provider creation, initialization, startup state, and server middleware wiring.
Provider adapters and tests
internal/providers/openai/compatible_provider.go, internal/providers/deepseek/*, internal/providers/kimicode/*, internal/providers/groq/groq_test.go, internal/providers/xai/xai_test.go, internal/providers/ollama/*, internal/providers/vllm/*, internal/providers/openrouter/*
Applies header overrides through provider-specific request paths and tests propagation, blocking behavior, and OpenRouter identity precedence.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant EchoMiddleware
  participant RequestSnapshotCapture
  participant ProviderFactory
  participant Provider
  participant UpstreamAPI

  Client->>EchoMiddleware: HTTP request
  EchoMiddleware->>RequestSnapshotCapture: capture snapshot context
  RequestSnapshotCapture->>RequestSnapshotCapture: filter passthrough headers
  EchoMiddleware->>ProviderFactory: create provider with header settings
  ProviderFactory->>Provider: pass HeaderOverrides and UserPathHeader
  Client->>Provider: ChatCompletion / Passthrough
  Provider->>UpstreamAPI: forward merged headers
  UpstreamAPI-->>Client: response
Loading

Possibly related PRs

  • ENTERPILOT/GoModel#161: Extends the OpenRouter request-mutation path that this PR changes for header precedence.
  • ENTERPILOT/GoModel#330: Adds the user-path header wiring that this PR consumes in factory and server paths.
  • ENTERPILOT/GoModel#486: Overlaps with the OpenAI-compatible provider request/header mutation path updated here.

Poem

A rabbit hopped through header lanes,
With custom stamps and skip/allow brains.
The burrow kept each path in sight,
And OpenRouter chose its names just right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: per-provider custom upstream and passthrough header overrides.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The example comments nested the header fields under a header_overrides block
and used the shorthand skip_mode/skip_headers names, neither of which the
parser accepts. Flatten the keys to the real schema
(passthrough_user_headers, passthrough_user_headers_skip_mode,
passthrough_user_headers_skip, custom_upstream_headers) in all three
example providers (openai, groq, deepseek), and correct the stale
header_overrides wording in .env.template.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/providers/init.go (1)

219-272: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the passthrough flag behind successful provider creation
anyPassthroughUserHeaders is set before factory.Create, so a config with passthrough_user_headers: true still turns on header capture even if that provider fails to initialize and never becomes active. Move the check after Create succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/init.go` around lines 219 - 272, The
passthrough-user-headers flag is being recorded before provider creation
succeeds, so failed providers can still enable header capture. In
initializeProviders, move the HeaderOverrides.PassthroughUserHeaders check to
after factory.Create returns successfully, and only then update
anyPassthroughUserHeaders for the provider that actually initialized.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/config.example.yaml`:
- Around line 269-279: The provider examples use a nested header_overrides block
with skip_mode and skip_headers, but RawProviderConfig expects flat top-level
fields instead. Update the example YAML in the openai, groq, and deepseek
sections to match the actual schema by moving the header fields to the top level
and using the real field names from config/providers.go
(custom_upstream_headers, passthrough_user_headers,
passthrough_user_headers_skip, passthrough_user_headers_skip_mode). Keep the
examples aligned with TestRawProviderConfig_YAMLHeaderFields so uncommenting
them produces the intended header behavior.

In `@internal/providers/config.go`:
- Around line 590-616: The comma-splitting in parseHeaderMapEnv cannot safely
represent header values that contain commas, so update the handling in
parseHeaderMapEnv/config parsing to use a safer format or separator (for example
JSON or another unambiguous encoding) and document the accepted format clearly
near the CUSTOM_UPSTREAM_HEADERS path. Make sure the new behavior is located
through parseHeaderMapEnv and any callers in config.go so operators don’t
silently get malformed header maps when values include commas.
- Around line 575-588: The parseBoolEnv helper currently fails open by treating
any unrecognized value as true, which can silently enable PassthroughUserHeaders
on bad env input. Update parseBoolEnv to use an explicit true-set and return
false or nil for anything else, so only clearly recognized enable values turn
the flag on. Keep the fix localized to parseBoolEnv in
internal/providers/config.go and preserve its existing trimming/lowercasing
behavior.

In `@internal/providers/headers.go`:
- Around line 169-204: Update the docstring for shouldForward to match the
implemented behavior: the current “Default-closed” wording is stale and
contradicts the default-open logic in the switch for mode "skip" and "" when
skipSet is empty. Rewrite the comment above shouldForward (and the nearby mode
handling note if needed) so it clearly states that empty skip sets forward all
headers except those blocked by IsHeaderBlocked or stripSet, and keep the
description consistent with shouldForward and
TestApplyHeaderOverrides_PassthroughMode_DefaultOpenSkipList.
- Around line 105-122: Update logStaticOverridden in
internal/providers/headers.go so it only warns for passthrough headers that
would actually be forwarded upstream after SkipMode filtering. Instead of
checking overlap against the raw PassthroughHeadersFromContext source alone, run
each candidate through the same shouldForward / allow-skip logic used when
building req.Header and only include names that would survive that filter. Keep
the warning and overlapping_names output, but base both on the effective
forwarded headers rather than the unfiltered source.

In `@internal/providers/kimicode/kimicode_headers_test.go`:
- Around line 55-83: TestNew_AppliesUserPathHeader only proves a request was
sent and does not verify the UserPathHeader behavior. Update the test to capture
request headers in the httptest handler, send a passthrough/user-path header
through the ChatCompletion call, and assert that the configured alias
X-Tenant-Path is not forwarded while the request still succeeds. Use the New
constructor, ProviderOptions.UserPathHeader, and provider.ChatCompletion to
locate the behavior.

In `@internal/providers/xai/xai_test.go`:
- Around line 489-499: The X-Custom-Header assertion in xai_test.go is
conditionally skipped when wantCustom is empty, so the default-case test does
not verify the header is absent. Update the header assertions in the relevant
test case to always check gotHeaders.Get("X-Custom-Header") against the expected
value, using the existing tc.wantCustom field and the surrounding gotHeaders
checks for X-Tenant-Path and X-User-Header as the reference points. Ensure the
no-options/default scenario explicitly expects an empty X-Custom-Header value so
stray headers are caught.

In `@internal/server/http.go`:
- Around line 289-293: The middleware wiring in the HTTP server is
double-processing passthrough headers because `RequestSnapshotCapture` is
already followed by `PassthroughHeaderCapture` when
`cfg.PassthroughUserHeadersEnabled` is true. Update the setup around
`e.Use(...)` in `http.go` so `RequestSnapshotCapture` is always called with
passthrough capture disabled, since `PassthroughHeaderCapture` already performs
the needed filtering and context overwrite for both ingress-managed and `/p/*`
routes. Keep the change localized to this middleware registration logic and
preserve the existing `RequestSnapshotCapture` and `PassthroughHeaderCapture`
behavior elsewhere.

---

Outside diff comments:
In `@internal/providers/init.go`:
- Around line 219-272: The passthrough-user-headers flag is being recorded
before provider creation succeeds, so failed providers can still enable header
capture. In initializeProviders, move the HeaderOverrides.PassthroughUserHeaders
check to after factory.Create returns successfully, and only then update
anyPassthroughUserHeaders for the provider that actually initialized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f9b0ed2b-e465-467d-91a2-6b2f914eae92

📥 Commits

Reviewing files that changed from the base of the PR and between e0eb0ee and 3f6500c.

📒 Files selected for processing (38)
  • .env.template
  • CLAUDE.md
  • config/config.example.yaml
  • config/config_test.go
  • config/providers.go
  • config/providers_test.go
  • docs/features/custom-headers.mdx
  • internal/app/app.go
  • internal/providers/config.go
  • internal/providers/config_test.go
  • internal/providers/deepseek/deepseek_headers_test.go
  • internal/providers/deepseek/deepseek_test.go
  • internal/providers/factory.go
  • internal/providers/factory_test.go
  • internal/providers/groq/groq_test.go
  • internal/providers/headers.go
  • internal/providers/headers_test.go
  • internal/providers/init.go
  • internal/providers/init_test.go
  • internal/providers/kimicode/kimicode_headers_test.go
  • internal/providers/ollama/ollama.go
  • internal/providers/ollama/ollama_test.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/openai/compatible_provider_test.go
  • internal/providers/openrouter/openrouter.go
  • internal/providers/openrouter/openrouter_test.go
  • internal/providers/vllm/vllm.go
  • internal/providers/vllm/vllm_test.go
  • internal/providers/xai/xai_test.go
  • internal/server/auth_test.go
  • internal/server/http.go
  • internal/server/model_validation_test.go
  • internal/server/passthrough_headers.go
  • internal/server/passthrough_headers_test.go
  • internal/server/passthrough_semantic_enrichment_test.go
  • internal/server/request_snapshot.go
  • internal/server/request_snapshot_test.go
  • run/lifecycle_test.go

Comment thread config/config.example.yaml Outdated
Comment thread internal/providers/config.go
Comment thread internal/providers/config.go
Comment thread internal/providers/headers.go
Comment thread internal/providers/headers.go
Comment thread internal/providers/kimicode/kimicode_headers_test.go
Comment thread internal/providers/xai/xai_test.go Outdated
Comment thread internal/server/http.go Outdated
weselben added 2 commits July 8, 2026 19:04
…ling

- parseBoolEnv now requires an explicit true-set; any unrecognized value
  defaults to false so typos cannot silently enable passthrough.
- logStaticOverridden only warns for headers that survive the same
  skip/allow and strip-set filtering applied during forwarding.
- anyPassthroughUserHeaders is only set after factory.Create succeeds,
  so failed providers cannot enable the middleware.
- Corrected the stale "Default-closed" docstring on shouldForward to
  reflect the actual default-open skip-mode behavior.
…ough capture

- TestNew_AppliesUserPathHeader now captures request headers and asserts
  the configured alias is blocked and user headers are not forwarded by
  default.
- xai_test no longer conditionally skips the X-Custom-Header assertion,
  so the default case explicitly verifies absence.
- RequestSnapshotCapture in server setup is always called with
  capturePassthroughHeaders=false; PassthroughHeaderCapture already handles
  the filtering and context overwrite, avoiding double work on the hot path.
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Confidence Score: 3/5

Safe to merge after fixing the docstring contradiction in shouldForward; the runtime behavior is correct but the comment actively misdescribes it.

The core header forwarding logic is correct and well-tested. One function docstring in headers.go says the opposite of what the code does, with a second inline comment that contradicts it in the same function. In security-sensitive forwarding code, a factually wrong docstring is a real maintenance hazard.

internal/providers/headers.go — the shouldForward docstring and the dead only switch case. internal/server/request_snapshot.go — the now-redundant capturePassthroughHeaders parameter and code block.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant RequestSnapshotCapture
    participant PassthroughHeaderCapture
    participant TaggingCapture
    participant CompatibleProvider
    participant ApplyHeaderOverrides
    participant Upstream

    Client->>RequestSnapshotCapture: HTTP request (with any headers)
    RequestSnapshotCapture->>PassthroughHeaderCapture: next(c)
    PassthroughHeaderCapture->>PassthroughHeaderCapture: FilterIncomingHeaders (blocklist)
    PassthroughHeaderCapture->>PassthroughHeaderCapture: WithPassthroughHeaders context
    PassthroughHeaderCapture->>TaggingCapture: next(c)
    TaggingCapture->>TaggingCapture: TaggingStripHeadersFromContext context
    TaggingCapture->>CompatibleProvider: routed request
    CompatibleProvider->>CompatibleProvider: SetHeaders (auth, provider-specific)
    CompatibleProvider->>ApplyHeaderOverrides: req, HeaderOverridesConfig, userPathAlias
    ApplyHeaderOverrides->>ApplyHeaderOverrides: applyStaticHeaders (CustomUpstreamHeaders)
    ApplyHeaderOverrides->>ApplyHeaderOverrides: applyPassthroughHeaders (from context, skip/allow)
    ApplyHeaderOverrides->>ApplyHeaderOverrides: shouldForward checks blocklist + stripSet
    ApplyHeaderOverrides-->>CompatibleProvider: headers applied
    CompatibleProvider->>Upstream: upstream request with merged headers
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant RequestSnapshotCapture
    participant PassthroughHeaderCapture
    participant TaggingCapture
    participant CompatibleProvider
    participant ApplyHeaderOverrides
    participant Upstream

    Client->>RequestSnapshotCapture: HTTP request (with any headers)
    RequestSnapshotCapture->>PassthroughHeaderCapture: next(c)
    PassthroughHeaderCapture->>PassthroughHeaderCapture: FilterIncomingHeaders (blocklist)
    PassthroughHeaderCapture->>PassthroughHeaderCapture: WithPassthroughHeaders context
    PassthroughHeaderCapture->>TaggingCapture: next(c)
    TaggingCapture->>TaggingCapture: TaggingStripHeadersFromContext context
    TaggingCapture->>CompatibleProvider: routed request
    CompatibleProvider->>CompatibleProvider: SetHeaders (auth, provider-specific)
    CompatibleProvider->>ApplyHeaderOverrides: req, HeaderOverridesConfig, userPathAlias
    ApplyHeaderOverrides->>ApplyHeaderOverrides: applyStaticHeaders (CustomUpstreamHeaders)
    ApplyHeaderOverrides->>ApplyHeaderOverrides: applyPassthroughHeaders (from context, skip/allow)
    ApplyHeaderOverrides->>ApplyHeaderOverrides: shouldForward checks blocklist + stripSet
    ApplyHeaderOverrides-->>CompatibleProvider: headers applied
    CompatibleProvider->>Upstream: upstream request with merged headers
Loading

Reviews (1): Last reviewed commit: "test(providers,server): strengthen tests..." | Re-trigger Greptile

Comment thread internal/providers/headers.go Outdated
Comment thread internal/server/request_snapshot.go Outdated
Comment thread internal/providers/headers.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
internal/providers/config.go (1)

580-590: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Fail-closed fix looks correct; consider logging unrecognized values.

The explicit true-set plus default → false resolves the earlier fail-open concern. One residual gap: unrecognized values (e.g. "tru", "enalbed") are now silently coerced to false, so a fat-fingered value that was meant to enable passthrough disappears with no operator feedback. A warn log would surface the misconfiguration without changing the safe default.

🪵 Optional: warn on unrecognized boolean env values
 	default:
+		slog.Warn("unrecognized boolean env value; defaulting to false", "value", value)
 		b := false
 		return &b
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/config.go` around lines 580 - 590, The boolean parsing in
the config helper now fails closed, but unrecognized inputs are silently coerced
to false. Update the parsing logic around the boolean env/value helper in
config.go to emit a warning when the normalized value does not match the known
true/false sets, while still returning false by default. Keep the existing
behavior in the parsing function that handles trimmed values, and use the
existing logger/config context so operators can spot typos like “tru” or
“enalbed” without changing the safe default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@internal/providers/config.go`:
- Around line 580-590: The boolean parsing in the config helper now fails
closed, but unrecognized inputs are silently coerced to false. Update the
parsing logic around the boolean env/value helper in config.go to emit a warning
when the normalized value does not match the known true/false sets, while still
returning false by default. Keep the existing behavior in the parsing function
that handles trimmed values, and use the existing logger/config context so
operators can spot typos like “tru” or “enalbed” without changing the safe
default.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 07bb5caf-ed5e-40b4-ba9f-e6253215baa2

📥 Commits

Reviewing files that changed from the base of the PR and between 3cd787e and 9f46b21.

📒 Files selected for processing (6)
  • internal/providers/config.go
  • internal/providers/headers.go
  • internal/providers/init.go
  • internal/providers/kimicode/kimicode_headers_test.go
  • internal/providers/xai/xai_test.go
  • internal/server/http.go

weselben added 2 commits July 8, 2026 19:42
…eader overrides

After resolving the earlier CodeRabbit review items, Greptile flagged two
maintainability issues in the header override implementation that would leave
misleading docs and dead parameters in the PR:

- `internal/providers/headers.go`: `shouldForward` had a stale docstring that
described the opposite forwarding behavior and a dead `case "only"` branch.
`"only"` is rejected at config validation time, so it was unreachable. The
function is now simplified to an if-chain and the docstring accurately
reflects the default-open behavior.

- `internal/server/request_snapshot.go`: `RequestSnapshotCapture` still carried
a `capturePassthroughHeaders` bool and a conditional block that captured
passthrough headers. On ingress-managed paths this value is always
overwritten by the later `PassthroughHeaderCapture` middleware; on
non-ingress-managed paths the block is never reached. Removed the parameter and
block, updated every caller and test, and dropped the test that exercised the
removed code path.

All changes are within the existing PR's changed-file set and the package test
suite (`go test ./internal/providers/... ./internal/server/... ./config/...`)
passes.
@codecov-commenter

codecov-commenter commented Jul 8, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

weselben added 5 commits July 8, 2026 21:36
…r overrides coverage

- Extend config_test.go with coverage for parseBoolEnv, parseHeaderMapEnv, and collectProviderEnvValues header overrides

- Extend headers_test.go with coverage for PassthroughHeadersFromContext, sourceHasHeader, logStaticOverridden, and applyPassthroughHeaders edge cases

- Add unsuffixed, suffixed, and ambiguous-provider env overlay tests for header override fields

- Integrate all coverage into existing test files instead of creating standalone test files
… skip mode

- Add TestInit_PropagateInvalidHeaderOverridesSkipMode to verify buildProviderConfig error is returned through Init

- Covers the error return path added by header overrides in both init.go and config.go
- Add TestServer_RegistersPassthroughHeaderCaptureWhenEnabled to ensure

  PassthroughUserHeadersEnabled causes the ingress capture middleware to run

- Covers the server.New registration path added by the header overrides feature
- Add TestNew_AppliesHeaderOverrideWiring to verify SetUserPathHeader is called

- Verify AnyPassthroughUserHeaders propagates from provider init to app state

- Uses minimal config with SQLite temp file and disabled storage-backed features
@weselben

weselben commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…r overrides

- Add TestInitializeProviders_PassthroughUserHeadersNotSetOnFailedProvider to guard the CodeRabbit finding that the passthrough flag must only be set after provider creation succeeds.
- Add TestProviderEnvValues_RawConfig_PassthroughUserHeaders and TestProviderEnvValues_Empty_HeaderFields to cover the missing line and partial branch Codecov reported in internal/providers/config.go.
@weselben

weselben commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)

internal/providers/init.go (1)> 219-272: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the passthrough flag behind successful provider creation
anyPassthroughUserHeaders is set before factory.Create, so a config with passthrough_user_headers: true still turns on header capture even if that provider fails to initialize and never becomes active. Move the check after Create succeeds.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/init.go` around lines 219 - 272, The
passthrough-user-headers flag is being recorded before provider creation
succeeds, so failed providers can still enable header capture. In
initializeProviders, move the HeaderOverrides.PassthroughUserHeaders check to
after factory.Create returns successfully, and only then update
anyPassthroughUserHeaders for the provider that actually initialized.

The passthrough flag is already set after factory.Create succeeds in the current branch, so a failed provider cannot enable header capture. I added a regression test to keep it that way and updated additional tests for config.go to have broader test coverage.

Commit: a7af32cfd0a798dadd0edb514bab59cad641d3a3

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '190,280p' internal/providers/init.go

Length of output: 3092


Confirmed — the factory.Create call happens on line before the err != nil check, which uses continue to skip failed providers, and the anyPassthroughUserHeaders flag is only set afterward, on the success path. So a failed provider indeed cannot set the flag. Thanks for the fix and the regression test.

✅ Action performed

Reviews resumed.

@weselben weselben marked this pull request as draft July 9, 2026 01:10
@weselben

weselben commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Converting to draft to fix a bug with the OpenRouter implementation. It currently overwrites headers even when 'User Headers Passthrough' is enabled.

Note: Other providers' implementations are working perfectly based on my final manual E2E tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants