{"record":{"id":"b891b3610198d5f5","repo":"JuliusBrussee/caveman","slug":"vertex-method-q-is-not-allowed-for-publisher-q","errorCode":null,"errorMessage":"vertex method %q is not allowed for publisher %q","messagePattern":"vertex method %q is not allowed for publisher %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/vertex/routing.go","lineNumber":38,"sourceCode":"\n// defaultModelPrefixes is the built-in model-id allowlist, matched as prefixes\n// so versioned ids (gemini-2.5-pro, claude-sonnet-4-5@20250929) are covered.\n// Operators override it with CAVE_VERTEX_MODEL_ALLOWLIST.\nvar defaultModelPrefixes = []string{\"gemini-\", \"claude-\"}\n\n// ResolveUpstreamURL validates the Vertex request shape (publisher + model on\n// the allowlist), enforces SSRF/host constraints against the aiplatform\n// endpoint, and resolves the upstream URL. The /vertex prefix is stripped so the\n// upstream path is the native aiplatform path\n// (/v1/projects/{p}/locations/{l}/publishers/{pub}/models/{model}:{method}). The\n// query string (e.g. ?alt=sse) is preserved unchanged — byte-safe passthrough.\nfunc (a Adapter) ResolveUpstreamURL(ctx context.Context, req *http.Request, route providers.RouteContext) (*url.URL, error) {\n\tpublisher, model, method := parsePredictPath(req.URL.Path)\n\tif publisher == \"\" || model == \"\" || method == \"\" {\n\t\treturn nil, fmt.Errorf(\"vertex request path %q does not name a publisher, model, and method\", req.URL.Path)\n\t}\n\tif !methodAllowed(publisher, method) {\n\t\treturn nil, fmt.Errorf(\"vertex method %q is not allowed for publisher %q\", method, publisher)\n\t}\n\tif !publisherAllowed(publisher) {\n\t\treturn nil, fmt.Errorf(\"vertex publisher %q is not on the allowlist\", publisher)\n\t}\n\tif !modelAllowed(model) {\n\t\treturn nil, fmt.Errorf(\"vertex model %q is not on the allowlist\", model)\n\t}\n\n\tbaseURL := a.BaseURL\n\tif route.BaseURL != \"\" {\n\t\tbaseURL = route.BaseURL\n\t}\n\tbase, err := url.Parse(baseURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"vertex base url invalid: %w\", err)\n\t}\n\tbase.Path = strings.TrimRight(base.Path, \"/\") + strings.TrimPrefix(req.URL.Path, \"/vertex\")\n\tbase.RawQuery = req.URL.RawQuery","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/proxy/providers/vertex/routing.go#L20-L56","documentation":"ResolveUpstreamURL parsed the publisher and method from the path, but methodAllowed(publisher, method) rejected the combination: each publisher (google, anthropic) only permits its own method set (e.g. anthropic models only accept anthropic-native methods, not gemini generateContent). It fires on cross-family method misuse before allowlist or SSRF checks run.","triggerScenarios":"Thrown at proxy/providers/vertex/routing.go:38 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use a method supported by the named publisher, e.g. generateContent/streamGenerateContent for google and the anthropic method for anthropic","Verify the client is not sending a Gemini-style path to a claude publisher or vice versa"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}