{"record":{"id":"4bc2e5b6af01af44","repo":"santifer/career-ops","slug":"manfred-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"manfred: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}","messagePattern":"manfred: untrusted hostname \"(.+?)\" — must be (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/manfred.mjs","lineNumber":37,"sourceCode":"// Wire in via a `job_boards:` entry with `provider: manfred`.\n\nconst FEED_BASE = 'https://www.getmanfred.com/api/v2/public/offers';\nconst TRUSTED_HOST = 'www.getmanfred.com';\nconst OFFER_BASE = 'https://www.getmanfred.com/ofertas-empleo';\nconst VALID_LANGS = ['EN', 'ES'];\nconst DEFAULT_LANG = 'EN';\n\n/** @param {string} url */\nfunction assertManfredUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`manfred: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`manfred: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== TRUSTED_HOST) {\n    throw new Error(`manfred: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}`);\n  }\n  return url;\n}\n\n/** Resolve the feed language: `lang` on the entry, uppercased, else EN. */\nexport function resolveLang(entry) {\n  const raw = typeof entry?.lang === 'string' ? entry.lang.trim().toUpperCase() : '';\n  return VALID_LANGS.includes(raw) ? raw : DEFAULT_LANG;\n}\n\n// The feed reports currency as the SYMBOL, not an ISO code, and the observed\n// values include a narrow-no-break-space variant of the euro sign. scan.mjs's\n// salary_filter compares currencies case-insensitively as plain strings, so a\n// symbol would never match a user's `currency: EUR` — map to ISO, and drop the\n// field entirely rather than guess when the symbol is unknown.\nconst CURRENCY_BY_SYMBOL = new Map([\n  ['€', 'EUR'],\n  ['£', 'GBP'],","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/manfred.mjs#L19-L55","documentation":"Third guard in assertManfredUrl: parsed.hostname must equal TRUSTED_HOST ('www.getmanfred.com'). Any other host is rejected. This is the SSRF allowlist — it prevents a config or construction error from directing the fetch at an off-brand or attacker-controlled host. The offending hostname is interpolated into the message.","triggerScenarios":"An HTTPS URL whose hostname is not 'www.getmanfred.com' — e.g. getmanfred.com (apex, missing www.), api.getmanfred.com, www.getmanfred.com.evil.com, or an unrelated host.","commonSituations":"The apex domain (getmanfred.com) was used instead of www; a subdomain typo; a copied URL pointed at a different domain; the trusted host genuinely changed and the constant needs updating.","solutions":["Use exactly https://www.getmanfred.com/... (include the www).","If the host legitimately moved, verify the new official host and update TRUSTED_HOST in providers/manfred.mjs.","Audit how the URL was constructed (apex vs www is the most common slip)."],"exampleFix":"// before\nfeedUrl = 'https://getmanfred.com/api/v2/public/offers'\n\n// after\nfeedUrl = 'https://www.getmanfred.com/api/v2/public/offers'","handlingStrategy":"validation","validationCode":"import { URL } from 'node:url';\nconst TRUSTED = new Set(['www.getmanfred.com']);\nexport function isTrustedManfredHost(value) {\n  try { return TRUSTED.has(new URL(value).hostname); } catch { return false; }\n}","typeGuard":"/** @param {string} url */\nfunction isManfredHost(url) {\n  try { return new URL(url).hostname === 'www.getmanfred.com'; } catch { return false; }\n}","tryCatchPattern":"try {\n  assertManfredUrl(url);\n} catch (err) {\n  if (err.message.includes('untrusted hostname')) console.error(`[security] ${err.message}`);\n  throw err;\n}","preventionTips":["Always include the www subdomain for getmanfred.com URLs.","Treat an untrusted-hostname error as a potential SSRF signal, not a typo to silence.","Extend TRUSTED_HOST only after verifying the new host is an official Manfred endpoint.","Lint config to catch apex-domain (getmanfred.com) slips."],"tags":["url-validation","ssrf","host-allowlist","manfred","security"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}