{"schema":"threatline.record/2","record":{"id":"CVE-2026-107220","aliases":["GHSA-g27h-8qhm-6pff"],"summary":"Excelize: Panic in cellInRange on a worksheet with an empty mergeCell ref","details":"### Summary\n\nA worksheet containing `<mergeCell ref=\"\"/>` 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] >= ref[0] && cell[0] <= ref[2] && cell[1] >= ref[1] && cell[1] <= 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    <mergeCells count=\"1\"><mergeCell ref=\"\"></mergeCell></mergeCells>\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.","categories":["Libraries & packages"],"product":"github.com/xuri/excelize/v2","vendor":"qax-os","ecosystem":"go","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-g27h-8qhm-6pff","source_modified_at":"2026-10-07T20:23:18Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"github.com/xuri/excelize/v2"},"range":">= 2.7.1, < 2.11.1-0.20260820023833-99903a3240e5"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-107220","source_modified_at":"2026-10-10T02:41:14.449Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"excelize","vendor":"qax-os","versions":[{"version":">= 2.7.1, <= 2.11.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-g27h-8qhm-6pff","source_modified_at":"2026-10-07T20:30:07.235710736Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Go","name":"github.com/xuri/excelize/v2","purl":"pkg:golang/github.com/xuri/excelize/v2"},"ranges":[{"events":[{"introduced":"2.7.1"},{"fixed":"2.11.1-0.20260820023833-99903a3240e5"}],"type":"SEMVER"}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-g27h-8qhm-6pff","source_modified_at":"2026-10-07T20:23:18Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"github.com/xuri/excelize/v2"},"version":"2.11.1-0.20260820023833-99903a3240e5"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-g27h-8qhm-6pff","source_modified_at":"2026-10-07T20:30:07.235710736Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Go","name":"github.com/xuri/excelize/v2","purl":"pkg:golang/github.com/xuri/excelize/v2"},"version":"2.11.1-0.20260820023833-99903a3240e5"}],"cvss":6.5,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00247,"percentile":0.15072,"score_date":"2026-10-23"},"published_at":"2026-10-07T18:37:52.712Z","modified_at":"2026-10-10T02:41:14.449Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-g27h-8qhm-6pff","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-07T20:23:18Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-107220","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-10T02:41:14.449Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-g27h-8qhm-6pff","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-07T20:30:07.235710736Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-107220","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/qax-os/excelize/security/advisories/GHSA-g27h-8qhm-6pff","tags":["exploit"]},{"source":"CVE List","url":"https://github.com/qax-os/excelize/pull/2379","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/qax-os/excelize/commit/99903a3240e58a47ff28fb8e05a03bd9d496ec24","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-g27h-8qhm-6pff","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"no","technical_impact":"partial","assessed_at":"2026-10-10T02:39:12.566271Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-10T02:41:14.449Z","content_hash":"a25542ff054875f5706379ab0cf8940197a1b7014ae28325f97c393aa0601e0b"}}
