{"id":"GHSA-g27h-8qhm-6pff","summary":"Excelize: Panic in cellInRange on a worksheet with an empty mergeCell ref","details":"### Summary\n\nA worksheet containing `\u003cmergeCell ref=\"\"/\u003e` makes `mergeCellsParser` leave the cached rectangle empty, and `cellInRange` then indexes that empty slice without a length check. Every non-streaming cell API panics with `index out of range [0] with length 0` on the first cell read after opening the file.\n\n### Where it is\n\n`cell.go`, `cellInRange`\n\n    func cellInRange(cell, ref []int) bool {\n    \treturn cell[0] \u003e= ref[0] && cell[0] \u003c= ref[2] && cell[1] \u003e= ref[1] && cell[1] \u003c= ref[3]\n    }\n\n`ref` is indexed at four positions with no bounds check.\n\nThe caller that can hand it an empty slice, `mergeCellsParser`\n\n    if ref := ws.MergeCells.Cells[i].Ref; len(ws.MergeCells.Cells[i].rect) == 0 && ref != \"\" {\n    \tif strings.Count(ref, \":\") != 1 {\n    \t\tref += \":\" + ref\n    \t}\n    \trect, err := rangeRefToCoordinates(ref)\n    \tif err != nil {\n    \t\treturn cell, err\n    \t}\n    \t_ = sortCoordinates(rect)\n    \tws.MergeCells.Cells[i].rect = rect\n    }\n    if cellInRange([]int{col, row}, ws.MergeCells.Cells[i].rect) {\n\nThe `ref != \"\"` condition means an empty `ref` skips the block that populates `rect`, so `rect` stays nil. The `cellInRange` call on the next line is unconditional and receives that nil slice.\n\n`Ref` comes straight from `xl/worksheets/sheetN.xml` through `encoding/xml`, so an empty string is entirely attacker-controlled.\n\n### Impact\n\nAny consumer that opens an untrusted workbook and reads a cell panics. The loop scans every merged cell, so any cell reference triggers it, not a specific one. Affected entry points include `GetCellValue`, `GetCellType`, `GetCellFormula`, `SetCellValue` and the in-cell branch of `GetPictures`. The streaming `Rows` and `GetRows` use the SAX path and do not go through this parser, and `GetMergeCells` routes through `Rect()` which errors cleanly, which is probably why this has not surfaced before.\n\nThere is no option or flag involved; opening the file succeeds and the panic fires on the first cell read. Unless the caller wraps the call in `recover()` it takes the process down.\n\nThis is a regression. Commit a34c81e (PR #1500, 2023-03-20) replaced `checkCellInRangeRef`, whose `len(rng) != 2` guard returned cleanly for an empty ref, with the cached-rect fast path above, and the guard did not come along. `git merge-base --is-ancestor` confirms that commit is an ancestor of v2.11.0, so released versions are affected as well as HEAD.\n\n### Proof of concept\n\nExecuted at HEAD.\n\nBuild a minimal xlsx whose `xl/worksheets/sheet1.xml` contains:\n\n    \u003cmergeCells count=\"1\"\u003e\u003cmergeCell ref=\"\"\u003e\u003c/mergeCell\u003e\u003c/mergeCells\u003e\n\nThen:\n\n    f, err := excelize.OpenReader(bytes.NewReader(data))\n    if err != nil { t.Fatal(err) }\n    _, _ = f.GetCellValue(\"Sheet1\", \"A1\")\n\nObserved, running against the repository at HEAD:\n\n    panic: runtime error: index out of range [0] with length 0\n      excelize.cellInRange           cell.go:1691\n      excelize.(*xlsxWorksheet).mergeCellsParser  cell.go:1660\n      excelize.(*File).getCellStringFunc          cell.go:1512\n      excelize.(*File).GetCellValue               cell.go:72\n\n`OpenReader` itself returns no error; the file is 1656 bytes. A1, B1 and A2 all reproduce it.\n\nFor context on how targeted this is, I ran a battery of 38 crafted files covering data validation, conditional formatting, cell and row references, number formats, cols, hyperlinks, dimension, shared strings and tables. Only the empty-ref merge cell panicked; everything else returned a clean error. The other parsing paths look well guarded.\n\n### Suggested fix\n\nSkip the entry when the rectangle is empty, before the range test:\n\n    if len(ws.MergeCells.Cells[i].rect) == 0 {\n    \tcontinue\n    }\n\nCredit goes to arpitjain099.","aliases":["CVE-2026-107220"],"modified":"2026-10-07T20:30:07.235710736Z","published":"2026-10-07T20:23:17Z","database_specific":{"cwe_ids":["CWE-125","CWE-129"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-10-07T20:23:17Z","nvd_published_at":null},"references":[{"type":"WEB","url":"https://github.com/qax-os/excelize/security/advisories/GHSA-g27h-8qhm-6pff"},{"type":"WEB","url":"https://github.com/qax-os/excelize/pull/2379"},{"type":"WEB","url":"https://github.com/qax-os/excelize/commit/99903a3240e58a47ff28fb8e05a03bd9d496ec24"},{"type":"PACKAGE","url":"https://github.com/qax-os/excelize"}],"affected":[{"package":{"name":"github.com/xuri/excelize/v2","ecosystem":"Go","purl":"pkg:golang/github.com/xuri/excelize/v2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"2.7.1"},{"fixed":"2.11.1-0.20260820023833-99903a3240e5"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-g27h-8qhm-6pff/GHSA-g27h-8qhm-6pff.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"}]}