{"record":{"id":"7aac0ae3c29a1995","repo":"JuliusBrussee/caveman","slug":"ambiguous-escaped-path-sequence-s","errorCode":null,"errorMessage":"ambiguous escaped path sequence %s","messagePattern":"ambiguous escaped path sequence (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":505,"sourceCode":"func validatePathComponents(path, rawPath string) error {\n\tif strings.Contains(path, `\\`) {\n\t\treturn fmt.Errorf(\"backslash is not allowed in path\")\n\t}\n\tsegments := strings.Split(path, \"/\")\n\tfor i, segment := range segments {\n\t\tif segment == \"\" && i > 0 && i < len(segments)-1 {\n\t\t\treturn fmt.Errorf(\"repeated path separators are not allowed\")\n\t\t}\n\t\tif segment == \".\" || segment == \"..\" {\n\t\t\treturn fmt.Errorf(\"dot segments are not allowed in path\")\n\t\t}\n\t}\n\t// URL.Path is decoded by net/url while RawPath retains a valid escaped\n\t// spelling. Reject separators, backslashes, and dot bytes in either form so\n\t// a path cannot change route identity after another decoder or proxy hop.\n\tfor _, escape := range []string{\"%2f\", \"%5c\", \"%2e\"} {\n\t\tif strings.Contains(strings.ToLower(path), escape) || strings.Contains(strings.ToLower(rawPath), escape) {\n\t\t\treturn fmt.Errorf(\"ambiguous escaped path sequence %s\", escape)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc parseBaseURL(raw, provider string) (*url.URL, error) {\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil, fmt.Errorf(\"provider %q has no configured upstream URL\", provider)\n\t}\n\tu, err := url.Parse(strings.TrimSpace(raw))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif u.Scheme != \"http\" && u.Scheme != \"https\" {\n\t\treturn nil, fmt.Errorf(\"provider %q upstream URL scheme %q is not allowed\", provider, u.Scheme)\n\t}\n\tif !u.IsAbs() || u.Host == \"\" || u.Hostname() == \"\" {\n\t\treturn nil, fmt.Errorf(\"provider %q upstream URL must be an absolute URL with a host\", provider)","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/openaicompat/openaicompat.go#L487-L523","documentation":"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.","triggerScenarios":"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.","commonSituations":"Model names or resource ids containing '/' being stuffed into the URL path instead of the body; clients double-encoding values; WAF-evasion probes.","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"],"exampleFix":"// before\nGET /compat/openai/v1/models/org%2fmodel-1\n\n// after\nGET /compat/openai/v1/models/org-model-1  (or pass id via request body)","handlingStrategy":"validation","validationCode":"for _, esc := range []string{\"%2f\", \"%5c\", \"%2e\"} {\n    if strings.Contains(strings.ToLower(u.RawPath), esc) {\n        return fmt.Errorf(\"ambiguous escape %s\", esc)\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := ValidateRequestPath(req.URL); err != nil {\n    http.Error(w, \"ambiguous path encoding\", http.StatusBadRequest)\n    return\n}","preventionTips":["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"],"tags":["go","security","url-encoding","path-validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}