n8n-io/n8n · error · Error
Webhook URL hostname resolves to a private/internal IP addre
Error message
Webhook URL hostname resolves to a private/internal IP address
What it means
`validateWebhookUrlWithDns` resolves the hostname's A records via `dns.resolve` and rejects the URL if any returned IPv4 is private/internal per `isPrivateIp`. This is the DNS-rebinding defense: a hostname that looks public can still resolve to an internal IP, so the literal-hostname checks alone are insufficient. The first private record found trips the throw.
Source
Thrown at packages/@n8n/ai-workflow-builder.ee/evaluations/cli/webhook.ts:159
/**
* Validate webhook URL with DNS resolution for comprehensive SSRF protection.
* Resolves the hostname and validates that resolved IPs are not private/internal.
*/
export async function validateWebhookUrlWithDns(webhookUrl: string): Promise<void> {
validateWebhookUrl(webhookUrl);
const url = new URL(webhookUrl);
const hostname = url.hostname.toLowerCase();
if (isPrivateIp(hostname)) {
throw new Error('Webhook URL cannot target private/internal IP addresses');
}
try {
const addresses = await dns.resolve(hostname);
for (const ip of addresses) {
if (isPrivateIp(ip)) {
throw new Error('Webhook URL hostname resolves to a private/internal IP address');
}
}
try {
const ipv6Addresses = await dns.resolve6(hostname);
for (const ip of ipv6Addresses) {
if (isPrivateIp(ip)) {
throw new Error('Webhook URL hostname resolves to a private/internal IP address');
}
}
} catch {
// IPv6 resolution may fail if no AAAA records exist, which is fine
}
} catch (error) {
if (error instanceof Error && error.message.includes('private/internal')) {
throw error;
}
}View on GitHub (pinned to 5ac6606e81)
Solutions
- Ensure the hostname's public A records resolve to public IPs only.
- If split-horizon DNS is the cause, run the eval from a network position that gets the public view, or pin the webhook to a host with consistent public resolution.
- If you genuinely need an internal target, do not use `validateWebhookUrlWithDns` — deliver results out-of-band from outside this code path.
Example fix
// before
// webhook.example.com A record -> 10.0.0.5
await validateWebhookUrlWithDns('https://webhook.example.com/hook');
// after
// fix DNS so webhook.example.com -> 203.0.113.5
await validateWebhookUrlWithDns('https://webhook.example.com/hook'); Defensive patterns
Strategy: try-catch
Validate before calling
import dns from 'node:dns/promises';
async function preflightPublicIpv4(hostname: string): Promise<void> {
let addrs: string[];
try { addrs = await dns.resolve(hostname); }
catch (e) { throw new Error(`cannot resolve ${hostname}: ${(e as Error).message}`); }
if (addrs.some(isPrivateIp)) {
throw new Error(`${hostname} resolves to a private IPv4; webhook rejected (SSRF)`);
}
}
await preflightPublicIpv4(new URL(webhookUrl).hostname); Try / catch
try {
await validateWebhookUrlWithDns(webhookUrl);
} catch (e) {
if (e instanceof Error && e.message.includes('resolves to a private/internal IP')) {
// SSRF guard tripped — do NOT retry; surface to operator and skip webhook
logger.error(`webhook rejected by SSRF guard: ${e.message}`);
return;
}
throw e;
} Prevention
- Pin webhook targets to hostnames whose public A records you control.
- From corp networks, run the eval where DNS returns the public view, or use a resolver that does.
- Do not retry on this error — it is a deliberate security block, not a transient failure.
When it happens
Trigger: A public-looking hostname whose A record returns `10.x`, `192.168.x`, `127.x`, etc. — either intentionally (internal service exposed via a public DNS name) or maliciously (DNS rebinding). Also triggered by split-horizon DNS where the eval host resolves to the internal view.
Common situations: Running evals from inside a corp network where DNS returns internal IPs for `.com` names; a test domain that points at `127.0.0.1` for fun; or a webhook provider whose DNS temporarily includes an internal address.
Related errors
- Webhook URL cannot target internal hostname: ${hostname}
- Webhook URL must use HTTPS. Got: ${url.protocol}
- Webhook URL cannot target localhost
- Webhook URL cannot target private/internal IP addresses
- Redirect to a different host: ${finalUrl}
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/b11ef5d16a60f949.
Report an issue: GitHub.