{"record":{"id":"e0b31f56556d82c3","repo":"santifer/career-ops","slug":"pinpoint-url-must-use-https-url","errorCode":null,"errorMessage":"pinpoint: URL must use HTTPS: ${url}","messagePattern":"pinpoint: URL must use HTTPS: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/pinpoint.mjs","lineNumber":32,"sourceCode":"// 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;\n  if (!PINPOINT_HOST_RE.test(parsed.hostname)) return null;\n  return `https://${parsed.hostname}/postings.json`;","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/pinpoint.mjs#L14-L50","documentation":"Thrown by pinpoint's assertPinpointUrl() when the URL parses but protocol is not 'https:'. Second SSRF gate: prevents plaintext HTTP fetches to Pinpoint ATS boards. The comment in source notes that detect() and fetch() both route through this constant, so the HTTPS requirement applies universally.","triggerScenarios":"A valid URL with http: scheme: entry.careers_url prefixed http://, or a test fixture against http://localhost. The value reaches assertPinpointUrl via resolveApiUrl() or a direct call.","commonSituations":"Portals.yml authored with http://. Config tool defaulting to http. Local development against a non-TLS mock server.","solutions":["Update careers_url to use https:// in portals.yml.","Use HTTPS for test mock servers.","Audit config-generation scripts to enforce https:// as default."],"exampleFix":"// before\ncareers_url: 'http://acme.pinpointhq.com'\n\n// after\ncareers_url: 'https://acme.pinpointhq.com'","handlingStrategy":"validation","validationCode":"/** Normalize Pinpoint URL to HTTPS. */\nfunction ensureHttps(url) {\n  if (typeof url !== 'string') return null;\n  return url.replace(/^http:\\/\\//i, 'https://');\n}\n\nentry.careers_url = ensureHttps(entry.careers_url) || entry.careers_url;","typeGuard":"/** @param {string} url @returns {boolean} */\nfunction isHttpsUrl(url) {\n  try { return new URL(url).protocol === 'https:'; } catch { return false; }\n}","tryCatchPattern":"try {\n  await pinpointProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('must use HTTPS')) {\n    entry.careers_url = (entry.careers_url || '').replace(/^http:/i, 'https:');\n    await pinpointProvider.fetch(entry, ctx);\n  } else throw err;\n}","preventionTips":["Author Pinpoint URLs with https:// in config.","Lint config for http:// entries.","Use HTTPS mock servers in tests."],"tags":["url-validation","ssrf-guard","https","pinpoint","config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}