santifer/career-ops · error · Error

teamtailor: URL must use HTTPS

Error message

teamtailor: URL must use HTTPS: ${url}

What it means

After parsing succeeds, assertFeedUrl enforces HTTPS — teamtailor feeds are only fetched over TLS as a security baseline. Any http: URL, including protocol-relative or plain-host forms that parse with http as the default scheme, triggers this error.

Solutions

  1. Change the scheme to https:// in portals.yml
  2. Rerun the scan after fixing; do not bypass — the provider intentionally refuses plaintext

Example fix

// before
careers_url: 'http://acme.teamtailor.com/jobs.rss'
// after
careers_url: 'https://acme.teamtailor.com/jobs.rss'
Defensive patterns

Strategy: validation

Validate before calling

if (new URL(entry.careers_url).protocol !== 'https:') {
  throw new Error(`${entry.name}: careers_url must use https://`);
}

Type guard

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

Try / catch

try {
  offers = await provider.fetch(entry, ctx);
} catch (e) {
  if (e.message.startsWith('teamtailor: URL must use HTTPS')) {
    console.warn(`${entry.name}: switch careers_url to https://`);
    return [];
  }
  throw e;
}

Prevention

When it happens

Trigger: careers_url written as http://acme.teamtailor.com/jobs.rss; a URL without a scheme where new URL() defaulted the protocol to http:; a redirect bookkeeping entry still on an old http link.

Common situations: Legacy config from before HTTPS was mandatory; manually typed URL missing the 's'; internal proxy config reused for this provider.

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/96703fc23c83815f. Report an issue: GitHub.

Appendix: source

Thrown at providers/teamtailor.mjs:41

// never fetched.

const TEAMTAILOR_HOST_RE = /^([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\.teamtailor\.com$/i;

/**
 * Validate a feed URL before fetching. Always HTTPS-only. The hostname is
 * pinned to `*.teamtailor.com` for auto-detected entries; an explicit
 * `provider: teamtailor` entry may use its configured branded host.
 * @param {string} url
 * @param {{ explicit?: boolean }} [opts]
 */
function assertFeedUrl(url, { explicit = false } = {}) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`teamtailor: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`teamtailor: URL must use HTTPS: ${url}`);
  if (!explicit && !TEAMTAILOR_HOST_RE.test(parsed.hostname)) {
    throw new Error(`teamtailor: untrusted hostname "${parsed.hostname}" — must be <slug>.teamtailor.com (or set "provider: teamtailor" to use a branded careers domain)`);
  }
  return url;
}

// Derive the RSS feed URL from a tracked_companies entry by normalizing any
// path on the configured host to /jobs.rss. Auto-detection (explicit=false)
// only claims *.teamtailor.com hosts; an explicit `provider: teamtailor` entry
// (explicit=true) may use a branded careers host. Returns null otherwise.
/**
 * @param {import('./_types.js').PortalEntry} entry
 * @param {{ explicit?: boolean }} [opts]
 */
function resolveFeedUrl(entry, { explicit = false } = {}) {
  const raw = entry?.api || entry?.careers_url || '';
  if (typeof raw !== 'string' || !raw) return null;
  let parsed;

View on GitHub (pinned to aac998c7ed)