{"record":{"id":"7da5ef9328b0b769","repo":"garrytan/gstack","slug":"invalid-file-url-url-e-message","errorCode":null,"errorMessage":"Invalid file URL: ${url} (${e.message})","messagePattern":"Invalid file URL: (.+?) \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/url-validation.ts","lineNumber":258,"sourceCode":"  }\n\n  // file:// path: validate against safe-dirs and allow; otherwise defer to http(s) logic.\n  if (parsed.protocol === 'file:') {\n    // Reject non-empty non-localhost hosts (UNC / network paths).\n    if (parsed.host !== '' && parsed.host.toLowerCase() !== 'localhost') {\n      throw new Error(\n        `Unsupported file URL host: ${parsed.host}. Use file:///<absolute-path> for local files.`\n      );\n    }\n\n    // Convert URL → filesystem path with proper decoding (handles %20, %2F, etc.)\n    // fileURLToPath strips query + hash; we reattach them after validation so SPA\n    // fixture URLs like file:///tmp/app.html?route=home#login survive intact.\n    let fsPath: string;\n    try {\n      fsPath = fileURLToPath(parsed);\n    } catch (e: any) {\n      throw new Error(`Invalid file URL: ${url} (${e.message})`);\n    }\n\n    // Reject path traversal after decoding — e.g. file:///tmp/safe%2F..%2Fetc/passwd\n    // Note: fileURLToPath doesn't collapse .., so a literal '..' in the decoded path\n    // is suspicious. path.resolve will normalize it; check the result against safe dirs.\n    validateReadPath(fsPath);\n\n    // Return the canonical file:// URL derived from the filesystem path + original\n    // query + hash. This guarantees page.goto() gets a well-formed URL regardless\n    // of input shape while preserving SPA route/query params.\n    return pathToFileURL(fsPath).href + parsed.search + parsed.hash;\n  }\n\n  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n    throw new Error(\n      `Blocked: scheme \"${parsed.protocol}\" is not allowed. Only http:, https:, and file: URLs are permitted.`\n    );\n  }","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/url-validation.ts#L240-L276","documentation":"Wraps a failure from Node's fileURLToPath(parsed) at url-validation.ts:255-258. The URL already parsed successfully and passed the host check, but Node's own file-URL-to-path conversion rejected it. The appended e.message is the underlying Node error, which is the key to the real cause.","triggerScenarios":"A file: URL that new URL() accepts but fileURLToPath rejects: malformed percent-encoding, an incompatible Windows drive-letter form, or a path that cannot be represented on the current OS. Most common on Windows with non-drive roots like file:///usr/local or bad %2F sequences.","commonSituations":"Cross-platform path strings rendered into file URLs naively; string concatenation that produces double-encoded sequences; Windows builds receiving Unix-style roots.","solutions":["Read the appended Node error message first — it names the exact malformation","Construct the URL with pathToFileURL(absolutePath).href rather than string-building it","Pass a plain absolute filesystem path and let the library form the file URL","On Windows, ensure drive letters are present (file:///C:/...) and avoid forward-slash roots"],"exampleFix":"// before (hand-built, fails fileURLToPath on Windows)\nawait goto('file://' + rawPath)\n// after\nimport { pathToFileURL } from 'node:url'\nawait goto(pathToFileURL(rawPath).href)","handlingStrategy":"validation","validationCode":"import { fileURLToPath, pathToFileURL } from 'node:url'\n// Pre-validate that Node can convert the file URL to a path.\nfunction isValidFileUrl(u: string): boolean {\n  try { fileURLToPath(new URL(u)); return true } catch { return false }\n}","typeGuard":"function isConvertibleFileUrl(u: string): u is string {\n  try { fileURLToPath(new URL(u)); return true } catch { return false }\n}","tryCatchPattern":"try {\n  await goto(normalizedUrl)\n} catch (e) {\n  if (e.message.startsWith('Invalid file URL:')) {\n    // rebuild canonically and retry once\n    await goto(pathToFileURL(absPath).href)\n  } else throw e\n}","preventionTips":["Construct file URLs via pathToFileURL so Node can always convert them back","On Windows, ensure drive-letter roots (file:///C:/...)","Avoid hand-rolling percent-encoding in paths"],"tags":["url-validation","file-protocol","node-url","cross-platform","navigation"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}