A newly disclosed high-severity vulnerability in the popular Go library Excelize shows how an incomplete security patch can leave a backdoor wide open even after developers think they’ve closed it.
Tracked as CVE-2026-59161, the flaw allows a few bytes of malicious XLSX data to force applications into allocating tens of megabytes of memory, with the potential to scale far higher.
Excelize, maintained by developer xuri, is one of the most widely used Go packages for reading and writing Excel files. Earlier this year, the maintainer patched a related issue, GHSA-h69g (CVE-2026-54063), that allowed attackers to manipulate row numbers to force excessive memory allocation in Excelize’s “checked” worksheet parser.
Excelize Flaw
That fix added a function called checkRowNum(), which rejects row numbers above Excelize’s maximum limit of 1,048,576 (Excel’s own row ceiling). The problem: this validation only lives in the checked parser path, used by functions like checkSheet() and workSheetReader().
Excelize’s streaming reader the code behind the Rows() and GetRows() functions never got the memo. According to security researcher DavidCarliez, who reported the issue via GHSA-q5j5-6p94-4gwc, the streaming path decodes XML directly and assigns row attributes without ever calling checkRowNum().
The proof-of-concept is remarkably small. A worksheet XML file containing just one row entry <row r="2000000"> with a cell that omits its coordinate attribute is enough to trigger the bug.
Because cells without an explicit r coordinate bypass the coordinate-based row check, GetRows() happily fills in 2 million empty rows to bridge the “gap,” then returns success with no error.
In testing, this single-row XLSX file caused:
- A returned slice with exactly 2,000,000 rows
- Roughly 46 MB of memory allocated in about 14–21 milliseconds
- No error returned at all, despite exceeding Excelize’s own row limit
Larger attacker-supplied row numbers scale the allocation proportionally, meaning a file only slightly larger than this PoC could push memory consumption into gigabytes.
This is classified under CWE-400 (Uncontrolled Resource Consumption) and CWE-770 (Allocation of Resources Without Limits or Throttling), both well-known denial-of-service patterns.
Any Go application that accepts user-uploaded spreadsheets and processes them- GetRows() think HR portals, financial reporting tools, or data-import pipelines is potentially exposed. An attacker doesn’t need valid credentials or complex payloads; they just need an upload form and a crafted file a few kilobytes in size.
Because this is a variant of a previously “fixed” vulnerability, it also underscores a recurring lesson in vulnerability management: patches that address one code path don’t automatically protect parallel implementations of the same logic.
As of this report, no fixed version exists. Affected versions include everything up to and including v2.10.1, as well as the current default branch. The researcher’s suggested remediation includes:
- Enforcing
checkRowNum()-equivalent validation immediately after parsing each row’srattribute in the streaming reader - Ensuring
GetRows()propagates row-parsing errors instead of silently continuing - Adding regression tests specifically targeting rows near the 1,048,576 boundary with missing cell coordinates
Until an official patch lands, teams using excelize for untrusted file processing should consider wrapping GetRows() calls with memory/time limits, validating file size and row counts pre-parse, or sandboxing spreadsheet ingestion entirely.