{"record":{"id":"b1c27f7bca70b90d","repo":"santifer/career-ops","slug":"wttj-url-must-use-https-url","errorCode":null,"errorMessage":"wttj: URL must use HTTPS: ${url}","messagePattern":"wttj: URL must use HTTPS: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/wttj.mjs","lineNumber":65,"sourceCode":"const MAX_HITS_CAP = 200;\n// An unfiltered keyword query matches a large slice of a global board — \"product\n// manager\" alone returns ~14k hits — so Algolia's own relevance ranking, not the\n// scanner's filters, decides which 200 are seen. A server-side `filters`\n// expression cuts the result set to something a single request can actually\n// exhaust (e.g. product-management + France + full_time is ~450), so the cap is\n// raised to Algolia's per-request ceiling for this index when one is configured.\nconst FILTERED_MAX_HITS_CAP = 1000;\nconst FILTERS_MAX_LEN = 1000;\n\n/** Pin a URL to an expected https host. */\nfunction assertHost(url, host, label) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`wttj: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`wttj: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== host.toLowerCase()) {\n    throw new Error(`wttj: untrusted ${label} hostname \"${parsed.hostname}\" — must be ${host}`);\n  }\n  return url;\n}\n\n/**\n * Parse the `window.env = {...}` payload served by /api/env and extract the\n * Algolia application id + client search key.\n * @param {string} text\n * @returns {{ appId: string, apiKey: string }}\n */\nexport function parseEnvPayload(text) {\n  const start = text.indexOf('{');\n  const end = text.lastIndexOf('}');\n  if (start === -1 || end <= start) throw new Error('wttj: /api/env payload has no JSON object');\n  let env;\n  try {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/wttj.mjs#L47-L83","documentation":"assertHost requires the parsed URL to use the https: protocol; any other scheme (http:, ftp:, javascript:, etc.) throws this error. Combined with the hostname check, it guarantees the provider only ever fetches pinned HTTPS endpoints.","triggerScenarios":"assertHost called with a syntactically valid URL whose protocol !== 'https:', e.g. 'http://apply.wttj.io/acme'.","commonSituations":"Configured careers_url using plain HTTP; a URL built programmatically with a default 'http' scheme; a captured link downgraded to http.","solutions":["Change the scheme to https:// and retry","Verify the endpoint actually serves HTTPS (all WTTJ public endpoints do)","Fix the source config/entry rather than weakening the assertion"],"exampleFix":"// before\nassertHost('http://www.wttj.fr/api/env', 'www.wttj.fr', 'env');\n// after\nassertHost('https://www.wttj.fr/api/env', 'www.wttj.fr', 'env');","handlingStrategy":"validation","validationCode":"function isHttps(u) { try { return new URL(u).protocol === 'https:'; } catch { return false; } }\nif (!isHttps(entry.careers_url)) throw new Error(`must be https: ${entry.careers_url}`);","typeGuard":"const isHttps = (u) => { try { return new URL(u).protocol === 'https:'; } catch { return false; } };","tryCatchPattern":"try { return await wttj.fetch(entry, ctx); } catch (e) { if (e.message.startsWith('wttj: URL must use HTTPS')) { console.warn(`Fix ${entry.name}: ${e.message}`); return []; } throw e; }","preventionTips":["Enforce https:// in portals.yml validation","Normalize http:// to https:// only after confirming the host serves TLS","Add a startup check that rejects non-HTTPS provider URLs"],"tags":["url-validation","https","ssrf-guard","wttj"],"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"}