Summary
Excelize: Unbounded <col max> attribute is loaded with no MaxColumns check and expanded per-column by flatCols(), so any column mutator hangs or OOMs the process
The max attribute of a <col> element in xl/worksheets/sheetN.xml is read verbatim on open with no check against MaxColumns, and flatCols() then walks Min..Max doing a deepcopy.Copy and append per iteration. Executed: max="300000", only about 18x past the real 16384 limit, cost 46.2 s of CPU and 122 MB on a single SetColWidth call, and the growth is linear in max up to 2^31-1.
Confirmed at ae2113b (HEAD at the time of audit).
Details
col.go:547, flatCols(), with the two loops at :549 and :564:
for i := col.Min; i <= col.Max; i++ {
...
for i := column.Min; i <= column.Max; i++ { ... fc = append(fc, deepcopy.Copy(...)) }
column.Min and column.Max are plain int attributes on xmlWorksheet.go:279-280, bound straight from the file. MaxColumns is 16384 (templates.go:176) and nothing in the parse path compares against it.
The contrast that makes this an oversight rather than a design decision is inside the callers themselves. SetColWidth validates its own column argument through parseColRange, which clamps to MaxColumns. But flatCols iterates ws.Cols.Col, the file-loaded slice, so the caller's validated argument never constrains the loop. The crafted column expands no matter which column the caller touches.
The row axis has the guard this axis is missing: GHSA-h69g-9hx6-f3v4 capped checkSheet's row allocation at TotalRows, and GHSA-q5j5-6p94-4gwc capped streaming Rows.Next. There is no column-axis equivalent.
Public entry points that flatten the existing columns: SetColWidth (col.go:498 into :534), SetColStyle (col.go:431 into :477), SetColVisible (col.go:282 into :313), SetColOutlineLevel (col.go:376 into :407).
PoC
Executed in-package: crafted a real xlsx, rezipped with an injected <cols> block before <sheetData>, opened it with OpenReader, then called SetColWidth.
<cols><col min="1" max="2147483647" width="9"/></cols>
f.SetColWidth("Sheet1", "A", "A", 12)
Measured:
max="300000": 46.2 s CPU, 122 MB allocated,ws.Cols.Colgrew to 300000 entries, from one call.max="5000000": did not complete in 110 s, killed by the test timeout.
Growth is linear in max. At max=2147483647 that is roughly two billion xlsxCol allocations, which is hundreds of gigabytes and in practice a permanent hang ending in OOM.
Why this is not GHSA-h69g-9hx6-f3v4 or GHSA-q5j5-6p94-4gwc
Both of those are row-axis allocation bounds, and both fixes cap at TotalRows. Neither touched <col> parsing or flatCols. This is the same weakness class on the column axis, and it is arguably worse than the two that were fixed, because those had a cap that was bypassed while this one has no cap at all.
Impact
Any service that opens an untrusted spreadsheet and calls a column mutator. The input is a few dozen bytes of XML inside an otherwise ordinary workbook, no authentication is involved beyond whatever gates the upload, and the process is either wedged for minutes or killed by the OOM reaper. Availability only; no read or write primitive here.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Validate col.Min and col.Max against MinColumns/MaxColumns when parsing the <col> element, or at the top of flatCols, returning ErrColumnNumber for out-of-range values. That mirrors what checkRowNum already does on the row axis.
Frequently Asked Questions
- What is CVE-2026-107223? CVE-2026-107223 is a high-severity security vulnerability in github.com/xuri/excelize/v2 (go), affecting versions >= 2.1.0, < 2.11.1-0.20260807015645-a54c578af309. It is fixed in 2.11.1-0.20260807015645-a54c578af309.
- Which versions of github.com/xuri/excelize/v2 are affected by CVE-2026-107223? github.com/xuri/excelize/v2 (go) versions >= 2.1.0, < 2.11.1-0.20260807015645-a54c578af309 is affected.
- Is there a fix for CVE-2026-107223? Yes. CVE-2026-107223 is fixed in 2.11.1-0.20260807015645-a54c578af309. Upgrade to this version or later.
- Is CVE-2026-107223 exploitable, and should I be worried? Whether CVE-2026-107223 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-107223 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-107223? Upgrade
github.com/xuri/excelize/v2to 2.11.1-0.20260807015645-a54c578af309 or later.