{"record":{"id":"e2d4580ad10a6ef4","repo":"santifer/career-ops","slug":"pinpoint-invalid-url-url","errorCode":null,"errorMessage":"pinpoint: invalid URL: ${url}","messagePattern":"pinpoint: invalid URL: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/pinpoint.mjs","lineNumber":30,"sourceCode":"//\n// Per-tenant subdomains are the variable part — SSRF defence uses a regex\n// match on `<safe-slug>.pinpointhq.com` rather than a static allowlist, the\n// same approach as the recruitee provider.\n\n// The tenant label must be a valid DNS label: it may contain hyphens but must\n// not start or end with one (so `acme-.pinpointhq.com` is rejected). The\n// optional trailing group keeps single-character labels (e.g. `a.pinpointhq.com`)\n// valid. detect() and fetch() both route through this constant via\n// resolveApiUrl()/assertPinpointUrl(), so the stricter check applies everywhere.\nconst PINPOINT_HOST_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.pinpointhq\\.com$/;\n\n/** @param {string} url */\nfunction assertPinpointUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`pinpoint: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`pinpoint: URL must use HTTPS: ${url}`);\n  if (!PINPOINT_HOST_RE.test(parsed.hostname)) {\n    throw new Error(`pinpoint: untrusted hostname \"${parsed.hostname}\" — must match <slug>.pinpointhq.com`);\n  }\n  return url;\n}\n\nfunction resolveApiUrl(entry) {\n  const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';\n  if (!raw) return null;\n  let parsed;\n  try {\n    parsed = new URL(raw);\n  } catch {\n    return null;\n  }\n  if (parsed.protocol !== 'https:') return null;","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/pinpoint.mjs#L12-L48","documentation":"assertPinpointUrl validates URLs before contacting a Pinpoint HQ tenant. The input string could not be parsed by the URL constructor, so it throws before the protocol and hostname checks. The URL is not a well-formed absolute URL at all.","triggerScenarios":"Calling fetch or validation paths reaching assertPinpointUrl (pinpoint.mjs line 30) with a careers_url like 'acme.pinpointhq.com' (no scheme), an empty/undefined string, or a string with characters illegal in URLs.","commonSituations":"portals.yml value missing https://; YAML quoting issues (colon-containing URL truncated or mangled); copy/paste introducing zero-width characters or line breaks into the scalar.","solutions":["Set careers_url to a full absolute URL: https://<slug>.pinpointhq.com in portals.yml.","Quote the YAML scalar and re-check indentation so the full string lands in one value.","Sanitize the value: strip whitespace/invisible characters; verify with new URL(value) in a node -e one-liner.","If the URL is constructed programmatically, fix the builder to always include the https:// scheme."],"exampleFix":"// before (portals.yml)\ncareers_url: acme.pinpointhq.com\n// after\ncareers_url: https://acme.pinpointhq.com","handlingStrategy":"validation","validationCode":"export function isWellFormedPinpointUrl(u) {\n  if (typeof u !== 'string' || u.trim() === '') return false;\n  try { const parsed = new URL(u); return parsed.protocol === 'https:'; } catch { return false; }\n}\nif (!isWellFormedPinpointUrl(entry.careers_url)) throw new Error(`pinpoint: careers_url for ${entry.name} is not a valid absolute https URL`);","typeGuard":"function isPinpointUrl(u) {\n  if (typeof u !== 'string') return false;\n  try {\n    const parsed = new URL(u);\n    return parsed.protocol === 'https:' && /^[a-z0-9-]+\\.pinpointhq\\.com$/.test(parsed.hostname);\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  await pinpointProvider.fetch(entry, ctx);\n} catch (e) {\n  if (String(e.message).startsWith('pinpoint: invalid URL')) {\n    logger.warn({ entry: entry.name, url: entry.careers_url }, 'unparseable careers_url — add https:// scheme, check YAML quoting');\n    return null;\n  }\n  throw e;\n}","preventionTips":["Always include https:// in careers_url values in portals.yml.","Quote YAML scalars with colons; avoid manual line wraps inside URLs.","Add a load-time lint that new URL()'s every careers_url and fails fast on bad input.","Strip whitespace/invisible characters when pasting URLs into config."],"tags":["url-validation","config","pinpoint","yaml"],"backgroundTag":"invalid-url-format","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}