Excelize: Unchecked pivot-cache field index in extractPivotTableFields causes unrecoverable panic
CVE-2026-107211 · GHSA-mx22-3794-2vpv · GO-2026-6695 · 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-107211?
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.20261003002531-6258dcebc4e2
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.20261003002531-6258dcebc4e2 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
extractPivotTableFields builds `order := pc.getPivotCacheFieldsName()` from xl/pivotCache/pivotCacheDefinitionN.xml's <cacheFields> list, then indexes it with values taken from a *separately parsed* xl/pivotTables/pivotTableN.xml part with zero cross-validation: `order[field.Fld]` where `field.Fld` is a raw attacker-controlled `int` from the `<dataField fld="N">` attribute, and `order[fieldIdx]` where fieldIdx is a loop index over `pt.PivotFields.PivotField` that need not match the cache's field count. I executed two independent PoCs against the current HEAD: (1) trimmed the pivot cache's <cacheFields> to count=0 while leaving the pivot table's 2 pivotFields untouched -> `runtime error: index out of range [0] with length 0` at pivotTable.go:1431; (2) left the 2 cache fields completely intact and only changed one attribute, `fld="1"` -> `fld="99999"`, in xl/pivotTables/pivotTable1.xml -> `runtime error: index out of range [99999] with length 2` at pivotTable.go:1443. Both stack traces confirmed via non-recovered `go test` runs: extractPivotTableFields -> getPivotTable -> GetPivotTables.
### Reachability / who can trigger this
Unauthenticated: any application that opens an untrusted .xlsx containing a pivot table and calls the public `File.GetPivotTables(sheet)` API. excelize has no recover() anywhere, so this crashes the host process (CLI/worker) or surfaces as an unhandled 500 in a request-scoped-recover HTTP service. Deterministic, single small crafted file, no user interaction beyond the service's normal open-and-read flow.
### Proof of Concept / Reproduction
**Method:**
1) Verified source at HEAD (commit e81f0500, 2026-09-27, freshly cloned/up to date; `git status` clean except the new PoC files I added): read pivotTable.go:1425-1454 and confirmed verbatim: `order := pc.getPivotCacheFieldsName()` (1428), loop `for fieldIdx, field := range pt.PivotFields.PivotField` indexing `order[fieldIdx]` at axisRow/axisCol/axisPage (1431/1434/1437), and `Data: order[field.Fld]` (1443) inside the `pt.DataFields.DataField` loop -- zero bounds checks anywhere. Confirmed xmlPivotTable.go:271 `Fld int `xml:"fld,attr"`` on xlsxDataField (raw attacker-controlled int, no validation) and the sibling Fld field at line 252. Ran `go build ./...` -- compiles clean. Both citations are accurate.
2) Found the repo already contained an untracked PoC test file from earlier work (pivot_fld_poc_test.go) implementing this exact finding via a byte-level zip-tamper technique (build a legitimate workbook with excelize's own public AddPivotTable API, then hex-edit one raw XML part inside the saved .xlsx's zip bytes -- not a hypothetical reimplementation, uses the real unmodified excelize package). Did not just trust it: ran it myself with `go test -run 'TestPivotTableDataFieldFldIndexPanic|TestPivotTableDataFieldFldAttributeOutOfRangePanic' -v .` and observed real PASS output with exact matching panic strings.
3) For stronger, independent evidence beyond a recover-wrapped test, I wrote my own standalone non-test Go program from scratch, cmd/pivotcrash/main.go (module github.com/xuri/excelize/v2, go1.27.0 toolchain), that imports the real compiled excelize package (no reimplementation) with two subcommands: `gen`/`gen-trim` (attacker: build a legit pivot-table workbook via the public API, then rezip with one XML part tampered -- either dataField fld="1"->fld="99999", or the pivot cache's <cacheFields count="2"> collapsed to count="0" -- writing the crafted .xlsx to disk) and `run` (victim: a fresh OS process that does exactly what any consumer service does -- `excelize.OpenFile(path)` then `f.GetPivotTables("Sheet1")` -- with zero recover() anywhere in the file or the call chain). Executed as four separate `go run` process invocations: gen, run, gen-trim, run.
**Evidence:**
Test run (pre-existing PoC file, re-executed by me, both PASS):
`--- PASS: TestPivotTableDataFieldFldIndexPanic (0.00s)` logging `CONFIRMED: GetPivotTables panicked ... runtime error: index out of range [0] with length 0`
`--- PASS: TestPivotTableDataFieldFldAttributeOutOfRangePanic (0.00s)` logging `CONFIRMED: GetPivotTables panicked ... runtime error: index out of range [99999] with length 2`
`ok github.com/xuri/excelize/v2 0.114s`
Standalone process-crash run #1 (`go run ./cmd/pivotcrash run pivotcrash_malicious.xlsx`, fld="99999" tamper, cache untouched with 2 fields):
[victim] file opened successfully, workbook parsed with no errors
[victim] now calling f.GetPivotTables("Sheet1") on the untrusted workbook...
panic: runtime error: index out of range [99999] with length 2
goroutine 1 [running]:
github.com/xuri/excelize/v2.(*File).extractPivotTableFields(...)
.../pivotTable.go:1443 +0xb9b
github.com/xuri/excelize/v2.(*File).getPivotTable(...)
.../pivotTable.go:1379 +0x7f6
github.com/xuri/excelize/v2.(*File).GetPivotTables(...)
.../pivotTable.go:1278 +0x2f7
main.runVictim(...) / main.main() -> exit status 2
Standalone process-crash run #2 (`go run ./cmd/pivotcrash run pivotcrash_malicious_trim.xlsx`, cacheFields trimmed to count=0, pivot table's 2 pivotFields untouched):
panic: runtime error: index out of range [0] with length 0
goroutine 1 [running]:
github.com/xuri/excelize/v2.(*File).extractPivotTableFields(...)
.../pivotTable.go:1431 +0xc15
github.com/xuri/excelize/v2.(*File).getPivotTable(...)
.../pivotTable.go:1379 +0x7f6
github.com/xuri/excelize/v2.(*File).GetPivotTables(...)
.../pivotTable.go:1278 +0x2f7
main.runVictim(...) / main.main() -> exit status 2
In both standalone runs the "GetPivotTables RETURNED NORMALLY" print statement that follows the call was never reached -- the OS process itself terminated via an unrecovered Go panic (exit status 2), for a crafted .xlsx that `excelize.OpenFile` accepted without any error. Both stack traces terminate at the exact claimed sink lines (pivotTable.go:1443 and :1431) via the exact claimed call chain (extractPivotTableFields -> getPivotTable -> GetPivotTables). This is a r…
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-mx22-3794-2vpv
- github.com/qax-os/excelize/commit/6258dcebc4e2a2aed985c38a08098dfd908521d1
- github.com/qax-os/excelize/pull/2435
- nvd.nist.gov/vuln/detail/CVE-2026-107211
- github.com/qax-os/excelize
Merged from OSV. Last updated 24 Oct 2026. All vulnerabilities · RSS · JSON