-
Notifications
You must be signed in to change notification settings - Fork 45
fix: propagate url.Parse error in buildURL to prevent nil pointer pan… #270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b9d9179
08363e4
02fae19
aa52d24
9dfb8a4
ad148fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,10 +17,12 @@ limitations under the License. | |||||||||||
| package router | ||||||||||||
|
|
||||||||||||
| import ( | ||||||||||||
| "errors" | ||||||||||||
| "fmt" | ||||||||||||
| "net/http" | ||||||||||||
| "net/http/httputil" | ||||||||||||
| "net/url" | ||||||||||||
| "strconv" | ||||||||||||
| "strings" | ||||||||||||
| "time" | ||||||||||||
|
|
||||||||||||
|
|
@@ -103,27 +105,54 @@ func (s *Server) handleGetSandboxError(c *gin.Context, err error) { | |||||||||||
| c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // errNoEntryPoint is returned when a sandbox has no entry points configured. | ||||||||||||
| var errNoEntryPoint = errors.New("no entry point found for sandbox") | ||||||||||||
|
|
||||||||||||
| func determineUpstreamURL(sandbox *types.SandboxInfo, path string) (*url.URL, error) { | ||||||||||||
| // prefer matched entrypoint by path | ||||||||||||
| for _, ep := range sandbox.EntryPoints { | ||||||||||||
| if strings.HasPrefix(path, ep.Path) { | ||||||||||||
| return buildURL(ep.Protocol, ep.Endpoint), nil | ||||||||||||
| return buildURL(ep.Protocol, ep.Endpoint) | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| // fallback to first entrypoint | ||||||||||||
| if len(sandbox.EntryPoints) == 0 { | ||||||||||||
| return nil, fmt.Errorf("no entry point found for sandbox") | ||||||||||||
| return nil, errNoEntryPoint | ||||||||||||
| } | ||||||||||||
| ep := sandbox.EntryPoints[0] | ||||||||||||
| return buildURL(ep.Protocol, ep.Endpoint), nil | ||||||||||||
| return buildURL(ep.Protocol, ep.Endpoint) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // validProxySchemes are the URL schemes accepted for reverse-proxy targets. | ||||||||||||
| // ws/wss are included to support WebSocket upgrades. | ||||||||||||
| var validProxySchemes = map[string]bool{ | ||||||||||||
| "http": true, "https": true, "ws": true, "wss": true, | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func buildURL(protocol, endpoint string) *url.URL { | ||||||||||||
| func buildURL(protocol, endpoint string) (*url.URL, error) { | ||||||||||||
| if protocol != "" && !strings.Contains(endpoint, "://") { | ||||||||||||
| endpoint = (strings.ToLower(protocol) + "://" + endpoint) | ||||||||||||
| endpoint = strings.ToLower(protocol) + "://" + endpoint | ||||||||||||
| } | ||||||||||||
| u, err := url.Parse(endpoint) | ||||||||||||
| if err != nil { | ||||||||||||
| return nil, fmt.Errorf("invalid endpoint URL %q: %w", endpoint, err) | ||||||||||||
| } | ||||||||||||
|
||||||||||||
| } | |
| } | |
| if u.Scheme == "" || u.Host == "" { | |
| return nil, fmt.Errorf("invalid endpoint URL %q: missing scheme or host", endpoint) | |
| } |
There was a problem hiding this comment.
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))),
})
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Copilot
AI
Apr 13, 2026
There was a problem hiding this comment.
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
AI
Apr 13, 2026
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.