{"record":{"id":"56db369c2160365a","repo":"santifer/career-ops","slug":"builtin-url-must-use-https-url","errorCode":null,"errorMessage":"builtin: URL must use HTTPS: ${url}","messagePattern":"builtin: URL must use HTTPS: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/builtin.mjs","lineNumber":171,"sourceCode":"}\n\n/**\n * SSRF guard — every request URL passes through here before it is fetched. The\n * host comes from config, so this is the only thing standing between a\n * portals entry and an arbitrary fetch target. It checks the RESOLVED host\n * against the allowlist again rather than trusting the caller.\n *\n * @param {string} url\n * @returns {string}\n */\nfunction assertHost(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`builtin: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`builtin: URL must use HTTPS: ${url}`);\n  const host = parsed.hostname.toLowerCase();\n  if (HOSTS.get(host) !== host) {\n    throw new Error(`builtin: untrusted hostname \"${parsed.hostname}\" — must be one of ${[...new Set(HOSTS.values())].join(', ')}`);\n  }\n  return url;\n}\n\n/** @param {string} s */\nfunction stripTags(s) {\n  return decodeEntities(String(s).replace(/<[^>]*>/g, ' ')).replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Text of the first element following an icon marker inside a card.\n * Anchoring on the icon class (rather than on field order) is what keeps this\n * readable when Built In reshuffles the card layout.\n *\n * @param {string} seg  card HTML","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/builtin.mjs#L153-L189","documentation":"assertHost in the builtin provider throws this when the URL parses but its protocol is not 'https:'. The provider only fetches Built In city sites over TLS as part of its SSRF defence; any http:// or other-scheme URL is rejected before the request is made.","triggerScenarios":"A request URL reaching assertHost with scheme http:, e.g. built from a config value 'http://www.builtinseattle.com' or an internally composed URL where the https prefix was mistyped or stripped.","commonSituations":"Hand-written config using http:// by habit; a rewrite/migration that dropped the 's'; a proxy-related override pointing at an http endpoint; test fixtures that use http and then run through the real guard.","solutions":["Change the scheme to https:// in the config value or URL-composition code.","Remember resolveHost expects a bare host — supply 'www.builtinseattle.com' and let the provider add the https scheme itself.","Update any http:// test fixtures to https:// so they pass the guard."],"exampleFix":"// before\nhost: http://www.builtinchicago.org\n// after\nhost: www.builtinchicago.org","handlingStrategy":"validation","validationCode":"function isHttpsUrl(url) {\n  try { return new URL(url).protocol === 'https:'; } catch { return false; }\n}\nif (!isHttpsUrl(url)) throw new Error(`builtin requests must use https: ${url}`);","typeGuard":"function isHttpsProtocol(url) { try { return new URL(url).protocol === 'https:'; } catch { return false; } }","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).startsWith('builtin: URL must use HTTPS')) {\n    console.warn(`Upgrading ${entry.name} URL to https and retrying once`);\n    return provider.fetch(entry, ctx, /* httpsOnly */ true);\n  }\n  throw err;\n}","preventionTips":["Let the provider own the scheme: configure bare hosts, not full URLs.","Normalize http:// to https:// once at config load.","Use https:// in test fixtures so they behave like production."],"tags":["url","https","security","config"],"backgroundTag":"invalid-url-format","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}