fix(sdk/go): strip credential headers on cross-host redirect#602
Merged
AbirAbbas merged 2 commits intoMay 29, 2026
Conversation
The control-plane client attaches X-API-Key (and, when DID auth is configured, the X-Caller-DID / X-DID-Signature / X-DID-Timestamp / X-DID-Nonce headers) as custom request headers on an http.Client with no CheckRedirect. Go's net/http strips Authorization/Cookie on a cross-host redirect but not arbitrary application headers, so a redirect from the configured baseURL to another host would replay the operator's credentials to that host. Add a CheckRedirect hook that drops every credential header the client attaches when a redirect targets a host other than the originally configured one (compared against via[0], so credentials only reach the operator-configured host across a multi-hop chain). Same-host redirects keep the headers, so ordinary redirect following is unaffected. The credential list is sourced from the did_auth header constants so it cannot drift. Also applied to clients supplied via WithHTTPClient that define no redirect policy. Refs GHSA-jp8j-g39q-qxwx. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Performance
✓ No regressions detected |
…llel tests
TestDiscoveryLoggingIncludesOptionalRequestID and
TestExecutionCleanupService_StartStopBranches both call
setupExecutionCleanupTestLogger, which swaps the process-global
logger.Logger, while also calling t.Parallel(). When they ran alongside
another parallel test, a sibling reassigned the global logger mid-run, so
the discovery test's captured buffer came back empty and its assertions
("discovery request failed", `"request_id":"req-123"`, `"format":"compact"`)
failed. This broke the `coverage (control-plane)` job and cascaded into
`coverage-summary` (missing control-plane.total.txt).
The non-test build is unaffected, so it slipped past the required gates
and only showed up in the coverage job's full parallel run.
Drop t.Parallel() from both tests so they run in the serial phase, where
nothing else mutates the global logger concurrently -- matching the
existing non-parallel logger-swapping tests in execution_cleanup_test.go.
Verified by running the previously-failing command 20x clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
📊 Coverage gateThresholds from
✅ Gate passedNo surface regressed past the allowed threshold and the aggregate stayed above the floor. |
Contributor
📐 Patch coverage gateThreshold: 80% on lines this PR touches vs
✅ Patch gate passedEvery surface whose lines were touched by this PR has patch coverage at or above the threshold. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Go SDK control-plane client (
sdk/go/client) attaches the operator's credentials as custom request headers —X-API-Key, and when DID auth is configuredX-Caller-DID/X-DID-Signature/X-DID-Timestamp/X-DID-Nonce— on anhttp.Clientthat follows redirects with noCheckRedirectpolicy.Go's
net/httpstripsAuthorization,Cookie,Cookie2andWWW-Authenticatewhen following a redirect to a different host, but it does not strip arbitrary application headers. So if the configuredbaseURLreturned a cross-host redirect, the client would replayX-API-Key(and the DID headers) to the redirect target.Authorizationset viaWithBearerTokenwas already stripped by the stdlib; the gap was the custom headers.Fix
CheckRedirecthook (stripSensitiveHeadersOnRedirect) that removes every credential header the client attaches when a redirect targets a host other than the originally configured one. The comparison is againstvia[0]so credentials only ever reach the operator-configured host, even across a multi-hop redirect chain.did_authheader constants so it can't drift if a header is renamed.WithHTTPClientthat don't already define their own redirect policy.Tests
TestStripsCredentialsOnCrossHostRedirect— end-to-end: a cross-host 302 no longer leaksX-API-Key/Authorization.TestKeepsCredentialsOnSameHostRedirect— same-host redirect still carries the credential (no regression).TestSendsCredentialsWithoutRedirect— baseline, no redirect.TestStripSensitiveHeadersOnRedirectUnit— deterministic coverage of the full 6-header credential set; also asserts non-credential headers (e.g.Accept) are preserved.Full
sdk/gosuite passes;gofmtandgo vetclean on the changed files.Refs GHSA-jp8j-g39q-qxwx.
🤖 Generated with Claude Code
Also included: control-plane coverage CI fix
Second commit (
fix(handlers): stop flaky coverage CI from global-logger race). Pre-existing flake onmain, unrelated to the SDK change but folded in to keep this to a single PR.TestDiscoveryLoggingIncludesOptionalRequestIDandTestExecutionCleanupService_StartStopBranchesboth swap the process-globallogger.Logger(viasetupExecutionCleanupTestLogger) while runningt.Parallel(); a sibling test reassigned the global mid-run, emptying the captured buffer and failing thecoverage (control-plane)job (which cascaded intocoverage-summary). Fix dropst.Parallel()from both, matching the existing non-parallel logger-swapping tests inexecution_cleanup_test.go. Verified with a 20x stress run of the previously-failing command; coverage jobs went green on the dedicated branch before folding in.Separate pre-existing
-race-only data races in the heartbeat coverage tests were observed but left for a follow-up (CI does not run-race).