Skip to content

fix: propagate url.Parse error in buildURL to prevent nil pointer pan…#270

Open
sicaario wants to merge 6 commits intovolcano-sh:mainfrom
sicaario:fix/buildurl-propagate-parse-error
Open

fix: propagate url.Parse error in buildURL to prevent nil pointer pan…#270
sicaario wants to merge 6 commits intovolcano-sh:mainfrom
sicaario:fix/buildurl-propagate-parse-error

Conversation

@sicaario
Copy link
Copy Markdown

What type of PR is this?

/kind bug

What this PR does / why we need it:

buildURL in the router was silently discarding the error returned by url.Parse using a blank identifier (_). This meant that when a sandbox entry point contained a malformed or empty endpoint string, url.Parse would return a nil *url.URL which was then passed directly to httputil.NewSingleHostReverseProxy. Passing a nil URL to the reverse proxy causes a panic at request time, which was only masked by gin's built-in recovery middleware returning a 500 — not proper error handling.

This PR fixes the issue by:

  • Changing buildURL to return (*url.URL, error) and propagating the parse error to the caller
  • Adding an errNoEntryPoint sentinel to distinguish between "no entry points configured" (404) and "malformed endpoint URL" (500) in forwardToSandbox
  • Returning a proper 500 Internal Server Error with a descriptive message instead of relying on panic recovery

Which issue(s) this PR fixes:
Fixes #269

Special notes for your reviewer:

The existing test TestForwardToSandbox_InvalidEndpoint was already covering this scenario but was passing for the wrong reason — it expected 500 because gin's panic recovery was catching the nil pointer dereference. The test still passes after this fix, but now tests legitimate error handling instead of panic recovery behaviour.

Does this PR introduce a user-facing change?:

Fixed a router panic caused by malformed sandbox endpoint URLs. Invalid endpoints now return a 500 Internal Server Error instead of crashing the router process.

…ic in reverse proxy

Signed-off-by: sicaario <hrmnp8@gmail.com>
Copilot AI review requested due to automatic review settings April 13, 2026 01:57
@volcano-sh-bot volcano-sh-bot added the kind/bug Something isn't working label Apr 13, 2026
@volcano-sh-bot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign yaozengzeng for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes a router panic by properly propagating url.Parse errors from buildURL so malformed sandbox endpoints are handled as normal errors rather than causing a nil-pointer panic inside the reverse proxy.

Changes:

  • Propagate url.Parse errors by changing buildURL to return (*url.URL, error).
  • Introduce errNoEntryPoint sentinel to distinguish “no entry points” (404) from “invalid endpoint config” (500).
  • Update forwardToSandbox to return 500 on invalid endpoint configuration instead of relying on gin recovery.

Comment thread pkg/router/handlers.go
Comment on lines +158 to +162
if errors.Is(err, errNoEntryPoint) {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid sandbox endpoint configuration"})
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The PR description says the 500 response will include a “descriptive message”, but the current body always returns the generic string "invalid sandbox endpoint configuration". If the intent is to be descriptive to clients, consider including the underlying error message (or a sanitized subset) in the JSON response; alternatively, update the PR description to reflect that the detailed reason is only available in logs.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request improves error handling in the router's URL construction logic. It introduces a specific errNoEntryPoint error, updates buildURL to validate parsed URLs, and refines forwardToSandbox to distinguish between missing entry points (404) and invalid configurations (500). I have no feedback to provide.

…ole struct

Signed-off-by: sicaario <hrmnp8@gmail.com>
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 13, 2026

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

Codecov Report

❌ Patch coverage is 91.30435% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.65%. Comparing base (845b798) to head (ad148fb).
⚠️ Report is 164 commits behind head on main.

Files with missing lines Patch % Lines
pkg/router/handlers.go 91.30% 2 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #270      +/-   ##
==========================================
+ Coverage   35.60%   43.65%   +8.04%     
==========================================
  Files          29       30       +1     
  Lines        2533     2623      +90     
==========================================
+ Hits          902     1145     +243     
+ Misses       1505     1355     -150     
+ Partials      126      123       -3     
Flag Coverage Δ
unittests 43.65% <91.30%> (+8.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread pkg/router/handlers.go
c.JSON(http.StatusNotFound, gin.H{
"error": err.Error(),
})
if errors.Is(err, errNoEntryPoint) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this branch is newly distinguishing missing entry points from malformed URLs, it would be good to add a small test that covers EntryPoints == nil/empty and asserts we now return 404 here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added TestForwardToSandbox_NoEntryPoints in the latest commit (02fae19) which sets EntryPoints: [] and asserts a 404 Not Found response, covering the errNoEntryPoint path.

Comment thread pkg/router/handlers.go
if errors.Is(err, errNoEntryPoint) {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid sandbox endpoint configuration"})
Copy link
Copy Markdown
Member

@acsoto acsoto Apr 13, 2026

Choose a reason for hiding this comment

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

The new behavior makes sense, but the response body here is still fairly generic. Since the PR description mentions a more descriptive error, I suggest to include a sanitized parse failure in the JSON response

Copy link
Copy Markdown
Author

@sicaario sicaario Apr 13, 2026

Choose a reason for hiding this comment

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

@acsoto Updated in 02fae19 — the 500 response now includes the sanitized parse error via fmt.Sprintf("invalid sandbox endpoint configuration: %v", err), so the caller gets context like invalid endpoint URL "://invalid-url": instead of the generic message.

Copy link
Copy Markdown
Member

@acsoto acsoto Apr 13, 2026

Choose a reason for hiding this comment

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

Thanks, the earlier comments are addressed. I think there is still one edge case left: \url.Parse may succeed for inputs like "" or "http://", but those are still not usable proxy targets. It would be better to validate u.Scheme and u.Host in buildURL so these cases also return 500 as invalid endpoint configuration, and add a small test for that.`

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@acsoto Added scheme whitelist (http/https/ws/wss) and port range (1–65535) validation in 9dfb8a4 — both cases where url.Parse succeeds but produces an unusable proxy target. Test table updated to cover all edge cases.

… points returning 404

Signed-off-by: sicaario <hrmnp8@gmail.com>
Copilot AI review requested due to automatic review settings April 13, 2026 04:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread pkg/router/handlers.go
u, err := url.Parse(endpoint)
if err != nil {
return nil, fmt.Errorf("invalid endpoint URL %q: %w", endpoint, err)
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

url.Parse can return a non-nil *url.URL without error for inputs that are still unusable for httputil.NewSingleHostReverseProxy (e.g., empty endpoint, http:// with empty host, missing scheme/host). To fully prevent runtime failures, validate the parsed URL has the required components (at least u.Scheme and u.Host), and return an error when they’re missing.

Suggested change
}
}
if u.Scheme == "" || u.Host == "" {
return nil, fmt.Errorf("invalid endpoint URL %q: missing scheme or host", endpoint)
}

Copilot uses AI. Check for mistakes.
Comment thread pkg/router/handlers_test.go Outdated
Comment on lines +432 to +434
func TestForwardToSandbox_NoEntryPoints(t *testing.T) {
setupEnv()
defer teardownEnv()
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

This test mutates process-global environment via setupEnv/teardownEnv, which can cause cross-test interference and flakiness if the suite uses parallel tests. Consider switching to t.Setenv(...) within the test to scope env changes to the test lifecycle and avoid global cleanup ordering issues.

Copilot uses AI. Check for mistakes.
Signed-off-by: sicaario <hrmnp8@gmail.com>
Copilot AI review requested due to automatic review settings April 13, 2026 21:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread pkg/router/handlers.go
Comment on lines 179 to +184
klog.Errorf("Failed to get sandbox access address %s: %v", sandbox.SandboxID, err)
c.JSON(http.StatusNotFound, gin.H{
"error": err.Error(),
})
if errors.Is(err, errNoEntryPoint) {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("invalid sandbox endpoint configuration: %v", err)})
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The 500 response here includes the detailed endpoint parsing error (and potentially the raw configured endpoint string) in the user-facing JSON. Given handleGetSandboxError masks 500s as "internal server error", consider logging the detailed error but returning a more generic 500 message (optionally with a stable error code) to avoid leaking internal configuration details.

Copilot uses AI. Check for mistakes.
Comment thread pkg/router/jwt_test.go Outdated

// Compare key components instead of whole struct — ParsePKCS1PrivateKey may
// not precompute Dp/Dq/Qinv identically to the original key.
assert.Equal(t, manager.privateKey.PublicKey.N, privateKey.PublicKey.N, "Public key N should match")
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

In this test, assert.Equal on *big.Int fields (e.g., PublicKey.N) relies on deep structural equality and can be more brittle than value comparison. For consistency with the checks below (using Cmp), consider comparing N via Cmp as well.

Suggested change
assert.Equal(t, manager.privateKey.PublicKey.N, privateKey.PublicKey.N, "Public key N should match")
assert.Equal(t, 0, manager.privateKey.PublicKey.N.Cmp(privateKey.PublicKey.N), "Public key N should match")

Copilot uses AI. Check for mistakes.
Comment thread pkg/router/handlers.go
if u.Scheme == "" {
return nil, fmt.Errorf("invalid endpoint URL %q: missing scheme", endpoint)
}
url, _ := url.Parse(endpoint)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

donot we have already guranteed this always succeed in

func buildSandboxInfo(sandbox *sandboxv1alpha1.Sandbox, podIP string, entry *sandboxEntry) *types.SandboxInfo {
	createdAt := sandbox.GetCreationTimestamp().Time
	expiresAt := createdAt.Add(DefaultSandboxTTL)
	if sandbox.Spec.ShutdownTime != nil {
		expiresAt = sandbox.Spec.ShutdownTime.Time
	}
	accesses := make([]types.SandboxEntryPoint, 0, len(entry.Ports))
	for _, port := range entry.Ports {
		accesses = append(accesses, types.SandboxEntryPoint{
			Path:     port.PathPrefix,
			Protocol: string(port.Protocol),
			Endpoint: net.JoinHostPort(podIP, strconv.Itoa(int(port.Port))),
		})

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

BuildSandboxInfo does guarantee the format. The validation still handles the case where Protocol is empty: url.Parse("host:port") succeeds but parses it as scheme="host" with an empty Host, giving a silently unusable proxy target. Also keeps buildURL safe for any future callers. Happy to move the validation upstream if you prefer though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I cannot get what you mean by but parses it as scheme="host" with an empty Host,.

From the api object validation, we have a hard limit that it must be set to http or https now

Copy link
Copy Markdown
Author

@sicaario sicaario Apr 15, 2026

Choose a reason for hiding this comment

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

@hzxuzhonghu to address this buildURL now takes protocol as a separate parameter and prepends it explicitly before parsing — so "HTTP" + "host:port" becomes "http://host:port" before url.Parse is called. This removes the misparse concern entirely.

The scheme == "" check is still kept because SandboxEntryPoint.Protocol is a plain string (not the API-validated ProtocolType), and the session manager can produce entries with an empty Protocol field — as seen in several tests. So it's a real path

Comment thread pkg/router/handlers.go
}

func buildURL(protocol, endpoint string) *url.URL {
func buildURL(protocol, endpoint string) (*url.URL, error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If iirc, the endpoint is constructed by us in buildSandboxInfor, it is podIP:port. And also the protocol can be only http and https now.

So i think this is actually not worth verifying.

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

Labels

kind/bug Something isn't working size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: router panic on malformed sandbox endpoint due to unhandled url.Parse error in buildURL

6 participants