{"record":{"id":"1c52a8e34844ec00","repo":"santifer/career-ops","slug":"workable-url-must-use-https-url","errorCode":null,"errorMessage":"workable: URL must use HTTPS: ${url}","messagePattern":"workable: URL must use HTTPS: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/workable.mjs","lineNumber":116,"sourceCode":"\n// Process-wide serialization: apply.workable.com fronts every tenant on the\n// same host, so this process never needs more than one in-flight request to\n// it at a time.\nlet workableQueue = Promise.resolve();\nfunction serialized(fn) {\n  const result = workableQueue.then(fn, fn);\n  workableQueue = result.then(() => undefined, () => undefined);\n  return result;\n}\n\nfunction assertWorkableUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`workable: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`workable: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_WORKABLE_HOSTS.has(parsed.hostname)) {\n    throw new Error(`workable: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_WORKABLE_HOSTS].join(', ')}`);\n  }\n  return url;\n}\n\n/**\n * Extract the account slug from a tracked_companies entry's careers_url.\n * @returns {string|null}\n */\nexport function resolveWorkableSlug(entry) {\n  const raw = entry && typeof entry.careers_url === 'string' ? entry.careers_url : '';\n  if (!raw) return null;\n  let parsed;\n  try {\n    parsed = new URL(raw);\n  } catch {\n    return null;","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/workable.mjs#L98-L134","documentation":"assertWorkableUrl validates URLs before the Workable provider fetches them. It parses the URL and rejects anything whose protocol is not 'https:'. This is part of the provider's SSRF guard: only HTTPS endpoints on allowlisted Workable hosts may be requested.","triggerScenarios":"Calling assertWorkableUrl, or configuring a tracked_companies entry whose careers_url/widget URL resolves to an http:// (or other non-https) scheme, e.g. 'http://apply.workable.com/acme/'.","commonSituations":"A portals.yml / tracker careers_url pasted from an old site that serves plain HTTP; a hand-built widgetUrlFor-style URL with 'http://' hardcoded; a redirect target captured as http.","solutions":["Change the URL scheme to https:// and retry","If the host does not serve HTTPS, verify the hostname is a real Workable endpoint (apply.workable.com / workable.com)","If the value comes from config, correct the careers_url there rather than bypassing the check"],"exampleFix":"// before\nconst url = 'http://apply.workable.com/acme/';\n// after\nconst url = 'https://apply.workable.com/acme/';","handlingStrategy":"validation","validationCode":"function isHttpsUrl(url) { try { return new URL(url).protocol === 'https:'; } catch { return false; } }\nif (!isHttpsUrl(entry.careers_url)) throw new Error(`skip: careers_url must be https: ${entry.careers_url}`);","typeGuard":"const isHttpsUrl = (u) => { try { return new URL(u).protocol === 'https:'; } catch { return false; } };","tryCatchPattern":"try { await provider.fetch(entry, ctx); } catch (e) { if (String(e.message).startsWith('workable: URL must use HTTPS')) { console.warn(`Fix config for ${entry.name}: ${e.message}`); return []; } throw e; }","preventionTips":["Always write careers_url values with https:// in portals.yml/tracker config","Lint config entries for http:// URLs at load time","Copy URLs from the browser address bar, which shows the real scheme"],"tags":["url-validation","https","ssrf-guard","provider-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"}