{"record":{"id":"854ab49aeb3927f1","repo":"santifer/career-ops","slug":"feishu-jobs-careers-url-must-use-https-on-jobs-by","errorCode":null,"errorMessage":"feishu-jobs: careers_url must use HTTPS on jobs.bytedance.com or a *.jobs.feishu.cn tenant","messagePattern":"feishu-jobs: careers_url must use HTTPS on jobs\\.bytedance\\.com or a \\*\\.jobs\\.feishu\\.cn tenant","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/feishu-jobs.mjs","lineNumber":133,"sourceCode":"      postedAt: Number.isFinite(p.publish_time) ? p.publish_time : undefined,\n    });\n  }\n  return { jobs, total };\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'feishu-jobs',\n\n  detect(entry) {\n    const origin = resolveFeishuOrigin(entry.careers_url);\n    return origin ? { url: origin } : null;\n  },\n\n  async fetch(entry, ctx) {\n    const origin = resolveFeishuOrigin(entry.careers_url);\n    if (!origin) {\n      throw new Error('feishu-jobs: careers_url must use HTTPS on jobs.bytedance.com or a *.jobs.feishu.cn tenant');\n    }\n    const api = `${origin}/api/v1/search/job/posts`;\n\n    const keywords = Array.isArray(entry.keywords) && entry.keywords.length\n      ? entry.keywords\n      : DEFAULT_KEYWORDS;\n    const entryLimit = Number(entry.max_pages);\n    const probeLimit = Number(ctx?.maxPages);\n    const entryMaxPages = Number.isSafeInteger(entryLimit) && entryLimit > 0\n      ? entryLimit\n      : DEFAULT_MAX_PAGES;\n    const probeMaxPages = Number.isSafeInteger(probeLimit) && probeLimit > 0\n      ? probeLimit\n      : Infinity;\n    const maxPages = Math.min(entryMaxPages, probeMaxPages);\n\n    /** @type {Map<string, import('./_types.js').Job>} */\n    const seen = new Map();","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/feishu-jobs.mjs#L115-L151","documentation":"feishu-jobs provider fetch() resolves the tenant origin from entry.careers_url via resolveFeishuOrigin(), which only accepts HTTPS URLs on jobs.bytedance.com or a *.jobs.feishu.cn subdomain. If careers_url is missing, not a string, unparseable, non-HTTPS, or on any other host, fetch throws this error because it cannot build the /api/v1/search/job/posts endpoint. This is the provider's SSRF/trust boundary, shared with detect().","triggerScenarios":"fetch(entry) where entry.careers_url is undefined, an empty or malformed string ('jobs.bytedance.com' without scheme), uses http:, or points at a host outside the allowlist (e.g. a company's own domain that merely embeds a Feishu widget, or www.bytedance.com). Also fires when an explicit provider: feishu-jobs selection bypasses detect() on an incompatible entry.","commonSituations":"Configuring a company that uses Feishu Jobs internally but hosts careers on its own domain — the real tenant URL is its *.jobs.feishu.cn subdomain which must be found and used; typos or missing https:// in portals.yml; copy-pasting a marketing URL instead of the careers-site origin.","solutions":["Set careers_url to the exact origin: https://jobs.bytedance.com for ByteDance, or the tenant's https://<subdomain>.jobs.feishu.cn for third-party tenants (e.g. https://vrfi1sk8a0.jobs.feishu.cn for MiniMax).","Ensure the value is a full URL with https:// scheme — resolveFeishuOrigin returns null for bare hostnames and silently skips them.","Verify the hostname: it must be exactly jobs.bytedance.com or end with .jobs.feishu.cn; a company's own domain that fronts a Feishu board will not pass — find the underlying feishu.cn tenant origin (check where the careers site's API calls go).","If the entry is not actually a Feishu Jobs board, remove or correct the provider: feishu-jobs pinning so detect() picks the right provider."],"exampleFix":"// before\n- name: MiniMax\n  careers_url: https://www.minimaxi.com/careers\n// after\n- name: MiniMax\n  careers_url: https://vrfi1sk8a0.jobs.feishu.cn","handlingStrategy":"validation","validationCode":"function isFeishuOrigin(value) {\n  if (typeof value !== 'string') return false;\n  try {\n    const u = new URL(value);\n    return u.protocol === 'https:' &&\n      (u.hostname === 'jobs.bytedance.com' || u.hostname.endsWith('.jobs.feishu.cn'));\n  } catch { return false; }\n}","typeGuard":"const hasValidFeishuCareersUrl = (entry) => isFeishuOrigin(entry?.careers_url);","tryCatchPattern":"try {\n  const jobs = await feishuProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('must use HTTPS on jobs.bytedance.com')) {\n    console.error(`Fix ${entry.name}.careers_url: needs https://jobs.bytedance.com or a *.jobs.feishu.cn origin`);\n    return [];\n  }\n  throw err;\n}","preventionTips":["Use the exact careers-site origin (scheme + host, no path needed)","For ByteDance use https://jobs.bytedance.com; for other tenants find their *.jobs.feishu.cn subdomain","Never point the entry at the company's own marketing/careers domain — find the underlying Feishu tenant origin","Sanity-check entries with provider.detect() before batch scans"],"tags":["configuration","ssrf","url-validation","feishu"],"backgroundTag":"invalid-url","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"}