Excelize ANCHORARRAY: mutually-referencing array formulas recurse unboundedly via re-entrant CalcCellValue, causing a fatal stack overflow
CVE-2026-107216 · GHSA-wp2g-vpjj-g53r · GO-2026-6688 · published 8 Oct 2026
How serious is it
- Severity: high.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-107216?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
github.com/xuri/excelize/v2(Go) >= 2.8.1, < 2.11.1-0.20260911060113-ea12859e43c6
How to check
- Run
go list -m github.com/xuri/excelize/v2in your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
github.com/xuri/excelize/v2to 2.11.1-0.20260911060113-ea12859e43c6 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### Summary
`ANCHORARRAY` (calc.go:15137 on current master) evaluates each cell of the referenced spill range by calling the **exported** `CalcCellValue`, which unconditionally constructs a fresh `calcContext` — fresh entry marker, fresh iterations map, full `MaxCalcIterations` budget (calc.go:896-900). The circular-reference control only exists **within one context**: the entry-exclusion marker and per-ref iteration budget are fields of that single context, and completion-based caching (`formulaArgCache`/`calcRawCache`) only ever stores results of evaluations that *finish*.
During a pure cycle no nested evaluation ever finishes, so nothing is ever cached to break the recursion, and each hop re-arms the entire budget. Two ordinary, Excel-legal constructs in an attacker-supplied workbook are enough:
- `A1` (dynamic array formula, ref `A1:A1`): `_xlfn.ANCHORARRAY($B$1)`
- `B1` (dynamic array formula, ref `B1:B1`): `_xlfn.ANCHORARRAY($A$1)`
→ `CalcCellValue → calcCellValue → evalInfixExp → parseReference → cellResolver → … → ANCHORARRAY → CalcCellValue → …` forever, ending in a **fatal, unrecoverable** Go runtime error: `runtime: goroutine stack exceeds …-byte limit / fatal error: stack overflow`. Go stack overflows cannot be recovered — the whole process aborts.
### Details
- The terminating edge the iterations gate is supposed to provide does not exist across contexts: there is no in-flight tracking shared between nested `CalcCellValue` calls.
- Both formulas are ordinary Excel-legal constructs; no exotic XML is required.
- Excelize's own APIs reach formula evaluation on untrusted cells implicitly (e.g. `pivotTable.go:538`, `picture.go:985/1141`, `col.go:892`), so a service that merely adds a pivot table or a picture over such a workbook dies.
- No option value prevents it: `MaxCalcIterations` is irrelevant because every hop gets a fresh budget.
- Measured: a 64 MB stack budget is exhausted in ~0.09 s; the ~1 GB default in ~1–2 s.
### PoC
A standalone program (public API only) was provided to the maintainer by email (`4-anchorarray-recursion`): `NewFile` + `SetCellFormula` with `FormulaOpts{Type: array, Ref: A1:A1 / B1:B1}`, then `CalcCellValue("Sheet1","A1")`. On master `ecd99d761fe0` (2026-09-08) the process aborts with `fatal error: stack overflow`; with the proposed patch the cycle terminates normally (`CYCLE_TERMINATED`) and existing calc tests pass.
### Impact
An attacker ships a workbook containing the two formulas; any service that evaluates a formula over those cells — directly via `CalcCellValue`, or implicitly via `AddPivotTable` / `AddPicture` / auto-fit — aborts. Remote, unauthenticated, process-fatal, no configuration prevents it.
### Proposed fix
Evaluate spill-range cells through the **current** calculation context — `fn.f.cellResolver(fn.ctx, …)` instead of the exported `CalcCellValue` — so the entry check and iterations gate of the running calculation apply. `cellResolver` returns the typed value directly (dropping a string round-trip); an `ArgEmpty → ""` shim preserves the existing `ToNumber` behavior for empty spill cells, and a fresh-context fallback covers the legacy nil-ctx test paths. A complete patch has been provided to the maintainer.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/qax-os/excelize/security/advisories/GHSA-wp2g-vpjj-g53r
- github.com/qax-os/excelize/commit/ea12859e43c64d498ecf839263c5f917391f3316
- nvd.nist.gov/vuln/detail/CVE-2026-107216
- github.com/qax-os/excelize
Merged from OSV. Last updated 24 Oct 2026. All vulnerabilities · RSS · JSON