The image-size npm package is a widely used image-dimension parser that has had multiple denial-of-service vulnerabilities in 2025, all caused by infinite loops in its format parsers when handling malformed files. If your Node.js service calls image-size on user-uploaded files, a single crafted buffer can pin the event loop at 100% CPU forever, taking down every request on that process. This post walks through the vulnerability history, the affected versions, and the patterns that make the library safe to keep using.
What image-size does and why it is everywhere
image-size (the image-size package on npm) reads the header bytes of an image and returns its width, height, and detected type without decoding the full file. It supports a long list of formats: PNG, JPEG, GIF, WebP, AVIF, HEIF, JXL, ICNS, TIFF, and more. Because it is small, dependency-free, and fast, it shows up in image CDNs, upload validators, static site generators, and framework internals. Next.js, for example, has used it to size local images at build time.
That popularity is exactly why its parser bugs matter. A dimension probe is usually the first thing a backend does with an untrusted upload, before any sandboxing or resizing. If the probe itself can hang, the attacker never needs to reach your image pipeline at all.
The April 2025 findBox advisory
In April 2025, the maintainers published GitHub advisory GHSA-m5qc-5hw7-8vg7 for a denial of service via infinite loop during image processing. The root cause was the shared findBox routine used by the box-structured formats (JXL, HEIF, JP2). ISO BMFF-style images are laid out as a sequence of boxes, each starting with a size field. When a crafted image declared a box with size 0, the parser's offset never advanced, and the while loop walking the box list never terminated.
The advisory was rated high severity (CVSS 8.7) because exploitation requires no authentication and no user interaction: you send one image, the process hangs. Affected ranges were >= 1.1.0, < 1.2.1 and >= 2.0.0, < 2.0.2, with fixes released in 1.2.1 and 2.0.2.
The September 2025 follow-up: CVE-2025-71319 and CVE-2025-71330
Fuzzing the fixed versions turned up more of the same bug class. In September 2025, security researcher Joshua Rogers disclosed two further infinite loops affecting image-size through version 2.0.2:
- CVE-2025-71319 — a crafted image containing a recognized box type with a zero-valued size field again causes the offset not to advance, this time surviving in the JXL and HEIF parsing paths.
- CVE-2025-71330 — the ICNS parser reads per-entry length fields, and a crafted entry with length
0never advancesimageOffset, so the loop condition stays true indefinitely.
Both are the same fundamental pattern: a length or size field read from attacker-controlled bytes is used to advance a cursor, and zero is never rejected. Because Node.js runs JavaScript on a single-threaded event loop, a synchronous infinite loop is not a slow request. It is a permanently dead worker: no timers fire, no other connections are accepted, and health checks fail until the process is killed.
If you consume npm image-size anywhere in a request path, check your resolved version against your vulnerability feed rather than assuming the April patch covered everything. An SCA tool such as Safeguard will flag the affected ranges transitively, which matters here because image-size is far more often a transitive dependency (pulled in by a framework or an image plugin) than a direct one.
Why this bug class keeps recurring in header parsers
Dimension sniffers are hand-rolled binary parsers, and they accumulate the classic failure modes of that genre:
- Trusting length fields. Any offset advanced by
offset += sizeneedssize > 0validated first. All three 2025 issues violated this. - Wide format surface. Every supported format is another parser, and formats like HEIF and JXL have nested box structures that are much harder to bound than a PNG IHDR chunk.
- Synchronous execution. The library is deliberately sync-friendly, which is convenient and also means there is no natural cancellation point.
None of this makes image-size a bad library. The maintainers responded to the April report with fixes across both major lines. It does mean that "reads only the header" is not the same as "safe on hostile input."
How to keep using image-size safely
Practical hardening, roughly in order of value:
Pin and update. Get onto at least 1.2.1 / 2.0.2, and track the advisory feed for the September CVEs, which were disclosed against 2.0.2. Renovate or Dependabot plus a lockfile makes this a one-line PR instead of an incident.
Restrict the formats you accept. Most applications only need JPEG, PNG, WebP, and GIF. The 2025 bugs lived in JXL, HEIF, JP2, and ICNS parsers that most users never intentionally exercise. Validate magic bytes against an allowlist before handing the buffer to the library:
const ALLOWED = [
[0xff, 0xd8, 0xff], // JPEG
[0x89, 0x50, 0x4e, 0x47], // PNG
[0x47, 0x49, 0x46, 0x38], // GIF
];
function allowedType(buf) {
return ALLOWED.some((magic) => magic.every((b, i) => buf[i] === b));
}
Isolate the parse. For genuinely untrusted input, run the probe in a worker_threads worker with a hard timeout, and terminate the worker if it does not answer:
const { Worker } = require("node:worker_threads");
function sizeWithTimeout(buf, ms = 500) {
return new Promise((resolve, reject) => {
const w = new Worker("./size-worker.js", { workerData: buf });
const t = setTimeout(() => { w.terminate(); reject(new Error("parse timeout")); }, ms);
w.once("message", (dims) => { clearTimeout(t); resolve(dims); });
w.once("error", (e) => { clearTimeout(t); reject(e); });
});
}
Killing a worker is safe; there is no way to interrupt an infinite loop on the main thread.
Cap input size. image-size only needs the leading bytes. Passing the first 64 KB instead of a whole multi-megabyte upload shrinks the parser's playground and your memory bill at the same time.
Where this fits in your dependency review process
The 2025 image-size story is a good template for reviewing any small utility package that touches untrusted bytes: check whether the same bug class has recurred (here, yes, three times in one year), check how quickly maintainers patched (here, quickly for the April report), and check whether your usage exposes the vulnerable surface at all (server-side upload handling does; sizing your own build-time assets mostly does not). We cover this style of assessment in more depth in the Safeguard Academy dependency-review material, and the SCA product page explains how reachability context changes the priority of a finding like this.
The pragmatic conclusion: keep the library, patch promptly, gate the formats, and never run a binary parser on hostile input without a kill switch.
FAQ
Is image-size safe to use in 2025?
Yes, with current versions and sensible input handling. The known 2025 issues are denial-of-service infinite loops, not code execution. Update to at least 1.2.1 or 2.0.2, watch for fixes addressing CVE-2025-71319 and CVE-2025-71330, and avoid feeding raw untrusted buffers to it without a format allowlist or a worker timeout.
Which versions of image-size are affected by the 2025 DoS bugs?
The April 2025 advisory (GHSA-m5qc-5hw7-8vg7) affected 1.1.0 through 1.2.0 and 2.0.0 through 2.0.1, fixed in 1.2.1 and 2.0.2. The September 2025 CVEs (CVE-2025-71319, CVE-2025-71330) were reported as affecting image-size through 2.0.2.
Can an infinite-loop bug really take down a whole Node.js service?
Yes. Node.js executes JavaScript on a single-threaded event loop, so one synchronous infinite loop blocks every request, timer, and callback on that process until it is killed. With a small worker pool, a handful of crafted uploads can exhaust the whole service.
How do I find out if image-size is in my dependency tree?
Run npm ls image-size to see where it enters your tree, since it is usually transitive. A software composition analysis scanner will additionally match your resolved version against the advisory ranges and flag it automatically.