high Patched

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

  1. Run go list -m github.com/xuri/excelize/v2 in your project to see the installed version, including copies pulled in by other packages.
  2. 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.
  1. Upgrade github.com/xuri/excelize/v2 to 2.11.1-0.20261003002531-6258dcebc4e2 or later, commit the lockfile and redeploy.
  2. 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

Merged from OSV. Last updated 24 Oct 2026. All vulnerabilities · RSS · JSON