#3853·kratos

bug: URL path parameters can break URL structure due to lack of partial escaping

Author: MoyashiWithDeviceCreated Jul 13, 2026Updated Jul 13, 2026
Labelsbug

Description

The BuildPath function constructs an HTTP request path from a path template and a request message (msg). However, values extracted from the message fields are injected directly into the path template without any escaping.

While certain path templates (such as resource name patterns like {name=publishers/*/books/*}) are designed to accept and preserve forward slashes (/), the complete lack of escaping allows characters like ?, #, or spaces to break the URL structure.

Affected Code

The raw string returned from queryParams.Get(key) is substituted directly: transport/http/path.go

go
path = pathTemplateParamRE.ReplaceAllStringFunc(pathTemplate, func(in string) string {
    matches := pathTemplateParamRE.FindStringSubmatch(in)
    key := matches[1]
    pathParams[key] = struct{}{}
    return queryParams.Get(key) // <--- Problem: Injected directly without validation/escaping
})

Technical Impact

If the fields in the request message contain specific special characters, it could lead to:

  • URL Structure Corruption: Spaces or control characters causing malformed HTTP requests.
  • Query/Fragment Injection: An attacker or unexpected data injecting a ? or # into the path segment, modifying query parameters or the URL fragment prematurely.
  • Unintended Routing: Misrouting requests at the downstream service or reverse proxy level.

Suggested Fix Approach

Using standard url.PathEscape directly is incorrect because it breaks existing resource name patterns by escaping / into %2F.

Instead, the function should escape only the unsafe characters while preserving /. One common approach is to split the value by /, escape each segment individually using url.PathEscape, and then join them back together.

For example, a helper function could be used during substitution:

go
// Helper to escape path segments while preserving slashes for resource names
func escapePathValue(v string) string {
    segments := strings.Split(v, "/")
    for i, s := range segments {
        segments[i] = url.PathEscape(s)
    }
    return strings.Join(segments, "/")
}

// Inside BuildPath:
path = pathTemplateParamRE.ReplaceAllStringFunc(pathTemplate, func(in string) string {
    matches := pathTemplateParamRE.FindStringSubmatch(in)
    key := matches[1]
    pathParams[key] = struct{}{}
    return escapePathValue(queryParams.Get(key)) // <--- Fix: Escape segments while keeping '/'
})