garrytan/gstack · critical · Error
Blocked: ${parsed.hostname} resolves to a cloud metadata IP.
Error message
Blocked: ${parsed.hostname} resolves to a cloud metadata IP. Possible DNS rebinding attack. What it means
DNS-rebinding defense at url-validation.ts:287-293. After the literal-hostname block, the validator resolves the hostname (resolve4 + resolve6) and rejects it if any A/AAAA record points to a blocked metadata IP. Loopback and private-net literals are skipped to avoid latency in concurrent E2E runs.
Source
Thrown at browse/src/url-validation.ts:292
`Blocked: scheme "${parsed.protocol}" is not allowed. Only http:, https:, and file: URLs are permitted.`
);
}
const hostname = normalizeHostname(parsed.hostname.toLowerCase());
if (BLOCKED_METADATA_HOSTS.has(hostname) || isMetadataIp(hostname) || isBlockedIpv6(hostname)) {
throw new Error(
`Blocked: ${parsed.hostname} is a cloud metadata endpoint. Access is denied for security.`
);
}
// DNS rebinding protection: resolve hostname and check if it points to metadata IPs.
// Skip for loopback/private IPs — they can't be DNS-rebinded and the async DNS
// resolution adds latency that breaks concurrent E2E tests under load.
const isLoopback = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
const isPrivateNet = /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/.test(hostname);
if (!isLoopback && !isPrivateNet && await resolvesToBlockedIp(hostname)) {
throw new Error(
`Blocked: ${parsed.hostname} resolves to a cloud metadata IP. Possible DNS rebinding attack.`
);
}
return url;
}
View on GitHub (pinned to 94993f7401)
Solutions
- Do not navigate to untrusted or scraped hostnames without an allowlist
- Resolve the hostname yourself (dig <host>) and confirm it is not a metadata IP
- Route through a forward proxy that enforces destination allowlisting
- If the target is legitimate, point it at a safe IP and re-test
Defensive patterns
Strategy: validation
Validate before calling
import { resolve4, resolve6 } from 'node:dns/promises'
async function resolvesToMetadata(host: string): Promise<boolean> {
const meta = new Set(['169.254.169.254'])
try {
const [v4, v6] = await Promise.all([resolve4(host), resolve6(host).catch(()=>[])])
return [...v4, ...v6].some(a => meta.has(a) || /^fd|^fe[89ab]/.test(a))
} catch { return false }
} Prevention
- Do not navigate to untrusted hostnames; use a destination allowlist
- Resolve and inspect hostnames before handing them to goto
- Route outbound traffic through an allowlisting proxy
When it happens
Trigger: A hostname whose DNS A or AAAA records resolve to 169.254.169.254 or another blocked IP — the classic rebinding payload where the name looks benign but the address is the metadata service.
Common situations: Attacker-controlled domain referenced in scraped/templated URLs; stale or poisoned /etc/hosts or corporate DNS; a wildcard DNS service (e.g. nip.io style) pointed at the metadata IP.
Related errors
- Blocked: scheme "${parsed.protocol}" is not allowed. Only ht
- Blocked: ${parsed.hostname} is a cloud metadata endpoint. Ac
- Unsupported file URL host: ${parsed.host}. Use file:///<abso
- Invalid file URL: file:/// has no path. Use file:///<absolut
- Invalid file URL: ${url}. Use file:///<absolute-path> or fil
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/1231a82c4ce72cd8.
Report an issue: GitHub.