{"record":{"id":"e035c37079e0b31a","repo":"santifer/career-ops","slug":"4dayweek-invalid-url-url","errorCode":null,"errorMessage":"4dayweek: invalid URL: ${url}","messagePattern":"4dayweek: invalid URL: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/4dayweek.mjs","lineNumber":53,"sourceCode":"    try {\n      const parsed = new URL(value);\n      if (parsed.protocol === 'https:' && parsed.hostname === TRUSTED_HOST) {\n        return { url: FEED_BASE };\n      }\n    } catch {\n      // Ignore malformed URLs; another provider may still claim the entry.\n    }\n  }\n  return null;\n}\n\n/** @param {string} url */\nfunction assertFourDayUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`4dayweek: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`4dayweek: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== TRUSTED_HOST) {\n    throw new Error(`4dayweek: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}`);\n  }\n  return url;\n}\n\n/** Resolve the page cap: a positive integer `max_pages` on the entry, capped. */\nfunction resolveMaxPages(entry) {\n  const v = entry?.max_pages;\n  if (Number.isInteger(v) && v > 0) return Math.min(v, MAX_PAGES_CAP);\n  return DEFAULT_MAX_PAGES;\n}\n\n// NaN-safe: posted is epoch SECONDS → ms; anything non-finite yields undefined.\nfunction toEpochMs(seconds) {\n  return Number.isFinite(seconds) ? seconds * 1000 : undefined;","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/santifer/career-ops/blob/1696bec4d021768e7359f9aad6b329cba883da20/providers/4dayweek.mjs#L35-L71","documentation":"assertFourDayUrl() in providers/4dayweek.mjs is an SSRF/allowlist guard: before the provider fetches or links any URL, it parses it with new URL() and throws 'invalid URL' if parsing fails. This catches malformed strings — missing scheme, spaces, typos — before they reach fetch.","triggerScenarios":"Passing any string that new URL() rejects to the 4dayweek provider's URL assertion path: an empty string, a bare hostname like '4dayweek.io/job/123', a URL with unencoded spaces, or a config entry where the URL field is undefined/null coerced to 'undefined'.","commonSituations":"portals.yml or a feed entry missing the url field (undefined interpolated into the message); copy-pasted URLs containing trailing whitespace or surrounding quotes; hand-written URLs missing 'https://'; template strings where an upstream variable was empty.","solutions":["Print/log the exact url value at the call site — the message already interpolates it — and look for undefined/empty/trailing whitespace.","Fix the source entry (portals.yml, feed config) so it is an absolute URL starting with https://4dayweek.io/...","If the URL comes from user input, trim() it and validate with new URL() before calling the provider.","Ensure the entry's URL field actually exists (not undefined) — a missing field coerces to the string 'undefined'."],"exampleFix":"// before\nprovider.check('4dayweek.io/job/123'); // TypeError: invalid URL\n// after\nprovider.check('https://4dayweek.io/job/123');","handlingStrategy":"validation","validationCode":"function isValidUrl(url) {\n  if (typeof url !== 'string') return false;\n  try { new URL(url); return true; } catch { return false; }\n}\nif (!isValidUrl(entry.url)) throw new Error(`Skip entry: malformed url ${JSON.stringify(entry.url)}`);","typeGuard":"function isUrlString(v) {\n  if (typeof v !== 'string' || v.length === 0) return false;\n  try { new URL(v); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  provider.check(url);\n} catch (err) {\n  if (err.message.startsWith('4dayweek: invalid URL')) {\n    console.warn(`Skipping malformed entry: ${err.message}`);\n    return null;\n  }\n  throw err;\n}","preventionTips":["Always store absolute https:// URLs in provider/feed entries — never bare hostnames.","trim() and strip surrounding quotes from URLs coming from config or user input.","Validate all url fields at config-load time (fail fast on the whole file, not per request).","Watch for undefined interpolated into URLs — check the entry actually has a url key."],"tags":["url-validation","ssrf-protection","input-validation"],"backgroundTag":"invalid-url-format","analyzedSha":"1696bec4d021768e7359f9aad6b329cba883da20","analyzedAt":"2026-09-01T19:19:23.111Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}