{"record":{"id":"738ef0893425721a","repo":"JuliusBrussee/caveman","slug":"compat-upstream-q-forward-headers-w-openaicompat","errorCode":null,"errorMessage":"compat upstream %q forward_headers: %w","messagePattern":"compat upstream %q forward_headers: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":381,"sourceCode":"//     requests and keep the OpenAI grammar on every other path (see\n//     anthropicWireZones): the Anthropic extractor keys the live/frozen\n//     boundary on the request's own cache_control breakpoints.\n//\n// Routing, header mapping, telemetry provider, and pricing keep the mount's\n// openai_compatible identity in every dialect.\nfunc NewNamedWithWireDialect(name, baseURL, wireDialect string, forwardHeaders ...string) (providers.Adapter, error) {\n\tif err := ValidateName(name); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := ValidateWireDialect(wireDialect); err != nil {\n\t\treturn nil, fmt.Errorf(\"compat upstream %q: %w\", name, err)\n\t}\n\tbaseURL = strings.TrimSpace(baseURL)\n\tif err := ValidateBaseURL(baseURL); err != nil {\n\t\treturn nil, fmt.Errorf(\"compat upstream %q base_url: %w\", name, err)\n\t}\n\tif err := ValidateForwardHeaders(forwardHeaders); err != nil {\n\t\treturn nil, fmt.Errorf(\"compat upstream %q forward_headers: %w\", name, err)\n\t}\n\tprefix := \"/compat/\" + name\n\treturn namedAdapter{\n\t\tBase: providers.Base{\n\t\t\tProvider:      \"openai_compatible\",\n\t\t\tBaseURL:       baseURL,\n\t\t\tRoutes:        []string{prefix + \"/\"},\n\t\t\tUsageProvider: wireDialectUsageProvider[wireDialect],\n\t\t},\n\t\tprefix:         prefix,\n\t\tforwardHeaders: append([]string(nil), forwardHeaders...),\n\t\twireDialect:    wireDialect,\n\t}, nil\n}\n\n// ValidateForwardHeaders permits explicit provider-specific headers without\n// letting a mount override routing, message framing, or Caveman credentials.\n// Standard provider authentication is handled by the credential mapper.","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/openaicompat/openaicompat.go#L363-L399","documentation":"NewNamedWithWireDialect validates the mount's forward_headers list with ValidateForwardHeaders and wraps failures as \"compat upstream %q forward_headers: %w\". Any header name that is not a valid HTTP header field name, starts with x-cave-/x-caveman-, or is in the denylist triggers this during mount construction.","triggerScenarios":"Passing forwardHeaders containing e.g. \"Authorization\", \"X-Caveman-User\", \"Content-Type\", \"Host\", or a syntactically invalid name (spaces, colon) to NewNamedWithWireDialect/NewNamed, or listing them in config forward_headers.","commonSituations":"Trying to forward credentials explicitly instead of using the credential mapper; copying curl-style header lists including Content-Type/User-Agent; hopping headers like Connection or Transfer-Encoding pasted from a debug session.","solutions":["Remove protected headers (auth, host, content-type, hop-by-hop, x-cave-*/x-caveman-*, x-forwarded-*) from forward_headers — authentication is handled by the credential mapper.","Forward only safe custom headers your upstream actually needs (e.g. \"x-request-id\", \"x-tenant\").","Use lowercase valid header names without spaces.","Run ValidateForwardHeaders on the list before constructing the adapter to get a precise failing name."],"exampleFix":"# before\nforward_headers = [\"Authorization\", \"X-Request-Id\"]\n# after\nforward_headers = [\"X-Request-Id\"]","handlingStrategy":"validation","validationCode":"for _, h := range forwardHeaders {\n    if err := openaicompat.ValidateForwardHeaders([]string{h}); err != nil {\n        return fmt.Errorf(\"drop or rename %q: %w\", h, err)\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := openaicompat.ValidateForwardHeaders(headers); err != nil {\n    return fmt.Errorf(\"filter forward_headers before mount creation: %w\", err)\n}","preventionTips":["Never list auth, content-type, user-agent, or hop-by-hop headers in forward_headers.","Keep forward_headers to a minimal allowlist of custom x-* app headers.","Lint config forward_headers lists in CI."],"tags":["go","proxy","config","headers","security"],"backgroundTag":"invalid-config-value","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"}