How to decode Base64 safely - cover art

Base64 and encoding 15 min read

How to decode Base64 safely

August 12, 2026 · 15 min read

Decoding Base64 looks harmless until untrusted input crashes your service, blows memory limits, or injects control bytes into downstream systems. Safe decoding means validating charset and length, choosing the right variant (standard vs URL), and never treating decoded bytes as trusted text without further checks.

Security reviews often flag “we Base64-decode user input” without limits. This guide covers practical guardrails for APIs, browser tools, and batch jobs.

What can go wrong

A small encoded string can expand dramatically - attackers abuse that ratio for denial of service. Invalid characters may throw exceptions that become 500 errors if uncaught. Decoded binary pasted into SQL, shells, or HTML without escaping enables injection in the next hop, not in Base64 itself.

Validate before decode

Reject input that is not a multiple of four after padding normalization, or cap maximum string length before decode. Allow only the expected alphabet: A-Za-z0-9+/= for standard, or include -_ for URL-safe.

const STANDARD_B64 = /^[A-Za-z0-9+/]*={0,2}$/;
const MAX_LEN = 1_000_000; // tune per use case

function safeDecode(b64) {
  if (b64.length > MAX_LEN) throw new Error("Input too large");
  if (!STANDARD_B64.test(b64)) throw new Error("Invalid charset");
  return Buffer.from(b64, "base64");
}

UTF-8 and binary handling

Browser atob() returns a “binary string” of Latin-1 code units, not UTF-8 text. For Unicode, decode to bytes first, then run TextDecoder with utf-8. For images and ciphertext, keep data as Uint8Array and never coerce to string.

function base64ToUtf8(b64) {
  const binary = atob(b64);
  const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

Decoding untrusted input

Treat decoded output as untrusted: scan for null bytes if the consumer expects text, enforce size limits after decode, and avoid echoing binary back to clients as interpreted HTML. For user-uploaded “files” in Base64, re-encode server-side only after virus scanning or format validation.

Secrets should be decoded in memory-isolated paths with no logging. If you only need to inspect JWT claims, decode the payload segment without verifying signature only in dev - production must always verify first.

Local vs server decode

Developer tools that decode locally in the browser keep tokens and PII off your servers - a meaningful privacy win. Server-side decode is required for automation, but minimize retention and redact logs.

Prefer maintained libraries over hand-rolled parsers; they handle padding edge cases and constant-time comparisons where needed for crypto formats.

FAQ

Is it safe to decode JWTs client-side?
Decoding for display is fine; trusting claims without signature verification is not. Never make authorization decisions on decode alone.
What maximum size should I allow?
Depends on use case. APIs often cap at 1–10 MB encoded; thumbnails need far less. Always cap decoded size too.
Should I log decoded Base64?
Avoid logging decoded secrets or PII. If you must debug, use redacted previews and short TTL logs.
Why does atob throw InvalidCharacterError?
Illegal characters, wrong alphabet, or incorrect padding. Normalize URL-safe input and fix length mod 4 first.

Related: Base64URL vs Base64

Browse all tools