{"record":{"id":"ed4d30896257f477","repo":"JuliusBrussee/caveman","slug":"backslash-is-not-allowed-in-path","errorCode":null,"errorMessage":"backslash is not allowed in path","messagePattern":"backslash is not allowed in path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":489,"sourceCode":"\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\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}","sourceCodeStart":471,"sourceCodeEnd":507,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/openaicompat/openaicompat.go#L471-L507","documentation":"validatePathComponents rejects any path containing a backslash byte. Backslashes are path separators on Windows and are normalized differently by various decoders, so allowing them could change route identity across proxy hops. Applies to /compat/ paths and to configured upstream base URLs.","triggerScenarios":"Request path like /compat/openai\\v1/chat; an upstream BaseURL configured with a backslash; a client copying a Windows-style path into an API URL.","commonSituations":"Windows developers pasting file-system-like paths into API clients; config values copied from Windows machines; malicious clients probing separator-confusion.","solutions":["Replace the backslash with a forward slash in the path","Fix the upstream BaseURL config value to use forward slashes","If the backslash is meaningful data, move it to the query string or request body, properly percent-encoded"],"exampleFix":"// before\nBaseURL: \"https://upstream.example.com\\v1\"\n\n// after\nBaseURL: \"https://upstream.example.com/v1\"","handlingStrategy":"validation","validationCode":"if strings.Contains(rawURL, \"\\\\\") {\n    return errors.New(\"backslash not allowed in URL\")\n}","typeGuard":null,"tryCatchPattern":"if err := ValidateBaseURL(cfg.BaseURL); err != nil {\n    return fmt.Errorf(\"invalid upstream config: %w\", err)\n}","preventionTips":["Always use forward slashes in URLs regardless of OS","Lint config files for backslashes in URL fields"],"tags":["go","url","path-validation","security"],"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"}