koala73/worldmonitor · error · Error
Webhook URL must not point to a private/local address
Error message
Webhook URL must not point to a private/local address
What it means
Returned by blockedNotificationWebhookUrlReason when the hostname is an IP literal (or otherwise directly classified) that isBlockedNotificationResolvedAddress flags as private/reserved — loopback, RFC1918 ranges, link-local, CGNAT 100.64/10, ULA, multicast, documentation ranges, etc. This is the static SSRF gate that catches an attacker (or misconfiguration) pointing the webhook directly at an internal IP before any DNS resolution is attempted.
Source
Thrown at api/_notification-webhook-ssrf.ts:223
async function defaultResolveHostname(hostname: string): Promise<string[]> {
const records = await Promise.all([
resolveDnsJson(hostname, 'A'),
resolveDnsJson(hostname, 'AAAA'),
]);
return records.flat();
}
/**
* Fail fast at registration when the webhook hostname currently resolves to a
* private or reserved address. Delivery repeats this check (and pins its
* connection) because DNS can change after registration.
*/
export async function assertNotificationWebhookRegistrationUrlSafe(
rawUrl: string,
resolveHostname: ResolveHostname = defaultResolveHostname,
): Promise<void> {
const staticError = blockedNotificationWebhookUrlReason(rawUrl);
if (staticError) throw new Error(staticError);
const hostname = new URL(rawUrl).hostname.toLowerCase();
if (isIpLiteral(hostname)) return;
let resolvedAddresses: string[];
try {
resolvedAddresses = await resolveHostname(hostname);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Webhook URL DNS resolution failed: ${message}`);
}
if (!resolvedAddresses.length) throw new Error('Webhook URL DNS resolution returned no addresses');
if (resolvedAddresses.some(isBlockedNotificationResolvedAddress)) {
throw new Error('Webhook URL must not point to a private/local address');
}
}
View on GitHub (pinned to ffec79ac33)
Solutions
- Register an https URL whose host resolves to a public IP you control.
- If the webhook legitimately lives behind a VPN/private network, expose it via a public ingress (load balancer, API gateway, tunnel) before registering.
- Re-run the registration through the public hostname only.
Example fix
// before
registerWebhook('https://10.0.0.5/internal-hook')
// after
registerWebhook('https://ingress.example.com/internal-hook') Defensive patterns
Strategy: validation
Validate before calling
import { isBlockedNotificationResolvedAddress } from './_notification-webhook-ssrf';
function webhookHostIsBlockedIpLiteral(rawUrl: string): boolean {
try {
const host = new URL(rawUrl).hostname.toLowerCase();
return isBlockedNotificationResolvedAddress(host);
} catch { return false; }
}
if (webhookHostIsBlockedIpLiteral(input)) {
return res.status(400).json({ error: 'Webhook URL must not point to a private/local address.' });
} Type guard
import { isBlockedNotificationResolvedAddress } from './_notification-webhook-ssrf';
function isPublicWebhookHost(value: unknown): boolean {
if (typeof value !== 'string') return false;
try {
const host = new URL(value).hostname.toLowerCase();
return !isBlockedNotificationResolvedAddress(host);
} catch { return false; }
} Try / catch
try {
await assertNotificationWebhookRegistrationUrlSafe(rawUrl);
} catch (err) {
if (err.message === 'Webhook URL must not point to a private/local address') {
return res.status(400).json({ error: 'Point the webhook at a public https endpoint.' });
}
throw err;
} Prevention
- Always expose webhooks via a public ingress; never register private-network addresses.
- When self-hosting, put the webhook behind a load balancer/API gateway with a public IP.
- Educate users that private-IP webhooks are blocked by design (SSRF defense).
When it happens
Trigger: Webhook registration with a URL whose host is a private IP literal such as `https://10.0.0.5/`, `https://127.0.0.1/`, `https://192.168.1.1/`, `https://[::1]/`, or any RFC1918/loopback/link-local/ULA address.
Common situations: Internal-tool webhook mistakenly registered against a private network address; adversarial SSRF probe; a stale config pointing at a decommissioned internal service.
Related errors
- Webhook URL must not point to a metadata endpoint
- Webhook URL is not a valid URL
- Webhook URL must use HTTPS
- Webhook URL DNS resolution returned no addresses
- serverUrl host is not allowed
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/18655adb282cb999.
Report an issue: GitHub.