Skip to content

[apiserver] validate request host before Do() to address gosec G704 (#4731)#4771

Open
1fanwang wants to merge 1 commit intoray-project:masterfrom
1fanwang:stewang/fix-gosec-g704-apiserver-client
Open

[apiserver] validate request host before Do() to address gosec G704 (#4731)#4771
1fanwang wants to merge 1 commit intoray-project:masterfrom
1fanwang:stewang/fix-gosec-g704-apiserver-client

Conversation

@1fanwang
Copy link
Copy Markdown

Problem

#4703 upgrades golangci-lint to a version where gosec G704 (SSRF via taint analysis) starts firing on apiserver/pkg/http/client.go:673:

apiserver/pkg/http/client.go:673:37: G704: SSRF via taint analysis (gosec)
		response, err := krc.httpClient.Do(httpRequest)

The URL passed to every method is built by concatenating krc.baseURL with request fields, which gosec treats as tainted. #4731 asks for a structural fix rather than a bare //nolint:gosec suppression.

Direction

Add a real defense at the same point - pre-parse baseURL once, then verify each request URL host matches it immediately before httpClient.Do(). Any stray rewrite of httpRequest.URL (the actual SSRF concern) becomes an explicit error instead of a silent foreign call.

I attempted to fully eliminate the suppression but gosec G704's taint analysis flags Do(httpRequest) purely on the URL's provenance (string concat from a struct field) and does not model the field-level host check as a sanitization. So a //nolint:gosec on the Do() line is still required; the change is to make that suppression honest by pointing at concrete validation rather than just claiming "internal config".

Happy to take a different shape if there's a refactor pattern (e.g., constructing requests from a *url.URL derived purely via JoinPath, or routing through a wrapper that gosec's taint can resolve) the maintainers prefer.

Evidence

Reproducer (against current master, since #4703 isn't merged yet but the new linter shows the same flag):

go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
cd kuberay
golangci-lint run --no-config -E gosec --timeout 5m ./apiserver/pkg/http/...

Before this change, output includes:

apiserver/pkg/http/client.go:673:37: G704: SSRF via taint analysis (gosec)
		response, err := krc.httpClient.Do(httpRequest)

After this change, with the project's actual lint config:

golangci-lint run --timeout 5m ./apiserver/pkg/http/...
0 issues.

Tested with golangci-lint v2.11.4 / go1.26.2 on macOS arm64.

Implementation

apiserver/pkg/http/client.go:

  • New baseURLHost field, populated by url.Parse once in NewKuberayAPIServerClient.
  • executeRequest now refuses to dispatch when httpRequest.URL is nil, when baseURLHost is empty (malformed input), or when httpRequest.URL.Host does not equal baseURLHost. The check sits immediately before httpClient.Do().
  • The kept //nolint:gosec on Do() now references the validation above.

apiserver/pkg/http/client_test.go:

  • Existing executeRequest tests now construct the client with "http://mock" so the host check passes (request URL is http://mock/test).
  • Two new tests:
    • TestExecuteRequestRejectsForeignHost - configured http://configured-host:8888, request to http://attacker.example/evil, expects validation error and transport.callCount == 0.
    • TestExecuteRequestRejectsEmptyBaseURLHost - covers the malformed-baseURL path.
  • The two TestUnmarshal* tests still pass "baseurl" because they mock ExecuteHttpRequest and don't reach the validation.

Interaction with #4703

If #4731 lands first, the //nolint:gosec // URL is constructed from internal config, not user input line that #4703 adds in the same spot can be dropped (or replaced by a rebase that picks up the version in this PR). If #4703 lands first, this PR will need a small rebase to remove that suppression and replace it with the version here. Either order works - just flagging so reviewers know.

Verification

  • go test ./apiserver/pkg/http/... - all existing tests + 2 new tests pass
  • golangci-lint run --timeout 5m ./apiserver/pkg/http/... (project config) - 0 issues
  • golangci-lint run --no-config -E gosec --timeout 5m ./apiserver/pkg/http/... - G704 gone
  • go build ./apiserver/... - clean

…ay-project#4731)

ray-project#4703 surfaces a `gosec G704: SSRF via taint analysis` warning on
`apiserver/pkg/http/client.go:673` because every per-method URL is built by
concatenating `krc.baseURL` with request fields. The current quick fix in ray-project#4703
suppresses the warning with a bare `//nolint:gosec`.

This change adds a real defense at the same point: pre-parse `baseURL` once at
construction, store the host, and verify `httpRequest.URL.Host` matches it
immediately before `httpClient.Do()`. Any stray rewrite of the request URL
becomes an explicit error instead of a silent foreign call. Tests cover both
the foreign-host and empty-baseURL-host paths.

The suppression cannot be fully removed: gosec's taint analysis flags
`Do(httpRequest)` purely on the URL provenance (string concat from a struct
field) and does not model field-level validation as sanitization. The
`//nolint:gosec` is kept on the `Do()` call only, with a comment that points
at the host check as the actual SSRF defense - which is a more honest
suppression than "URL is constructed from internal config".
@1fanwang 1fanwang force-pushed the stewang/fix-gosec-g704-apiserver-client branch from 4d8b6c5 to 75cbdd2 Compare April 27, 2026 08:59
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.

1 participant