{"record":{"id":"72886175aca895f7","repo":"santifer/career-ops","slug":"manfred-invalid-url-url","errorCode":null,"errorMessage":"manfred: invalid URL: ${url}","messagePattern":"manfred: invalid URL: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/manfred.mjs","lineNumber":33,"sourceCode":"//      between a usable provider and an unusable one.\n//   2. `lang` is REQUIRED: without it the API answers 400 with\n//      `\"lang must be one of the following values: EN, ES\"`.\n//\n// 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","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/manfred.mjs#L15-L51","documentation":"assertManfredUrl runs the value through new URL(); if construction throws (not an absolute parseable URL), this error fires. It is the first of three guards (parse → HTTPS → trusted host) for the Manfred (getmanfred.com) feed.","triggerScenarios":"The URL passed to assertManfredUrl is empty, relative, missing a scheme ('www.getmanfred.com/...'), or otherwise rejected by the URL constructor. assertManfredUrl is applied to FEED_URL-derived and offer URLs.","commonSituations":"A constructed Manfred URL was built from a missing/blank parameter; an api: field in portals.yml lacks https://; a templated URL had an undefined segment producing an unparseable string.","solutions":["Read the value in the message and ensure it is a full absolute URL.","Provide all parameters needed to build the Manfred feed URL (the base is https://www.getmanfred.com/api/v2/public/offers).","Trim whitespace from any config value feeding the URL."],"exampleFix":"// before\nfeedUrl = 'www.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';\nexport function isValidAbsoluteUrl(value) {\n  if (typeof value !== 'string' || !value) return false;\n  try { new URL(value); return true; } catch { return false; }\n}\n// Pre-validate any manfred api:/feed URL in the config loader before runtime.","typeGuard":"/** @param {string} url */\nfunction isParseableUrl(url) {\n  try { new URL(url); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  assertManfredUrl(candidate);\n} catch (err) {\n  console.warn(`manfred URL invalid for ${entry.name}: ${err.message}`);\n  entry.disabled = true;\n}","preventionTips":["Always build Manfred URLs from the FEED_BASE constant with explicit https://.","Validate generated URLs parse before passing them to the provider.","Lint config to ensure no bare hostname (missing scheme) is stored."],"tags":["url-validation","manfred","ssrf-guard","config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}