{"record":{"id":"ba8d855086ee0566","repo":"langfuse/langfuse","slug":"protocol-not-allowed","errorCode":"protocol-not-allowed","errorMessage":"Only HTTP and HTTPS protocols are allowed","messagePattern":"Only HTTP and HTTPS protocols are allowed","errorType":"exception","errorClass":"OutboundUrlValidationError","httpStatus":null,"severity":"error","filePath":"packages/shared/src/server/llm/baseUrlValidation.ts","lineNumber":42,"sourceCode":"  };\n}\n\nexport async function validateLlmConnectionBaseURL(\n  urlString: string,\n  whitelist: LlmBaseUrlValidationWhitelist = llmBaseUrlWhitelistFromEnv(),\n): Promise<void> {\n  const effectiveWhitelist = env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION\n    ? {\n        hosts: [],\n        ips: [],\n        ip_ranges: [],\n      }\n    : whitelist;\n\n  const url = parseOutboundUrl(urlString);\n\n  if (![\"https:\", \"http:\"].includes(url.protocol)) {\n    throw new OutboundUrlValidationError(\n      \"protocol-not-allowed\",\n      \"Only HTTP and HTTPS protocols are allowed\",\n    );\n  }\n\n  await validateOutboundUrlHost({\n    url,\n    whitelist: effectiveWhitelist,\n    logContext: \"LLM base URL\",\n    // Existing LLM validation accepts public IP literals after CIDR checks so\n    // custom gateways are not forced through DNS at write time.\n    shouldSkipDnsCheckForLiteralIps: true,\n  });\n\n  if (env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION && url.protocol !== \"https:\") {\n    throw new OutboundUrlValidationError(\n      \"https-required\",\n      \"Only HTTPS base URLs are allowed on Langfuse Cloud\",","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/langfuse/langfuse/blob/59d92c7cf365150d10b753b5a0d1708902a2ed60/packages/shared/src/server/llm/baseUrlValidation.ts#L24-L60","documentation":"Thrown by validateLlmConnectionBaseURL when the parsed base URL of an LLM connection uses a protocol other than http: or https: (e.g. ftp:, file:, or a typo like 'httptest://'). The URL is first parsed with parseOutboundUrl, then its protocol is checked against an allowlist before DNS/host validation runs. This protects the server from being coerced into unexpected outbound schemes.","triggerScenarios":"Saving or using an LLM connection whose baseUrl is e.g. 'ftp://example.com/v1', 'file:///etc/passwd', or a malformed string like 'https//api.openai.com/v1' that parses with an unexpected protocol. Reachable via validateBaseURLForWrite (API key routers), fetchSecureLlmUrl, and llmApiKeyRouter.","commonSituations":"Typo in baseUrl (missing colon), copy-pasting a websocket (ws://) or gRPC endpoint instead of the HTTP endpoint, or leaving a placeholder like 'YOUR_BASE_URL' in config.","solutions":["Fix the baseUrl to start with http:// or https:// (e.g. https://api.openai.com/v1).","If you intended a websocket/gRPC provider, use its HTTP-compatible endpoint instead.","Strip accidental prefixes/suffixes and re-run validation before saving the connection."],"exampleFix":"// before\nbaseUrl: \"https//api.openai.com/v1\"\n// after\nbaseUrl: \"https://api.openai.com/v1\"","handlingStrategy":"validation","validationCode":"function hasAllowedProtocol(url: string): boolean {\n  try {\n    const p = new URL(url.trim()).protocol;\n    return p === \"https:\" || p === \"http:\";\n  } catch {\n    return false;\n  }\n}","typeGuard":"const isValidLlmBaseUrl = (u: string): boolean =>\n  hasAllowedProtocol(u);","tryCatchPattern":"catch (e) { if (e instanceof OutboundUrlValidationError && e.code === \"protocol-not-allowed\") { /* surface to user editing baseUrl */ } else throw e; }","preventionTips":["Validate base URLs with new URL() in the form before submitting.","Never build base URLs by concatenation without a scheme."],"tags":["llm","url-validation","ssrf","protocol"],"backgroundTag":"invalid-url-scheme","analyzedSha":"59d92c7cf365150d10b753b5a0d1708902a2ed60","analyzedAt":"2026-08-27T22:22:00.402Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}