{"record":{"id":"58dc042d93f9116e","repo":"santifer/career-ops","slug":"themuse-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"themuse: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}","messagePattern":"themuse: untrusted hostname \"(.+?)\" — must be (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/themuse.mjs","lineNumber":24,"sourceCode":"// Response shape: { results: [...], page: n, page_count: N }\n// All pages are fetched sequentially and aggregated before normalizing.\n//\n// Wire in via a `job_boards:` entry with `provider: themuse`.\n\nconst FEED_BASE = 'https://www.themuse.com/api/public/jobs';\nconst TRUSTED_HOST = 'www.themuse.com';\n\n/** @param {string} url */\nfunction assertMuseUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`themuse: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`themuse: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== TRUSTED_HOST) {\n    throw new Error(`themuse: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}`);\n  }\n  return url;\n}\n\n/**\n * Normalize a single result from the Muse API response. Exported for unit tests.\n *\n * Field mapping:\n *   name              → title\n *   refs.landing_page → url\n *   company.name      → company\n *   locations[0].name → location\n *\n * Returns null when required fields (title or url) are missing or invalid.\n *\n * @param {any} j\n * @returns {{ title: string, url: string, company: string, location: string } | null}\n */","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/themuse.mjs#L6-L42","documentation":"assertMuseUrl's third check requires the hostname to equal TRUSTED_HOST ('www.themuse.com') exactly. It is an SSRF pin over the hardcoded FEED_BASE; like the other themuse guards it is unreachable from config and only fires if FEED_BASE/TRUSTED_HOST are edited or the function is called externally.","triggerScenarios":"FEED_BASE was changed to a host other than www.themuse.com without updating TRUSTED_HOST, or TRUSTED_HOST was changed but FEED_BASE was not (or vice versa).","commonSituations":"A fork retargets the feed but updates only one of the two constants, breaking the pairing.","solutions":["Restore both FEED_BASE and TRUSTED_HOST to their shipped values (https://www.themuse.com/api/public/jobs and www.themuse.com)","If retargeting, keep TRUSTED_HOST in sync with the FEED_BASE hostname"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Keep TRUSTED_HOST and FEED_BASE hostname in sync.\nif (new URL(FEED_BASE).hostname !== TRUSTED_HOST) {\n  throw new Error('themuse: FEED_BASE hostname does not match TRUSTED_HOST');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat TRUSTED_HOST and FEED_BASE as a pair; change both or neither.","Add a unit test asserting the FEED_BASE hostname equals TRUSTED_HOST."],"tags":["ssrf-guard","url-validation","hostname","themuse","unreachable-defensive"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}