{"record":{"id":"8ce92a8fd1c117b4","repo":"mastra-ai/mastra","slug":"push-notification-url-must-not-target-local-or-pri","errorCode":null,"errorMessage":"Push notification URL must not target local or private IPs: ${hostname}","messagePattern":"Push notification URL must not target local or private IPs: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/server/src/server/a2a/push-notification-sender.ts","lineNumber":95,"sourceCode":"\n  private async resolveValidatedDestination(rawUrl: string) {\n    const url = new URL(rawUrl);\n\n    if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n      throw new Error(`Push notification URL must use http or https: ${url.protocol}`);\n    }\n\n    const hostname = url.hostname.toLowerCase();\n    if (this.options.allowedHosts && !this.options.allowedHosts.includes(hostname)) {\n      throw new Error(`Push notification host is not allowed: ${hostname}`);\n    }\n\n    if (isDisallowedHostname(hostname)) {\n      throw new Error(`Push notification URL must not target local or internal hosts: ${hostname}`);\n    }\n\n    if (isDisallowedIpAddress(hostname)) {\n      throw new Error(`Push notification URL must not target local or private IPs: ${hostname}`);\n    }\n\n    const resolvedAddresses =\n      isIP(hostname) === 0\n        ? await (this.options.lookup ?? defaultLookup)(hostname, { all: true, verbatim: true })\n        : [{ address: hostname, family: isIP(hostname) }];\n\n    if (resolvedAddresses.some(result => isDisallowedIpAddress(result.address))) {\n      throw new Error(`Push notification URL resolved to a local or private IP: ${hostname}`);\n    }\n\n    const requestUrl = new URL(url.toString());\n    requestUrl.hostname = resolvedAddresses[0]!.address;\n\n    return {\n      originalUrl: url,\n      requestUrl,\n      hostHeader: url.host,","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/a2a/push-notification-sender.ts#L77-L113","documentation":"The sender rejects push notification URLs whose hostname is itself a local or private IP address (127.0.0.0/8, 10/8, 172.16/12, 192.168/16, link-local, etc.) via isDisallowedIpAddress, again to prevent server-side request forgery into internal networks.","triggerScenarios":"A push notification URL uses a literal private/loopback IP as the host, e.g. 'http://127.0.0.1:4111/callback', 'http://10.0.0.5/hook', 'http://169.254.169.254/...'.","commonSituations":"Local testing against a dev server bound to loopback, container-to-container calls using bridge-network IPs, misconfigured Kubernetes in-cluster callback addresses, or copying an internal service address into the push config.","solutions":["Use a DNS hostname backed by a public IP for the push endpoint","For local dev, tunnel the receiver publicly or use an address that passes the private-IP check","If internal delivery is truly required, deploy a reverse proxy with a public hostname that forwards to the internal service"],"exampleFix":"// before\nurl: 'http://192.168.1.10:4111/a2a/push'\n// after\nurl: 'https://a2a-push.example.com/a2a/push'","handlingStrategy":"validation","validationCode":"import { isIP } from 'node:net';\nfunction assertNonPrivateIpHost(raw: string) {\n  const host = new URL(raw).hostname;\n  if (isIP(host) !== 0 && /^(127\\.|10\\.|192\\.168\\.|169\\.254\\.|172\\.(1[6-9]|2\\d|3[01])\\.)/.test(host)) {\n    throw new Error(`Refusing private IP host: ${host}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await sender.sendNotifications(task, configs);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('must not target local or private IPs')) {\n    logger.warn('Private-IP push target rejected', { err: err.message });\n  } else throw err;\n}","preventionTips":["Use DNS names, not literal IPs, for push endpoints","Exclude loopback/link-local/RFC1918 literals in webhook input validation","Put internal receivers behind a publicly addressed reverse proxy"],"tags":["a2a","ssrf","security","private-ip"],"backgroundTag":"ssrf-private-ip","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}