santifer/career-ops · error

builtin: URL must use HTTPS

Error message

builtin: URL must use HTTPS: ${url}

What it means

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.

Solutions

  1. Change the scheme to https:// in the config value or URL-composition code.
  2. Remember resolveHost expects a bare host — supply 'www.builtinseattle.com' and let the provider add the https scheme itself.
  3. Update any http:// test fixtures to https:// so they pass the guard.

Example fix

// before
host: http://www.builtinchicago.org
// after
host: www.builtinchicago.org
Defensive patterns

Strategy: validation

Validate before calling

function isHttpsUrl(url) {
  try { return new URL(url).protocol === 'https:'; } catch { return false; }
}
if (!isHttpsUrl(url)) throw new Error(`builtin requests must use https: ${url}`);

Type guard

function isHttpsProtocol(url) { try { return new URL(url).protocol === 'https:'; } catch { return false; } }

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('builtin: URL must use HTTPS')) {
    console.warn(`Upgrading ${entry.name} URL to https and retrying once`);
    return provider.fetch(entry, ctx, /* httpsOnly */ true);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/56db369c2160365a. Report an issue: GitHub.

Appendix: source

Thrown at providers/builtin.mjs:171

}

/**
 * SSRF guard — every request URL passes through here before it is fetched. The
 * host comes from config, so this is the only thing standing between a
 * portals entry and an arbitrary fetch target. It checks the RESOLVED host
 * against the allowlist again rather than trusting the caller.
 *
 * @param {string} url
 * @returns {string}
 */
function assertHost(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`builtin: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`builtin: URL must use HTTPS: ${url}`);
  const host = parsed.hostname.toLowerCase();
  if (HOSTS.get(host) !== host) {
    throw new Error(`builtin: untrusted hostname "${parsed.hostname}" — must be one of ${[...new Set(HOSTS.values())].join(', ')}`);
  }
  return url;
}

/** @param {string} s */
function stripTags(s) {
  return decodeEntities(String(s).replace(/<[^>]*>/g, ' ')).replace(/\s+/g, ' ').trim();
}

/**
 * Text of the first element following an icon marker inside a card.
 * Anchoring on the icon class (rather than on field order) is what keeps this
 * readable when Built In reshuffles the card layout.
 *
 * @param {string} seg  card HTML

View on GitHub (pinned to aac998c7ed)