garrytan/gstack · error · Error
Blocked: scheme "${parsed.protocol}" is not allowed. Only ht
Error message
Blocked: scheme "${parsed.protocol}" is not allowed. Only http:, https:, and file: URLs are permitted. What it means
Navigation-safety guard at url-validation.ts:271-274. After file: is handled and http:/https: are allowed, any remaining scheme is rejected. This is an SSRF/script-injection control blocking data:, javascript:, chrome:, blob:, ftp:, about: and every other protocol from reaching page.goto().
Source
Thrown at browse/src/url-validation.ts:273
try {
fsPath = fileURLToPath(parsed);
} catch (e: any) {
throw new Error(`Invalid file URL: ${url} (${e.message})`);
}
// Reject path traversal after decoding — e.g. file:///tmp/safe%2F..%2Fetc/passwd
// Note: fileURLToPath doesn't collapse .., so a literal '..' in the decoded path
// is suspicious. path.resolve will normalize it; check the result against safe dirs.
validateReadPath(fsPath);
// Return the canonical file:// URL derived from the filesystem path + original
// query + hash. This guarantees page.goto() gets a well-formed URL regardless
// of input shape while preserving SPA route/query params.
return pathToFileURL(fsPath).href + parsed.search + parsed.hash;
}
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
throw new Error(
`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)) {View on GitHub (pinned to 94993f7401)
Solutions
- Serve the content from http://localhost:<port> instead
- For inline HTML, use the load-html command (setContent) rather than goto
- Write the data URI's payload to a temp file and navigate via file:///
- Use a plain http(s) URL for any real page
Example fix
// before
await goto('data:text/html,<h1>hello</h1>')
// after — use load-html for inline content
await handleWriteCommand('load-html', ['--from-file', 'payload.json'], session, bm) Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED = new Set(['http:', 'https:', 'file:'])
function isAllowedScheme(u: string): boolean {
try { return ALLOWED.has(new URL(u).protocol) } catch { return false }
} Type guard
function isNavigableUrl(u: string): u is string {
try {
const p = new URL(u)
return p.protocol === 'http:' || p.protocol === 'https:' || p.protocol === 'file:'
} catch { return false }
} Prevention
- For inline HTML, use the load-html command instead of data: URIs
- Maintain an allowlist of schemes at the call site
- Serve fixture content from http://localhost
When it happens
Trigger: Calling goto/validateNavigationUrl with a URL whose protocol is not http:, https:, or file: — e.g. data:text/html,<h1>hi, javascript:alert(1), chrome://settings, blob:..., about:blank, ftp://host/f.
Common situations: Trying to load an inline HTML data URI as a fixture; using about:blank as a starting page; scraped content containing a blob: or chrome: link; test code that defaults to an empty data URI.
Related errors
- Blocked: ${parsed.hostname} is a cloud metadata endpoint. Ac
- Blocked: ${parsed.hostname} resolves to a cloud metadata IP.
- 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/99694baa3529a773.
Report an issue: GitHub.