{"record":{"id":"f7228852f79eb877","repo":"decolua/9router","slug":"invalid-url-format","errorCode":null,"errorMessage":"Invalid URL format","messagePattern":"Invalid URL format","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"src/sse/handlers/fetch.js","lineNumber":78,"sourceCode":"      return errorResponse(HTTP_STATUS.UNAUTHORIZED, \"Invalid API key\");\n    }\n  }\n\n  if (!providerInput || typeof providerInput !== \"string\") {\n    log.warn(\"FETCH\", \"Missing provider/model\");\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, \"Missing required field: provider (or model)\");\n  }\n\n  if (!targetUrl || typeof targetUrl !== \"string\") {\n    log.warn(\"FETCH\", \"Missing url\");\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, \"Missing required field: url\");\n  }\n\n  // Validate URL format\n  try {\n    new URL(targetUrl);\n  } catch {\n    log.warn(\"FETCH\", \"Invalid URL\", { url: targetUrl });\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, \"Invalid URL format\");\n  }\n\n  // SSRF guard: reject internal/private/metadata targets\n  try {\n    assertPublicUrl(targetUrl);\n  } catch (err) {\n    log.warn(\"FETCH\", \"Blocked URL\", { url: targetUrl });\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, err.message);\n  }\n\n  // Combo expansion: providerInput may be a combo name → run fallback/round-robin across providers\n  const combos = await getCombos();\n  const comboModels = getComboModelsFromData(providerInput, combos);\n  if (comboModels) {\n    const comboStrategies = settings.comboStrategies || {};\n    const comboStrategy = comboStrategies[providerInput]?.fallbackStrategy || settings.comboStrategy || \"fallback\";\n    const comboStickyLimit = settings.comboStickyRoundRobinLimit;","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/sse/handlers/fetch.js#L60-L96","documentation":"After confirming a `url` string was provided, handleFetch validates it with the WHATWG URL parser (new URL(targetUrl)). A SyntaxError - meaning the string is not an absolute URL with a scheme like http:// or https:// - produces HTTP 400 'Invalid URL format'. Relative paths, bare hostnames, and malformed URLs are all rejected here.","triggerScenarios":"Sending url='example.com/page' (no scheme), url='/relative/path', url='http://' or 'https://[bad' (malformed), or any string the URL constructor cannot parse as absolute.","commonSituations":"Passing a hostname without protocol because browsers auto-prefix http://; user-typed input pasted with typos or spaces; template interpolation producing 'https://{missing}'; URLs truncated by a length-limited form field.","solutions":["Prefix the scheme if missing: 'https://' + host when the input is a bare hostname","Trim whitespace and strip wrapping quotes/angle brackets from the URL string before sending","Pre-validate client-side with new URL(value) inside try/catch to catch it before the request","URL-encode unsafe characters (spaces, unencoded non-ASCII) in query/path segments"],"exampleFix":"// before\nconst url = 'example.com/docs';\n// after\nconst raw = 'example.com/docs'.trim();\nconst url = /^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\//.test(raw) ? raw : 'https://' + raw;","handlingStrategy":"validation","validationCode":"function assertAbsoluteUrl(value) {\n  if (typeof value !== 'string') throw new TypeError('url must be a string');\n  const u = new URL(value); // throws SyntaxError on invalid input\n  if (!['http:', 'https:'].includes(u.protocol)) throw new TypeError('url must be http(s)');\n  return u.href;\n}\nconst safeUrl = assertAbsoluteUrl(input.trim());","typeGuard":"function isParseableUrl(value) {\n  if (typeof value !== 'string') return false;\n  try { const u = new URL(value); return u.protocol === 'http:' || u.protocol === 'https:'; } catch { return false; }\n}","tryCatchPattern":"try {\n  const res = await fetch(endpoint, { method: 'POST', body: JSON.stringify({ model, url }) });\n  if (res.status === 400 && (await res.text()).includes('Invalid URL format')) {\n    console.error('Not an absolute URL:', url);\n  }\n} catch (err) { /* network failure */ }","preventionTips":["Run new URL(value) in the client before sending; it fails on the same inputs the server rejects","Trim and strip whitespace/quotes from user-provided URLs","Auto-prefix https:// when the input has no scheme"],"tags":["http-400","url-parsing","input-validation","web-fetch"],"backgroundTag":"invalid-url-format","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}