garrytan/gstack · error · Error
--out: malformed base64 in data URL (decode would corrupt ou
Error message
--out: malformed base64 in data URL (decode would corrupt output)
What it means
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.
Source
Thrown at browse/src/read-commands.ts:146
* otherwise write corrupted bytes. `--raw` forces a literal write even for data URLs.
*
* Non-base64 strings are surrogate-sanitized (matching what the stdout egress path
* did before) and written as UTF-8. Parent directories are created — validateOutputPath
* gates the location but does not mkdir.
*/
export function writeEvalResult(outPath: string, str: string, opts: { raw: boolean }): number {
validateOutputPath(outPath);
fs.mkdirSync(path.dirname(path.resolve(outPath)), { recursive: true });
if (!opts.raw && str.startsWith('data:')) {
const comma = str.indexOf(',');
if (comma !== -1) {
const header = str.slice('data:'.length, comma);
const tokens = header.split(';').map(t => t.trim().toLowerCase());
if (tokens.includes('base64')) {
const payload = str.slice(comma + 1).replace(/\s+/g, '');
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(payload)) {
throw new Error('--out: malformed base64 in data URL (decode would corrupt output)');
}
const buf = Buffer.from(payload, 'base64');
fs.writeFileSync(outPath, buf);
return buf.length;
}
}
}
const buf = Buffer.from(stripLoneSurrogates(str), 'utf-8');
fs.writeFileSync(outPath, buf);
return buf.length;
}
/**
* Extract clean text from a page (strips script/style/noscript/svg).
* Exported for DRY reuse in meta-commands (diff).
*/
export async function getCleanText(page: Page | Frame): Promise<string> {View on GitHub (pinned to 94993f7401)
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>'`
Example fix
// before: malformed base64 in the returned data URL browse js 'render()' --out=img.png // throws // after: write the data URL literally, or fix the renderer browse js 'render()' --out=img.png --raw
Defensive patterns
Strategy: validation
Validate before calling
function isValidBase64DataUrl(s: string): boolean {
if (!s.startsWith('data:')) return true; // not a data URL — no validation needed
const comma = s.indexOf(',');
if (comma === -1) return true;
const header = s.slice('data:'.length, comma);
const tokens = header.split(';').map(t => t.trim().toLowerCase());
if (!tokens.includes('base64')) return true; // not base64
const payload = s.slice(comma + 1).replace(/\s+/g, '');
return /^[A-Za-z0-9+/]*={0,2}$/.test(payload);
}
if (!isValidBase64DataUrl(result)) {
throw new Error('Result is a base64 data URL with invalid characters');
} Type guard
function isBase64DataUrl(s: string): boolean {
if (!s.startsWith('data:')) return false;
const comma = s.indexOf(',');
if (comma === -1) return false;
return s.slice('data:'.length, comma).split(';').map(t => t.trim().toLowerCase()).includes('base64');
} Try / catch
try {
writeEvalResult(outPath, str, { raw });
} catch (e: any) {
if (/malformed base64/.test(e.message)) {
// fall back to literal write
writeEvalResult(outPath, str, { raw: true });
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- --out specified more than once
- --out requires a file path
- pdf: --from-file requires a path
- Unknown pdf flag: ${a}
- pdf: --margins is mutex with --margin-top/--margin-right/--m
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/08fad82f057442b2.
Report an issue: GitHub.