Skip to content

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

Description

@MoyashiWithDevice

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

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:

// 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 '/'
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions