Advisories for Golang/Github.com/Xuri/Excelize package

2026

Excelize: Unchecked pivot-cache field index in extractPivotTableFields causes unrecoverable panic

extractPivotTableFields builds order := pc.getPivotCacheFieldsName() from xl/pivotCache/pivotCacheDefinitionN.xml's 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 to count=0 …

Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days

A worksheet whose <row r="…"> number is far past Excel's 1,048,576-row limit makes File.GetRows and the Rows iterator loop once for every missing row. The row limit is checked in Rows.Next, but not in Rows.Columns, which also reads <row> elements. A 1.5 KB file with <row r="231999999999940"> after an ordinary first row keeps GetRows busy for an estimated 11 days (about 4 ns per missing row), using one CPU core …

Excelize: Nil-pointer dereference in GetSlicers when a worksheet has extLst present but no drawing element

GetSlicers guards only if ws.ExtLst == nil { return } (slicer.go:816-818) and then immediately dereferences ws.Drawing.RID with no nil check on ws.Drawing itself. Drawing and ExtLst are two independently optional child elements of in xl/worksheets/sheetN.xml, populated separately by encoding/xml depending on whether each tag is present. I executed a PoC: took a plain NewFile() workbook (no drawing, no slicer), saved it, then injected a bare <extLst></extLst> immediately before </worksheet> in …

Excelize: extractPart allocates attacker-controlled, unbounded and negative-sized buffers from CFB directory entries: remote panic / OOM DoS

extractPart allocates directly from the mscfb directory-entry size with no clamping (crypt.go:198, same missing bound at crypt.go:204): buf := make([]byte, entry.Size) entry.Size is the raw streamSize field from the attacker-controlled CFB directory entry (mscfb v1.0.8 fixFile, file.go:109-120: full uint64 → int64 for major version 4, low uint32 for v3). mscfb only detects short sector chains later, inside File.Read (file.go:485-489 "emergency brake") — i.e. after the allocation. The zip path already …

Excelize Decrypt: unrecoverable panics on malformed OLE/CFB encrypted workbooks

Any file whose first 8 bytes are the OLE compound-file signature (D0 CF 11 E0 A1 B1 1A E1) is routed by OpenFile/OpenReader/OpenBytes → openReaderAt → Decrypt. The version dispatch only guarantees len(EncryptionInfo) >= 4 before handing attacker-controlled EncryptionInfo/EncryptedPackage buffers to standardDecrypt/agileDecrypt, and no callee validates structure. Malformed but version-valid content therefore fails as an unrecovered runtime panic instead of an error, terminating the calling process.

Excelize ANCHORARRAY: mutually-referencing array formulas recurse unboundedly via re-entrant CalcCellValue, causing a fatal stack overflow

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 …

Excelize: Unbounded spinCount in agile decryption burns CPU during OpenFile

Opening a file whose first eight bytes are the OLE magic number sends excelize down the decryption path whether or not the caller set a password, or supports encrypted workbooks at all. openReaderAt (excelize.go:198-215) branches on the header alone, and agileDecrypt calls convertPasswdToKey before the verifier hash is checked, so the key-derivation loop runs spinCount times regardless. spinCount (crypt.go:98) is a plain int filled by a bare xml.Unmarshal of the …

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 …

Excelize: RIGHT() on supplementary-plane text slices with a negative index and panics

leftRight (calc.go:14236) tests the length with countUTF16String, which counts a rune above U+FFFF as 2. The RIGHT branch on 14241 then slices []rune(text) at utf8.RuneCountInString(text)-numChars, which counts it as 1. For text with N such runes the two measures are 2N and N, so any numChars between them passes the guard and gives a negative index. The numChars < 0 check at 14216 does not help, since the value that …

Excelize: GetStyle panics on a negative fillId, borderId or fontId in styles.xml

File.GetStyle indexes the fill, border and font tables with values taken straight out of xl/styles.xml, and the conditions gating those lookups check only the upper bound. A workbook whose cellXfs entry carries fillId="-1", borderId="-1" or fontId="-1" reaches a negative slice index and panics. excelize has no recover(), so the panic leaves GetStyle and takes the calling process with it. Same defect class as GHSA-48hm-4h8j-58fg, the negative shared-string index, in a …

Excelize: GetConditionalFormats indexes conditional-formatting rule sub-elements with no length or nil check

GetConditionalFormats reads sub-elements of a <cfRule> straight out of xl/worksheets/sheetN.xml and indexes them without checking length, and in one case without checking for nil. Three rule types are affected: cellIs, dataBar and colorScale. A workbook with a rule that is missing a child a real Excel file would always have panics the call.

Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader

A Zip64 uncompressed-size of 2^63 casts to a negative int64 that bypasses the unzip-size guard and reaches make([]byte, 0, negativeCap) A Zip64 uncompressed-size in the range [2^63, 2^64) casts to a negative int64 in zip.File.FileInfo().Size(), and ReadZipReader does signed arithmetic on that value, so the total-decompression guard is bypassed and the negative size reaches make([]byte, 0, size) in readFile. A 159-byte crafted file panics OpenFile and OpenReader with runtime error: …

Excelize: a row whose earlier cell has a higher column reference than its last cell panics index out of range on almost every worksheet read API

Affected versions and vulnerable location Confirmed at ae2113b (current HEAD). Sink: rows.go:967 ws.SheetData.Row[rowIdx].C[colNum-1] = *colData inside checkRow. checkRow computes lastCol from the column of the last cell in document order (rows.go:940), allocates targetList of that length, then re-scatters every source cell into C[colNum-1]. Root cause The slice is sized from the last cell's column, but cells are not required to be column-sorted in the XML. A cell that appears earlier …

Excelize ColumnNameToNumber: int64 overflow yields an out-of-domain coordinate with nil error, causing negative slice index panic on r="0" rows

ColumnNameToNumber (lib.go:220-237) accumulates the bijective base-26 value of a column name in an int64 with only an upper-bound check (col > MaxColumns) applied after the loop and no overflow detection. A 14-letter column name whose true value is 3·2⁶⁴ — e.g. VGWQHXLSDVIKWV — wraps to col = 0 and passes the guard, so CellNameToCoordinates("VGWQHXLSDVIKWV1") returns (col=0, row=1, err=nil). When normalizing a r="0" row, checkSheetR0 (excelize.go:417-443, called from checkSheet at excelize.go:405) …

Excelize ColumnNameToNumber: int64 overflow yields an out-of-domain coordinate with nil error, causing negative slice index panic on r="0" rows

ColumnNameToNumber (lib.go:220-237) accumulates the bijective base-26 value of a column name in an int64 with only an upper-bound check (col > MaxColumns) applied after the loop and no overflow detection. A 14-letter column name whose true value is 3·2⁶⁴ — e.g. VGWQHXLSDVIKWV — wraps to col = 0 and passes the guard, so CellNameToCoordinates("VGWQHXLSDVIKWV1") returns (col=0, row=1, err=nil). When normalizing a r="0" row, checkSheetR0 (excelize.go:417-443, called from checkSheet at excelize.go:405) …

Excelize: Streaming GetRows row-bound bypass causes attacker-controlled allocation

Excelize's prior row-bound fix for GHSA-h69g / CVE-2026-54063 protects the checked worksheet parser, but the streaming worksheet reader used by Rows and GetRows does not enforce the same TotalRows bound on the row r attribute. A small XLSX file can set a row number above Excelize's maximum row (1048576) and omit the cell coordinate. GetRows then appends empty rows up to the attacker-controlled row index and returns success. This was …

Excelize: Streaming GetRows row-bound bypass causes attacker-controlled allocation

Excelize's prior row-bound fix for GHSA-h69g / CVE-2026-54063 protects the checked worksheet parser, but the streaming worksheet reader used by Rows and GetRows does not enforce the same TotalRows bound on the row r attribute. A small XLSX file can set a row number above Excelize's maximum row (1048576) and omit the cell coordinate. GetRows then appends empty rows up to the attacker-controlled row index and returns success. This was …

Excelize: Negative shared-string index causes panic in GetCellValue and GetRows

Excelize parses shared-string cell values with strconv.Atoi and checks only the upper bound before indexing the shared string slice. If an XLSX file contains a shared-string cell with <v>-1</v>, the parsed index is negative. The upper-bound check still passes (len(sharedStrings) > -1), and Excelize indexes sharedStrings[-1], causing a runtime panic. This was reproduced on the current default branch commit 1213a8bd7c5ab360554603ac5c995ccaf6eb4314 and the latest release tag v2.10.1 (5ad5ab3af0054c55bdce09f1530085600e9f2e45). The issue is …

Excelize: Negative shared-string index causes panic in GetCellValue and GetRows

Excelize parses shared-string cell values with strconv.Atoi and checks only the upper bound before indexing the shared string slice. If an XLSX file contains a shared-string cell with <v>-1</v>, the parsed index is negative. The upper-bound check still passes (len(sharedStrings) > -1), and Excelize indexes sharedStrings[-1], causing a runtime panic. This was reproduced on the current default branch commit 1213a8bd7c5ab360554603ac5c995ccaf6eb4314 and the latest release tag v2.10.1 (5ad5ab3af0054c55bdce09f1530085600e9f2e45). The issue is …

Excelize: Unbounded Row Index Allocation in Worksheet Parser (checkSheet OOM/Panic DoS)

The checkSheet() function in github.com/xuri/excelize/v2 uses an attacker-controlled <row r="N"> XML attribute value directly as the length argument to make([]xlsxRow, row) without validating it against the Excel row limit (TotalRows = 1,048,576). A specially crafted XLSX file can trigger two denial-of-service variants: (A) an out-of-memory process kill when r=2147483647 forces a ~16 GB allocation attempt, and (B) a runtime panic via out-of-bounds slice indexing when r=-1. Any service that opens …