{"record":{"id":"7cec1038636a7a6f","repo":"musistudio/claude-code-router","slug":"provider-manifest-url-must-use-https","errorCode":null,"errorMessage":"Provider manifest URL must use https.","messagePattern":"Provider manifest URL must use https\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/contracts/deep-link.ts","lineNumber":481,"sourceCode":"    throw new Error(`${label} is too long.`);\n  }\n  return value;\n}\n\nfunction validateProviderBaseUrl(value: string): void {\n  const url = new URL(providerUrlWithDefaultScheme(value));\n  if (![\"http:\", \"https:\"].includes(url.protocol)) {\n    throw new Error(\"Provider Base URL must use http or https.\");\n  }\n  if (!url.hostname) {\n    throw new Error(\"Provider Base URL is invalid.\");\n  }\n}\n\nfunction validateManifestUrl(value: string): void {\n  const url = new URL(value);\n  if (url.protocol !== \"https:\") {\n    throw new Error(\"Provider manifest URL must use https.\");\n  }\n  if (url.username || url.password) {\n    throw new Error(\"Provider manifest URL cannot include credentials.\");\n  }\n  if (!url.hostname) {\n    throw new Error(\"Provider manifest URL is invalid.\");\n  }\n}\n\nfunction normalizeProviderProtocol(value: string | undefined): GatewayProviderProtocol | undefined {\n  if (!value) {\n    return undefined;\n  }\n  const protocol = value.trim();\n  if (!providerProtocols.has(protocol as GatewayProviderProtocol)) {\n    throw new Error(`Unsupported provider protocol: ${value}`);\n  }\n  return protocol as GatewayProviderProtocol;","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/contracts/deep-link.ts#L463-L499","documentation":"validateManifestUrl requires manifest URLs fetched via deep link to use https:. Because the manifest can carry credentials and is fetched automatically, plain http:// is rejected to prevent interception of the manifest content.","triggerScenarios":"A ccr:// provider link with manifest=http://example.com/provider.json — protocol is not https: so it throws before credential/host checks.","commonSituations":"Local development with a local http manifest server; providers hosting manifests on non-TLS endpoints; typos dropping the 's'.","solutions":["Serve the manifest over https (use a TLS proxy or hosting with certs); for local dev use a self-signed-cert https server or a tunnel (ngrok https)","Fix the typo http:// -> https:// in the link generator","Never point at plain-http endpoints in production links"],"exampleFix":"// before\nccr://provider/install?manifest=http%3A%2F%2Fexample.com%2Fm.json\n// after\nccr://provider/install?manifest=https%3A%2F%2Fexample.com%2Fm.json","handlingStrategy":"validation","validationCode":"if (!manifestUrl.startsWith(\"https://\")) return reject(\"manifest must be https\");","typeGuard":"const isHttpsManifestUrl = (u: string) => { try { return new URL(u).protocol === \"https:\"; } catch { return false; } };","tryCatchPattern":"try { parseProviderManifestDeepLinkPayload(url); } catch (e) { if (e instanceof Error && e.message === \"Provider manifest URL must use https.\") return upgradeToHttps(url); throw e; }","preventionTips":["Host manifests only on TLS endpoints","For local dev use an https tunnel instead of http://localhost"],"tags":["manifest","https","security","validation"],"backgroundTag":"insecure-url-rejected","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}