zloirock/core-js · error · SyntaxError

Extra bits

Error message

Extra bits

What it means

In Uint8Array.fromBase64 (as polyfilled by core-js), a final base64 chunk of 2 characters encodes 2 bytes but still carries 6 leftover bits of the third byte. With lastChunkHandling 'strict', if those leftover bits are non-zero the input isn't canonical base64, so a SyntaxError 'Extra bits' is thrown. 'loose' mode ignores this.

Source

Thrown at packages/core-js/internals/uint8-from-base64.js:48

  if (chunkLength < 4) {
    chunk += chunkLength === 2 ? 'AA' : 'A';
  }

  var triplet = (alphabet[at(chunk, 0)] << 18)
    + (alphabet[at(chunk, 1)] << 12)
    + (alphabet[at(chunk, 2)] << 6)
    + alphabet[at(chunk, 3)];

  var chunkBytes = [
    (triplet >> 16) & 255,
    (triplet >> 8) & 255,
    triplet & 255
  ];

  if (chunkLength === 2) {
    if (throwOnExtraBits && chunkBytes[1] !== 0) {
      throw new SyntaxError('Extra bits');
    }
    return [chunkBytes[0]];
  }

  if (chunkLength === 3) {
    if (throwOnExtraBits && chunkBytes[2] !== 0) {
      throw new SyntaxError('Extra bits');
    }
    return [chunkBytes[0], chunkBytes[1]];
  }

  return chunkBytes;
};

var writeBytes = function (bytes, elements, written) {
  var elementsLength = elements.length;
  for (var index = 0; index < elementsLength; index++) {
    bytes[written + index] = elements[index];

View on GitHub (pinned to 84e45fba09)

Solutions

  1. Use lastChunkHandling: 'loose' (the default) if you accept non-canonical input.
  2. Re-encode the data at the source with a canonical base64 encoder.
  3. Strip/normalize the final partial chunk before decoding.
  4. Drop 'strict' only if bitwise-exact round-tripping isn't required for security (strict exists to catch tampering).

Example fix

// before
Uint8Array.fromBase64('QR==', { lastChunkHandling: 'strict' }); // SyntaxError: Extra bits
// after
Uint8Array.fromBase64('QQ==', { lastChunkHandling: 'strict' }); // canonical, ok
Defensive patterns

Strategy: validation

Validate before calling

function isCanonicalTail2(b64, alphabet) {
  const m = b64.match(/(..?)$/);
  return m ? true : false; // prefer re-encoding: canonical iff low bits zero
}
// simplest pre-check: re-encode decoded bytes and compare when using strict
const bytes = Uint8Array.fromBase64(b64); // loose
if (btoa(String.fromCharCode(...bytes)) !== b64) throw new Error('non-canonical base64');

Type guard

function isCanonicalBase64(s) {
  try { return btoa(String.fromCharCode(...Uint8Array.fromBase64(s))) === s; }
  catch { return false; }
}

Try / catch

try {
  bytes = Uint8Array.fromBase64(b64, { lastChunkHandling: 'strict' });
} catch (e) {
  if (e instanceof SyntaxError && e.message === 'Extra bits') {
    bytes = Uint8Array.fromBase64(b64); // loose fallback
  } else throw e;
}

Prevention

When it happens

Trigger: `Uint8Array.fromBase64(str, { lastChunkHandling: 'strict' })` where the string ends in a 2-char final chunk (no '==') whose low bits are non-zero, e.g. 'QQ==' is fine but 'QR==' style non-canonical endings throw.

Common situations: Data encoded by a non-canonical or hand-rolled encoder that didn't zero the trailing bits, strings truncated by a regex/split that dropped padding, or switching code from loose/decoder defaults to strict mode.

Related errors


AI-assisted analysis of zloirock/core-js@84e45fba09 (2026-08-30). Data as JSON: /api/errors/90ba4526a8e72141. Report an issue: GitHub.