{"record":{"id":"eb6e52636bab8dad","repo":"JuliusBrussee/caveman","slug":"compat-route-path-rejected-w","errorCode":null,"errorMessage":"compat route path rejected: %w","messagePattern":"compat route path rejected: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":264,"sourceCode":"// Gateways call this with the original URL (including RawPath), while the\n// adapter MatchRoute/ResolveUpstreamURL checks provide a second fail-closed\n// boundary for direct callers.\nfunc ValidateRequestPath(u *url.URL) error {\n\tif u == nil {\n\t\treturn fmt.Errorf(\"request URL is missing\")\n\t}\n\tif !strings.HasPrefix(u.Path, \"/compat/\") {\n\t\treturn nil\n\t}\n\treturn validateCompatPath(u.Path, u.RawPath)\n}\n\nfunc validateCompatPath(path, rawPath string) error {\n\tif !strings.HasPrefix(path, \"/compat/\") {\n\t\treturn nil\n\t}\n\tif err := validatePathComponents(path, rawPath); err != nil {\n\t\treturn fmt.Errorf(\"compat route path rejected: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc 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","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/providers/openaicompat/openaicompat.go#L246-L282","documentation":"Thrown by validateCompatPath (wrapped from validatePathComponents) when a /compat/ path contains an ambiguous or rejected encoding — the underlying causes include a backslash anywhere in the path, repeated path separators (empty middle segments), or encoded-segment mismatches between Path and RawPath. The gate exists because path-encoding ambiguity can bypass prefix stripping and route to unintended upstream paths.","triggerScenarios":"Requests to /compat/... whose path contains a literal '\\\\', an empty interior segment like /compat//v1, percent-encodings that disagree with the decoded form (RawPath inconsistency), or other malformed segments flagged by validatePathComponents.","commonSituations":"Clients double-encoding or manually concatenating URL parts; a proxy in front rewriting paths and leaving doubled slashes; attack probes with backslashes or %5C trying to escape the mount; hand-built request strings in tests with typos.","solutions":["Clean the client URL: single forward slashes only, no backslashes, and let net/http handle encoding (use url.Parse on the full string rather than string concatenation).","If an upstream genuinely needs an encoded segment, ensure Path and RawPath are consistent (parse the URL, do not hand-set both).","For fronting proxies, disable path-merging rewrites that can introduce '//' on the /compat/ mount."],"exampleFix":"// before (hand-built, ambiguous)\nreq, _ := http.NewRequest(\"POST\", baseURL+\"/compat//v1/chat/completions\", body)\n\n// after\nu, _ := url.Parse(baseURL)\nu = u.JoinPath(\"compat\", \"v1\", \"chat/completions\")\nreq, _ := http.NewRequest(\"POST\", u.String(), body)","handlingStrategy":"validation","validationCode":"// reject ambiguous encodings before building the request\nif strings.Contains(rawPath, `\\`) || strings.Contains(path, `\\`) ||\n    strings.Contains(path[1:], `//`) {\n    return errors.New(\"ambiguous compat path encoding\")\n}\n// prefer: let the gateway run openaicompat.ValidateRequestPath(u) on the parsed URL.","typeGuard":null,"tryCatchPattern":"Map to HTTP 400 at the gateway boundary. These paths are rejected for security reasons — never normalize-and-retry automatically; fix the producing client.","preventionTips":["Build client URLs with net/url (Parse/JoinPath) instead of string concatenation.","Keep fronting proxies from merging path segments on the /compat/ mount.","Monitor 400s with this message: a spike usually means a client regression or probing."],"tags":["openai-compat","path-validation","security","url-encoding"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}