mastra-ai/mastra · error

Push notification URL must not target local or private IPs:

Error message

Push notification URL must not target local or private IPs: ${hostname}

What it means

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.

Source

Thrown at packages/server/src/server/a2a/push-notification-sender.ts:95

  private async resolveValidatedDestination(rawUrl: string) {
    const url = new URL(rawUrl);

    if (url.protocol !== 'https:' && url.protocol !== 'http:') {
      throw new Error(`Push notification URL must use http or https: ${url.protocol}`);
    }

    const hostname = url.hostname.toLowerCase();
    if (this.options.allowedHosts && !this.options.allowedHosts.includes(hostname)) {
      throw new Error(`Push notification host is not allowed: ${hostname}`);
    }

    if (isDisallowedHostname(hostname)) {
      throw new Error(`Push notification URL must not target local or internal hosts: ${hostname}`);
    }

    if (isDisallowedIpAddress(hostname)) {
      throw new Error(`Push notification URL must not target local or private IPs: ${hostname}`);
    }

    const resolvedAddresses =
      isIP(hostname) === 0
        ? await (this.options.lookup ?? defaultLookup)(hostname, { all: true, verbatim: true })
        : [{ address: hostname, family: isIP(hostname) }];

    if (resolvedAddresses.some(result => isDisallowedIpAddress(result.address))) {
      throw new Error(`Push notification URL resolved to a local or private IP: ${hostname}`);
    }

    const requestUrl = new URL(url.toString());
    requestUrl.hostname = resolvedAddresses[0]!.address;

    return {
      originalUrl: url,
      requestUrl,
      hostHeader: url.host,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Use a DNS hostname backed by a public IP for the push endpoint
  2. For local dev, tunnel the receiver publicly or use an address that passes the private-IP check
  3. If internal delivery is truly required, deploy a reverse proxy with a public hostname that forwards to the internal service

Example fix

// before
url: 'http://192.168.1.10:4111/a2a/push'
// after
url: 'https://a2a-push.example.com/a2a/push'
Defensive patterns

Strategy: validation

Validate before calling

import { isIP } from 'node:net';
function assertNonPrivateIpHost(raw: string) {
  const host = new URL(raw).hostname;
  if (isIP(host) !== 0 && /^(127\.|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.)/.test(host)) {
    throw new Error(`Refusing private IP host: ${host}`);
  }
}

Try / catch

try {
  await sender.sendNotifications(task, configs);
} catch (err) {
  if (err instanceof Error && err.message.includes('must not target local or private IPs')) {
    logger.warn('Private-IP push target rejected', { err: err.message });
  } else throw err;
}

Prevention

When it happens

Trigger: 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/...'.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/8ce92a8fd1c117b4. Report an issue: GitHub.