{"record":{"id":"25964ff07051f0e4","repo":"koala73/worldmonitor","slug":"callbackurl-hostname-is-a-blocked-metadata-endpoin","errorCode":null,"errorMessage":"callbackUrl hostname is a blocked metadata endpoint","messagePattern":"callbackUrl hostname is a blocked metadata endpoint","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: if the URL's lowercase hostname is in BLOCKED_METADATA_HOSTNAMES (169.254.169.254, metadata.google.internal, metadata.internal, instance-data, metadata, computemetadata, link-local.s3.amazonaws.com), registration rejects with this message (thrown at webhook-shared.ts:121, surfaced as 400 by registerWebhook). These hostnames expose cloud instance metadata (credentials, IAM tokens) and are the primary SSRF jackpot.","triggerScenarios":"POST RegisterWebhook with a callbackUrl pointing at a cloud metadata service, e.g. https://169.254.169.254/latest/meta-data or http://metadata.google.internal/computeMetadata/v1 — either as an attack or as a misconfigured internal integration.","commonSituations":"Security testing/probing of the webhook feature; a partner accidentally pasting an internal cloud URL; automated tooling that discovers and registers internal endpoints; attempts to make WorldMonitor delivery fetch instance credentials.","solutions":["Point the callback at your own public https endpoint — metadata hostnames can never be webhook targets","If this appeared in logs unexpectedly, treat it as a potential SSRF attempt and review the caller/credentials that submitted it","Audit any integration templates or scripts that auto-generate callbackUrl from environment discovery"],"exampleFix":"// before\nregisterWebhook(ctx, { callbackUrl: 'http://metadata.google.internal/computeMetadata/v1', chokepointIds });\n// after\nregisterWebhook(ctx, { callbackUrl: 'https://status.example.com/webhooks/worldmonitor', chokepointIds });","handlingStrategy":"validation","validationCode":"const BLOCKED = new Set(['169.254.169.254','metadata.google.internal','metadata.internal','instance-data','metadata','computemetadata','link-local.s3.amazonaws.com']);\nif (BLOCKED.has(new URL(callbackUrl).hostname.toLowerCase())) throw new RangeError('metadata endpoints cannot be webhook targets');","typeGuard":null,"tryCatchPattern":"catch (e) { if (e?.details?.[0]?.description?.includes('blocked metadata endpoint')) { replace the callback with your own public https endpoint } else throw e; }","preventionTips":["Never register cloud metadata hostnames as callbacks","Treat unexpected occurrences in logs as SSRF attempts and review the submitting credentials","Audit scripts that auto-discover and register internal endpoints"],"tags":["ssrf","security","cloud-metadata","webhooks","http-400","callback-url"],"backgroundTag":"ssrf-protection","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"}