{"record":{"id":"27c9c32a3f236b51","repo":"mastra-ai/mastra","slug":"push-notification-url-must-not-target-local-or-int","errorCode":null,"errorMessage":"Push notification URL must not target local or internal hosts: ${hostname}","messagePattern":"Push notification URL must not target local or internal hosts: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/server/src/server/a2a/push-notification-sender.ts","lineNumber":91,"sourceCode":"\n  getStore() {\n    return this.pushNotificationStore;\n  }\n\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","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/a2a/push-notification-sender.ts#L73-L109","documentation":"Even without an allowlist, the sender hard-blocks push notification URLs whose hostname is a well-known local/internal name (e.g. localhost, *.local, metadata endpoints, *.internal) via isDisallowedHostname. This prevents SSRF into loopback or cloud-internal services.","triggerScenarios":"Registering a push notification URL whose hostname resolves to a disallowed name such as 'localhost', 'localhost.localdomain', 'metadata.google.internal', or any '*.[internal/local]' style host.","commonSituations":"Developers pointing push webhooks at a locally running test server (localhost:4111), Docker-compose service names like 'http://backend:3000', or accidentally registering an internal-only callback host in production config.","solutions":["Use a publicly resolvable hostname for the push notification endpoint","For local development, expose the endpoint via a tunnel (ngrok/smee) or a machine's LAN IP if isDisallowedIpAddress permits it","If this is a legitimate internal host, host the receiver externally or relax the sender's hostname blocklist via supported options"],"exampleFix":"// before\nurl: 'http://localhost:4111/a2a/push'\n// after\nurl: 'https://dev-tunnel.example.com/a2a/push'","handlingStrategy":"validation","validationCode":"const LOCAL_HOST_RE = /(^|\\.)(local|internal|localhost)$/i;\nfunction assertPublicHost(raw: string) {\n  const host = new URL(raw).hostname.toLowerCase();\n  if (host === 'localhost' || LOCAL_HOST_RE.test(host)) {\n    throw new Error(`Host ${host} is local/internal`);\n  }\n}","typeGuard":"const isPublicHostname = (raw: string): boolean => {\n  const h = new URL(raw).hostname.toLowerCase();\n  return h !== 'localhost' && !/\\.(local|internal)$/i.test(h);\n};","tryCatchPattern":"try {\n  await sender.sendNotifications(task, configs);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('must not target local or internal hosts')) {\n    logger.warn('Internal push target rejected', { err: err.message });\n  } else throw err;\n}","preventionTips":["Never register localhost or internal service names as push endpoints in shared environments","Use public tunnels for local development","Document that docker-compose service names will be blocked"],"tags":["a2a","ssrf","security","localhost"],"backgroundTag":"ssrf-blocked-host","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}