{"record":{"id":"ecb72f81b62539a4","repo":"santifer/career-ops","slug":"glints-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"glints: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_GLINTS_HOSTS].join(', ')}","messagePattern":"glints: untrusted hostname \"(.+?)\" — must be one of: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/glints.mjs","lineNumber":79,"sourceCode":"      }\n      createdAt\n    }\n    expInfo\n    hasMore\n  }\n}`;\n\n/** @param {string} url */\nfunction assertGlintsUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`glints: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`glints: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_GLINTS_HOSTS.has(parsed.hostname))\n    throw new Error(`glints: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_GLINTS_HOSTS].join(', ')}`);\n  return url;\n}\n\n// NaN-safe Date.parse\nfunction toEpochMs(value) {\n  if (!value) return undefined;\n  const parsed = Date.parse(value);\n  return Number.isNaN(parsed) ? undefined : parsed;\n}\n\n/**\n * Derive the job detail base URL from the API hostname.\n * @param {string} apiUrl\n * @returns {string}\n */\nfunction deriveBaseUrl(apiUrl) {\n  try {\n    const parsed = new URL(apiUrl);","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/glints.mjs#L61-L97","documentation":"glints.mjs throws this in assertGlintsUrl() when the URL is valid HTTPS but its hostname is not in ALLOWED_GLINTS_HOSTS ('glints.com', 'www.glints.com', 'glints.id'). It is the host-allowlist half of the SSRF guard for the operator-supplied entry.api; combined with redirect:'error' it keeps Glints requests on a known-good host.","triggerScenarios":"entry.api is set to a host outside the allowlist (e.g. a regional mirror like https://glints.sg/... or a proxy host); a test calls assertGlintsUrl('https://evil.com/...'); an operator pointed api at a generic GraphQL proxy.","commonSituations":"Operator tries a regional Glints domain not yet in the allowlist; a corporate egress proxy host was used; a malicious/typo'd api value targets an internal host (the guard's purpose).","solutions":["Set api to one of the allowed hosts (https://glints.com/api/v2-alc/graphql) or omit api to use the default.","If a legitimate additional Glints host is needed, add it to ALLOWED_GLINTS_HOSTS in glints.mjs with justification.","Treat an unexpected hostname here as a possible config-injection/SSRF attempt and verify the source of the api value."],"exampleFix":"# before\n- name: Glints (ID)\n  provider: glints\n  api: https://glints-proxy.internal/graphql   # off-allowlist -> throws\n\n# after\n- name: Glints (ID)\n  provider: glints\n  api: https://glints.com/api/v2-alc/graphql   # (or omit api entirely)","handlingStrategy":"validation","validationCode":"// Pre-flight: ensure a Glints entry.api host is allowlisted (SSRF defense at config time).\nconst GLINTS_HOSTS = new Set(['glints.com','www.glints.com','glints.id']);\nfunction glintsApiHostAllowed(entry) {\n  const api = entry.api || 'https://glints.com/api/v2-alc/graphql';\n  let u;\n  try { u = new URL(api); } catch { return false; }\n  return GLINTS_HOSTS.has(u.hostname);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Omit api unless you must override; the default is already allowlisted.","Treat an off-allowlist api as a possible SSRF attempt and audit its origin.","If a new legitimate Glints host appears, add it to ALLOWED_GLINTS_HOSTS with a justification comment."],"tags":["ssrf-guard","host-allowlist","url-validation","glints","config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}