{"record":{"id":"08fad82f057442b2","repo":"garrytan/gstack","slug":"out-malformed-base64-in-data-url-decode-would","errorCode":null,"errorMessage":"--out: malformed base64 in data URL (decode would corrupt output)","messagePattern":"--out: malformed base64 in data URL \\(decode would corrupt output\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/read-commands.ts","lineNumber":146,"sourceCode":" * otherwise write corrupted bytes. `--raw` forces a literal write even for data URLs.\n *\n * Non-base64 strings are surrogate-sanitized (matching what the stdout egress path\n * did before) and written as UTF-8. Parent directories are created — validateOutputPath\n * gates the location but does not mkdir.\n */\nexport function writeEvalResult(outPath: string, str: string, opts: { raw: boolean }): number {\n  validateOutputPath(outPath);\n  fs.mkdirSync(path.dirname(path.resolve(outPath)), { recursive: true });\n\n  if (!opts.raw && str.startsWith('data:')) {\n    const comma = str.indexOf(',');\n    if (comma !== -1) {\n      const header = str.slice('data:'.length, comma);\n      const tokens = header.split(';').map(t => t.trim().toLowerCase());\n      if (tokens.includes('base64')) {\n        const payload = str.slice(comma + 1).replace(/\\s+/g, '');\n        if (!/^[A-Za-z0-9+/]*={0,2}$/.test(payload)) {\n          throw new Error('--out: malformed base64 in data URL (decode would corrupt output)');\n        }\n        const buf = Buffer.from(payload, 'base64');\n        fs.writeFileSync(outPath, buf);\n        return buf.length;\n      }\n    }\n  }\n\n  const buf = Buffer.from(stripLoneSurrogates(str), 'utf-8');\n  fs.writeFileSync(outPath, buf);\n  return buf.length;\n}\n\n/**\n * Extract clean text from a page (strips script/style/noscript/svg).\n * Exported for DRY reuse in meta-commands (diff).\n */\nexport async function getCleanText(page: Page | Frame): Promise<string> {","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/read-commands.ts#L128-L164","documentation":"Thrown by writeEvalResult when the js/eval result is a data URL with a base64 payload, --raw is NOT set, and the payload contains characters outside the base64 charset [A-Za-z0-9+/=]. The check exists because Buffer.from(payload, 'base64') silently drops invalid characters, which would write a corrupted file with no error. Refusing early prevents silent corruption.","triggerScenarios":"A js/eval expression returns a `data:<type>;...;base64,<payload>` string whose payload has invalid characters, and the user pipes it to --out without --raw. The regex /^[A-Za-z0-9+/]*={0,2}$/ fails.","commonSituations":"A page render function returns a malformed or truncated data URL; URL-encoding artifacts in the payload; a data URL that uses a non-standard encoding prefix; copy-paste truncation mid-payload; the function returned a data: URL but with a charset suffix that leaked into the payload.","solutions":["Pass --raw to write the data URL literally without decoding","Fix the page-side function to emit a valid base64 payload","Strip non-base64 characters from the payload before passing through","Inspect the payload: `node -e \"console.log(process.argv[1].slice(process.argv[1].indexOf(',')+1))\" '<data-url>'`"],"exampleFix":"// before: malformed base64 in the returned data URL\nbrowse js 'render()' --out=img.png            // throws\n\n// after: write the data URL literally, or fix the renderer\nbrowse js 'render()' --out=img.png --raw","handlingStrategy":"validation","validationCode":"function isValidBase64DataUrl(s: string): boolean {\n  if (!s.startsWith('data:')) return true; // not a data URL — no validation needed\n  const comma = s.indexOf(',');\n  if (comma === -1) return true;\n  const header = s.slice('data:'.length, comma);\n  const tokens = header.split(';').map(t => t.trim().toLowerCase());\n  if (!tokens.includes('base64')) return true; // not base64\n  const payload = s.slice(comma + 1).replace(/\\s+/g, '');\n  return /^[A-Za-z0-9+/]*={0,2}$/.test(payload);\n}\n\nif (!isValidBase64DataUrl(result)) {\n  throw new Error('Result is a base64 data URL with invalid characters');\n}","typeGuard":"function isBase64DataUrl(s: string): boolean {\n  if (!s.startsWith('data:')) return false;\n  const comma = s.indexOf(',');\n  if (comma === -1) return false;\n  return s.slice('data:'.length, comma).split(';').map(t => t.trim().toLowerCase()).includes('base64');\n}","tryCatchPattern":"try {\n  writeEvalResult(outPath, str, { raw });\n} catch (e: any) {\n  if (/malformed base64/.test(e.message)) {\n    // fall back to literal write\n    writeEvalResult(outPath, str, { raw: true });\n  } else throw e;\n}","preventionTips":["When the renderer's output is uncertain, pass --raw to write literally and skip decoding","Validate data URL payloads in the page-side function before returning them","Inspect the payload with a hex viewer if decoding fails repeatedly","Watch for URL-encoding artifacts (%, +) that leak into base64 payloads"],"tags":["base64","data-url","corruption-prevention","cli","out-flag"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}