Fix SSRF: enforce host/scheme match in HTTPIntegrationConnector.Execute#170
Fix SSRF: enforce host/scheme match in HTTPIntegrationConnector.Execute#170
Conversation
… SSRF Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses a critical SSRF vulnerability identified by CodeQL where user-controlled action paths could flow into HTTP requests without proper validation. The fix adds structural URL validation to ensure constructed URLs match the configured base URL's scheme and host.
Changes:
- Adds
validateURLHost()function to perform scheme and host validation before HTTP requests - Integrates the validation into
HTTPIntegrationConnector.Execute()immediately after URL construction - Adds comprehensive unit and integration tests to verify the protection works correctly
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| module/integration.go | Implements new validateURLHost() function and integrates it into the Execute() method to validate constructed URLs match the configured base URL before HTTP requests |
| module/integration_test.go | Adds TestValidateURLHost with table-driven test cases and TestHTTPIntegrationConnector_ExecuteHostInjectionBlocked to verify host injection is prevented |
| example/go.mod | Updates indirect dependencies (AWS SDK, DigitalOcean, miniredis, etc.) - routine dependency updates unrelated to the security fix |
| example/go.sum | Corresponding checksum updates for the dependency changes in go.mod |
| func TestValidateURLHost(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| reqURL string | ||
| baseURL string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "matching host and scheme", | ||
| reqURL: "https://api.example.com/v1/resource", | ||
| baseURL: "https://api.example.com", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "matching host with path in base", | ||
| reqURL: "https://api.example.com/v1/resource", | ||
| baseURL: "https://api.example.com/v1", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "different host", | ||
| reqURL: "https://evil.com/steal", | ||
| baseURL: "https://api.example.com", | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "different scheme", | ||
| reqURL: "http://api.example.com/resource", | ||
| baseURL: "https://api.example.com", | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "invalid request URL", | ||
| reqURL: "://bad-url", | ||
| baseURL: "https://api.example.com", | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tc := range tests { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| err := validateURLHost(tc.reqURL, tc.baseURL) | ||
| if (err != nil) != tc.wantErr { | ||
| t.Errorf("validateURLHost(%q, %q) error = %v, wantErr %v", tc.reqURL, tc.baseURL, err, tc.wantErr) | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
The validateURLHost tests should include test cases for port-related edge cases. Important scenarios to cover:
- Different explicit ports (e.g., baseURL with :8080, reqURL with :9090)
- Explicit port vs default port (e.g., "https://example.com:443" vs "https://example.com")
- Missing port in baseURL but explicit port in reqURL
The Go url.Parse Host field includes ports, so "example.com:8080" and "example.com:9090" would be correctly detected as different hosts. However, the edge case where one URL has an explicit default port (":443" for https, ":80" for http) and the other omits it could be problematic depending on url.Parse behavior. This should be tested to ensure the validation correctly handles these cases.
CodeQL flagged
module/integration.go:290— user-controlledactionpath flows intourl.JoinPathand directly intohttp.Client.Do, with only a DNS-based private-IP check as a guard. DNS-based validation is bypassable via rebinding; there was no structural guarantee the constructed URL still targeted the configured host.Changes
module/integration.go— addsvalidateURLHost(reqURL, baseURL string) error, a pure URL-structural check comparingSchemeandHostof the constructed URL against the configuredbaseURL. Called inExecute()immediately afterurl.JoinPath, before any DNS lookup or HTTP call:module/integration_test.go— addsTestValidateURLHost(table-driven: matching host/scheme allowed, mismatched host or scheme blocked, invalid URL blocked) andTestHTTPIntegrationConnector_ExecuteHostInjectionBlocked(verifies a crafted path cannot contact a different server).💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.