{"record":{"id":"dffc6dc82918195d","repo":"JuliusBrussee/caveman","slug":"repeated-path-separators-are-not-allowed","errorCode":null,"errorMessage":"repeated path separators are not allowed","messagePattern":"repeated path separators are not allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":494,"sourceCode":"\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}\n\treturn nil\n}\n\nfunc parseBaseURL(raw, provider string) (*url.URL, error) {\n\tif strings.TrimSpace(raw) == \"\" {","sourceCodeStart":476,"sourceCodeEnd":512,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/openaicompat/openaicompat.go#L476-L512","documentation":"validatePathComponents rejects empty path segments in the middle of a path (i.e. '//'), while allowing the leading and trailing empty segments of '/compat/x/' style paths. Repeated separators can bypass prefix matching or be collapsed differently by intermediate proxies, changing route identity.","triggerScenarios":"Requests like /compat/openai//v1/chat/completions; base URL config strings with double slashes in the path portion; clients concatenating base and suffix paths carelessly producing 'a//b'.","commonSituations":"String-joining a base URL ending in '/' with a path starting with '/'; proxies that normalized then re-emitted double slashes; config typos.","solutions":["Remove the empty segment(s): collapse '//' to '/' in the request path","Fix base/path string concatenation to avoid a double slash","Clean the configured BaseURL so its path has no '//'","Use url.JoinPath or the adapter's joinCompatPath instead of manual concatenation"],"exampleFix":"// before\nGET /compat/openai//v1/chat/completions\n\n// after\nGET /compat/openai/v1/chat/completions","handlingStrategy":"validation","validationCode":"u, err := url.Parse(rawURL)\nif err == nil && strings.Contains(strings.TrimPrefix(u.Path, \"/\"), \"//\") {\n    return errors.New(\"double slash in path\")\n}","typeGuard":null,"tryCatchPattern":"if err := ValidateBaseURL(base); err != nil {\n    return fmt.Errorf(\"bad upstream base url: %w\", err)\n}","preventionTips":["Join paths with url.JoinPath instead of string concatenation","Strip trailing slashes from configured base URLs before joining"],"tags":["go","url","path-validation"],"backgroundTag":"invalid-url-format","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"}