{"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":null,"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/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/glints.mjs#L61-L97","documentation":"assertGlintsUrl allowlists exactly three hosts: glints.com, www.glints.com, and glints.id (ALLOWED_GLINTS_HOSTS). A parsed HTTPS URL on any other hostname triggers this error, which lists the allowed set. Since Glints is accessed through a reverse-engineered endpoint, the allowlist blocks a misconfigured or malicious api override from sending queries elsewhere.","triggerScenarios":"A glints entry sets api to a URL on another host — e.g. https://api.glints.com/..., a regional mirror like glints.sg, or a third-party/mock endpoint — and assertGlintsUrl rejects the hostname.","commonSituations":"Assuming subdomains (api.glints.com) are allowed (they are not — only the three exact hosts); pointing at a staging or self-hosted mock of the GraphQL endpoint; a typo in the configured host.","solutions":["Use one of the allowed hosts exactly: glints.com, www.glints.com, or glints.id (e.g. https://glints.com/api/v2-alc/graphql)","Remove the api override entirely to fall back to the vetted default endpoint","If you genuinely need another host (local mock), the allowlist is a hard boundary in provider code — do not bypass it in config; test mocks outside the provider instead"],"exampleFix":"// before\napi: https://api.glints.com/v2-alc/graphql\n// after\napi: https://glints.com/api/v2-alc/graphql","handlingStrategy":"validation","validationCode":"const ALLOWED = new Set(['glints.com','www.glints.com','glints.id']);\nif (entry.api) {\n  const host = new URL(entry.api).hostname;\n  if (!ALLOWED.has(host)) throw new Error(`glints entry ${entry.name}: host ${host} not in allowlist`);\n}","typeGuard":"function isAllowedGlintsHost(v) { try { return ['glints.com','www.glints.com','glints.id'].includes(new URL(v).hostname); } catch { return false; } }","tryCatchPattern":"try {\n  assertGlintsUrl(url);\n} catch (e) {\n  if (String(e.message).includes('untrusted hostname')) {\n    console.error(`Only glints.com, www.glints.com, glints.id are allowed; got ${url}. Remove the api override to use the default.`);\n  }\n  throw e;\n}","preventionTips":["Omit the api field entirely unless you truly need a custom endpoint — the default is already allowlisted","Note subdomains (api.glints.com) are NOT in the allowlist; only exact hosts are","Do not attempt to bypass the host allowlist for mocks; test mocks outside the provider"],"tags":["allowlist","ssrf","hostname","security","glints"],"backgroundTag":"invalid-url","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"}