{"record":{"id":"624d16a382b8271c","repo":"koala73/worldmonitor","slug":"callbackurl-must-use-https","errorCode":null,"errorMessage":"callbackUrl must use https","messagePattern":"callbackUrl must use https","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"warning","filePath":"server/worldmonitor/shipping/v2/webhook-shared.ts","lineNumber":121,"sourceCode":"    return (data.Answer ?? [])\n      .filter(answer => answer.type === expectedType && typeof answer.data === 'string')\n      .map(answer => answer.data!);\n  };\n  const records = await Promise.all([resolveRecordType('A'), resolveRecordType('AAAA')]);\n  return records.flat();\n}\n\n/**\n * Validate the current DNS answer before storing a webhook. Delivery makes the\n * same check immediately before send and pins the resulting socket, which\n * keeps this fail-fast check from becoming the only SSRF control.\n */\nexport async function assertCallbackUrlRegistrationSafe(\n  callbackUrl: string,\n  resolveHostname: ResolveHostname = defaultResolveHostname,\n): Promise<void> {\n  const staticError = isBlockedCallbackUrl(callbackUrl);\n  if (staticError) throw new Error(staticError);\n\n  const hostname = new URL(callbackUrl).hostname.toLowerCase();\n  if (isIpLiteral(hostname)) return;\n  let resolvedAddresses: string[];\n  try {\n    resolvedAddresses = await resolveHostname(hostname);\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error);\n    throw new Error(`callbackUrl DNS resolution failed: ${message}`);\n  }\n  if (!resolvedAddresses.length) throw new Error('callbackUrl DNS resolution returned no addresses');\n  const blocked = resolvedAddresses.find(isBlockedResolvedAddress);\n  if (blocked) throw new Error('callbackUrl resolves to a private/reserved address');\n}\n\nexport async function generateSecret(): Promise<string> {\n  const bytes = new Uint8Array(32);\n  crypto.getRandomValues(bytes);","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/server/worldmonitor/shipping/v2/webhook-shared.ts#L103-L139","documentation":"Static SSRF-policy check in isBlockedCallbackUrl: after parsing the URL, any protocol other than https: returns 'callbackUrl must use https', rethrown at webhook-shared.ts:121 and surfaced by registerWebhook as a 400 on callbackUrl. Webhook payloads carry a signing secret and partner data, so plaintext http callbacks are rejected outright — there is no opt-out.","triggerScenarios":"POST RegisterWebhook with callbackUrl starting 'http://' (including http://localhost or an http intranet host); a URL whose scheme is typo'd ('httpss://') so it fails the equality; mixed local testing against a plain-http dev server.","commonSituations":"Local dev using http://localhost:3000 as the callback (also independently blocked as a private host); partner endpoints that only expose plain http; scheme defaulted to http by an HTTP client library when building the URL.","solutions":["Use an https:// callback URL with a valid TLS certificate on a public host","For local testing, use a tunnel (for example a localhost https tunnel) to get a real https hostname","Check the scheme explicitly before submitting: new URL(url).protocol === 'https:'"],"exampleFix":"// before\nregisterWebhook(ctx, { callbackUrl: 'http://api.example.com/cb', chokepointIds });\n// after\nregisterWebhook(ctx, { callbackUrl: 'https://api.example.com/cb', chokepointIds });","handlingStrategy":"validation","validationCode":"if (new URL(callbackUrl).protocol !== 'https:') throw new RangeError('callbackUrl must use https');","typeGuard":"const isHttpsUrl = (v: unknown): v is string => { if (typeof v !== 'string') return false; try { return new URL(v).protocol === 'https:'; } catch { return false; } };","tryCatchPattern":"catch (e) { if (e?.details?.[0]?.description === 'callbackUrl must use https') { switch to an https endpoint and re-submit; do not expect an http exception } else throw e; }","preventionTips":["Provision TLS on callback receivers before registering","For local dev, use an https tunnel — http callbacks are rejected without exception","Double-check scheme typos like 'httpss://' which also fail this check"],"tags":["validation","https","ssl","webhooks","ssrf","http-400"],"backgroundTag":"https-required","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}