{"record":{"id":"902e859b882770fb","repo":"santifer/career-ops","slug":"justjoin-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"justjoin: untrusted hostname \"${parsed.hostname}\" — must be justjoin.it","messagePattern":"justjoin: untrusted hostname \"(.+?)\" — must be justjoin\\.it","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/justjoin.mjs","lineNumber":23,"sourceCode":"// Browser URLs under https://justjoin.it/job-offers/... are accepted for\n// detection, but fetches use https://justjoin.it/api/candidate-api/offers.\n\nconst ALLOWED_HOSTS = new Set(['justjoin.it']);\nconst API_BASE = 'https://justjoin.it/api/candidate-api/offers';\nconst JOB_BASE = 'https://justjoin.it/job-offer/';\nconst PAGE_SIZE = 100;\nconst MAX_PAGES = 50;\n\nfunction assertJustJoinUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`justjoin: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`justjoin: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_HOSTS.has(parsed.hostname)) {\n    throw new Error(`justjoin: untrusted hostname \"${parsed.hostname}\" — must be justjoin.it`);\n  }\n  if (!parsed.pathname.startsWith('/job-offers') && parsed.pathname !== '/api/candidate-api/offers') {\n    throw new Error(`justjoin: URL path must be /job-offers or /api/candidate-api/offers: ${url}`);\n  }\n  return parsed;\n}\n\nfunction detectUrl(entry) {\n  const url = entry.api || entry.careers_url || '';\n  if (typeof url !== 'string' || !url.trim()) return null;\n  try {\n    const parsed = assertJustJoinUrl(url);\n    return { url: parsed.href };\n  } catch {\n    return null;\n  }\n}\n","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/justjoin.mjs#L5-L41","documentation":"Thrown by assertJustJoinUrl() when the parsed URL's hostname is not exactly 'justjoin.it' (the sole entry in ALLOWED_HOSTS). The provider refuses to fetch any other host to prevent SSRF via redirect or a tampered entry pointing at an internal/attacker server. Note www.justjoin.it or any subdomain is also rejected since the set is a strict exact match.","triggerScenarios":"An api/careers_url pointing at a different domain (www.justjoin.it, an API proxy, an internal IP); a CNAME or vanity domain that isn't in the allow-list; an attempt to use the justjoin provider for a non-justjoin board.","commonSituations":"A www-prefixed URL that the user assumed was equivalent; pointing the provider at a proxy or mirror; wrong provider selected for a board.","solutions":["Use the canonical host justjoin.it (drop any www or subdomain prefix).","If the company is not on justjoin.it, switch the entry's provider to the correct ATS.","Confirm the URL is https://justjoin.it/... with no subdomain."],"exampleFix":"# before\nacme:\n  provider: justjoin\n  api: https://www.justjoin.it/api/candidate-api/offers\n\n# after\nacme:\n  provider: justjoin\n  api: https://justjoin.it/api/candidate-api/offers","handlingStrategy":"validation","validationCode":"function isTrustedJustJoinHost(url) {\n  try { return new URL(url).hostname === 'justjoin.it'; } catch { return false; }\n}","typeGuard":"/** @param {string} url @returns {boolean} */\nfunction isJustJoinHttpsUrl(url) {\n  try {\n    const p = new URL(url);\n    return p.protocol === 'https:' && p.hostname === 'justjoin.it';\n  } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Use the exact host justjoin.it — no www or subdomain.","If a company is not on justjoin.it, do not use the justjoin provider.","Lint entries against the provider's trusted-host set."],"tags":["justjoin","ssrf-guard","hostname-allowlist","config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}