Skip to content

fix: route --record proxy through the configured models gateway - #3428

Merged
Sayt-0 merged 2 commits into
docker:mainfrom
dgageot:fix/record-through-models-gateway
Jul 2, 2026
Merged

Sayt-0 merged 2 commits into
docker:mainfrom
dgageot:fix/record-through-models-gateway

Conversation

@dgageot

@dgageot dgageot commented Jul 2, 2026

Copy link
Copy Markdown
Member

When docker agent run <agent> --record was used with a models gateway configured, the recording proxy bypassed the gateway entirely and forwarded requests directly to providers' public endpoints. This caused HTTP 401 errors (e.g. Anthropic "invalid x-api-key") because the gateway's managed credentials were never used, and environment API keys that happened to be set were injected instead.

The recording proxy now chains through the configured upstream gateway. GatewayTargetURL preserves the gateway's path prefix, query parameters, and percent-encoded paths when rewriting request targets. Cassette entries are stored under the canonical provider URL (via WithRecordURL) so existing --fake replay continues to work without modification.

The second commit hardens the forwarding path against credential leakage: the Docker Desktop token is never forwarded to non-Docker gateways (credential headers are stripped and provider env keys are re-applied instead); requests with an unknown or missing X-Cagent-Forward host are rejected rather than silently misrouted; the gateway URL is validated at startup; and full target URLs are kept out of logs and cassettes so gateway query parameters that may carry secrets are never recorded. New tests in pkg/fake/proxy_gateway_test.go cover forwarding, auth handling, cassette contents, and credential-leakage prevention.

dgageot added 2 commits July 2, 2026 23:06
Ensures that recorded sessions using a models gateway keep auth and routing
working by forwarding to the gateway rather than provider endpoints directly,
while saving cassette entries under canonical provider URLs for replay.

Assisted-By: claude-opus-4-5
Never forward Desktop token to non-Docker gateways; reject requests
with unknown/missing X-Cagent-Forward host; validate gateway URL at
startup; avoid logging full target URLs; preserve percent-encoded paths.

Assisted-By: Claude
@dgageot
dgageot requested a review from a team as a code owner July 2, 2026 21:20

@docker-agent docker-agent 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.

Assessment: 🟡 NEEDS ATTENTION

This PR correctly routes the --record proxy through the configured models gateway and hardens credential handling. The URL construction, cassette URL override via context (WithRecordURL), gateway URL validation at startup, and Docker-trusted gateway pass-through are all sound. One credential-handling edge case in the newly introduced gatewayAuthHeaderUpdater warrants attention.

Comment thread pkg/fake/proxy.go
// credential headers the client set and re-apply the provider env key
// it would have sent to the gateway directly (SDKs default to env keys
// when no gateway token is available).
return func(host string, req *http.Request) {

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.

[MEDIUM] gatewayAuthHeaderUpdater silently strips credentials for unrecognized provider hosts

When a non-Docker third-party gateway is configured and the X-Cagent-Forward host is not one of the 5 hard-coded providers (api.openai.com, api.anthropic.com, generativelanguage.googleapis.com, api.mistral.ai, openrouter.ai), envAPIKeyForHost returns "", so the if guard at line 195 prevents APIKeyHeaderUpdater from being called. However, the three Header.Del calls above it (lines 192–194) have already executed unconditionally, stripping Authorization, X-Api-Key, and X-Goog-Api-Key from the outgoing request.

The result is that any custom or unrecognized provider endpoint (Azure OpenAI, Cohere, Bedrock-compatible, private OpenAI-compatible deployments, etc.) routed through a non-Docker gateway will have all credential headers deleted with no replacement, producing silent 401 errors from the gateway. The comment says "re-apply the provider env key it would have sent to the gateway directly", but the re-apply is skipped for the unknown-host case with no warning or fallback.

Suggestion: For unrecognized hosts, preserve the original Authorization header instead of deleting it (the goal is stripping the Docker Desktop token specifically, which is only present for Docker-trusted URLs — the early return above already handles that case):

return func(host string, req *http.Request) {
    if envAPIKeyForHost(host) != "" {
        // Known provider: strip Desktop token and apply provider env key
        req.Header.Del("Authorization")
        req.Header.Del("X-Api-Key")
        req.Header.Del("X-Goog-Api-Key")
        APIKeyHeaderUpdater(host, req)
    }
    // Unknown host: leave whatever credentials the client sent intact
}

@aheritier aheritier added area/gateway Gateway, proxy, and routing kind/fix PR fixes a bug (maps to fix:). Use on PRs only. labels Jul 2, 2026
@Sayt-0
Sayt-0 merged commit ca41ade into docker:main Jul 2, 2026
10 of 11 checks passed
pull Bot pushed a commit to TheTechOddBug/cagent that referenced this pull request Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/gateway Gateway, proxy, and routing kind/fix PR fixes a bug (maps to fix:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants