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

  1. Pass --raw to write the data URL literally without decoding
  2. Fix the page-side function to emit a valid base64 payload
  3. Strip non-base64 characters from the payload before passing through
  4. 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 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

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/08fad82f057442b2. Report an issue: GitHub.