JuliusBrussee/caveman · error
ambiguous escaped path sequence
Error message
ambiguous escaped path sequence %s
What it means
URL.Path is decoded by net/url while RawPath preserves the escaped spelling. validatePathComponents rejects %2f (/), %5c (\), and %2e (.) in either spelling, case-insensitively, so an ambiguous path cannot flip meaning after another decoder or proxy hop processes it.
Solutions
- Move the value containing slashes/escapes out of the path (query parameter or JSON body)
- Send the value exactly once-encoded so no %2f/%5c/%2e remains in the path
- Percent-decode and re-encode the id correctly in the client; use only path-safe characters in path segments
- Check for double encoding in intermediate clients/proxies and remove it
Example fix
// before GET /compat/openai/v1/models/org%2fmodel-1 // after GET /compat/openai/v1/models/org-model-1 (or pass id via request body)
Defensive patterns
Strategy: validation
Validate before calling
for _, esc := range []string{"%2f", "%5c", "%2e"} {
if strings.Contains(strings.ToLower(u.RawPath), esc) {
return fmt.Errorf("ambiguous escape %s", esc)
}
} Try / catch
if err := ValidateRequestPath(req.URL); err != nil {
http.Error(w, "ambiguous path encoding", http.StatusBadRequest)
return
} Prevention
- Pass slash-bearing values in the query string or body, never the path
- Ensure clients encode values exactly once
- Alert on %2f/%5c/%2e appearing in access logs for compat routes
When it happens
Trigger: Path containing %2f, %5c, or %2e (in any case) in either Path or RawPath, e.g. /compat/openai/v1/models%2fcompletions; double-encoded sequences like %252f surviving one decode; data-bearing path segments where ids contain encoded slashes.
Common situations: Model names or resource ids containing '/' being stuffed into the URL path instead of the body; clients double-encoding values; WAF-evasion probes.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- backslash is not allowed in path
- compat route path rejected
- caveman-code: path escapes the workspace
- compat upstream forward_headers
- dot segments are not allowed in path
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/7aac0ae3c29a1995.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/providers/openaicompat/openaicompat.go:505
func validatePathComponents(path, rawPath string) error {
if strings.Contains(path, `\`) {
return fmt.Errorf("backslash is not allowed in path")
}
segments := strings.Split(path, "/")
for i, segment := range segments {
if segment == "" && i > 0 && i < len(segments)-1 {
return fmt.Errorf("repeated path separators are not allowed")
}
if segment == "." || segment == ".." {
return fmt.Errorf("dot segments are not allowed in path")
}
}
// URL.Path is decoded by net/url while RawPath retains a valid escaped
// spelling. Reject separators, backslashes, and dot bytes in either form so
// a path cannot change route identity after another decoder or proxy hop.
for _, escape := range []string{"%2f", "%5c", "%2e"} {
if strings.Contains(strings.ToLower(path), escape) || strings.Contains(strings.ToLower(rawPath), escape) {
return fmt.Errorf("ambiguous escaped path sequence %s", escape)
}
}
return nil
}
func parseBaseURL(raw, provider string) (*url.URL, error) {
if strings.TrimSpace(raw) == "" {
return nil, fmt.Errorf("provider %q has no configured upstream URL", provider)
}
u, err := url.Parse(strings.TrimSpace(raw))
if err != nil {
return nil, err
}
if u.Scheme != "http" && u.Scheme != "https" {
return nil, fmt.Errorf("provider %q upstream URL scheme %q is not allowed", provider, u.Scheme)
}
if !u.IsAbs() || u.Host == "" || u.Hostname() == "" {
return nil, fmt.Errorf("provider %q upstream URL must be an absolute URL with a host", provider)View on GitHub (pinned to 3ee70a1026)