{"id":"CVE-2026-107220","kind":"vulnerability","aliases":["CVE-2026-107220","GHSA-g27h-8qhm-6pff","GO-2026-6680"],"related":[],"title":"Excelize: Panic in cellInRange on a worksheet with an empty mergeCell ref","description":"### 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.","summary":"Excelize, a Go library for reading and writing Excel files, panics in cellInRange when a worksheet contains a mergeCell with an empty ref. A panic crashes the process that is handling the file, which can cause denial of service.","severity":"medium","cvss":6.5,"epss":0.00264,"exploited":false,"exploited_at":null,"products":[{"name":"github.com/xuri/excelize/v2","fixed":["2.11.1-0.20260820023833-99903a3240e5"],"affected":">= 2.7.1, < 2.11.1-0.20260820023833-99903a3240e5","ecosystem":"go"},{"name":"excelize","fixed":[],"affected":">= 2.7.1, <= 2.11.0","ecosystem":"vendor"}],"ecosystems":["go","vendor"],"tags":["cwe-125","cwe-129","excelize","go","package","spreadsheet-parsing"],"not_affected_if":["no-xlsx-parsing","trusted-input-only"],"how_to_check":"- Check whether the module `github.com/xuri/excelize/v2` appears in `go.mod` and at what version.\n- Run `go list -m github.com/xuri/excelize/v2` in the project to see the resolved version.","fix":"- Move `github.com/xuri/excelize/v2` to `2.11.1-0.20260820023833-99903a3240e5` or later.","status":"open","published_at":"2026-10-07T19:17:34+00:00","first_seen_at":"2026-11-06T00:09:03+00:00","triaged_at":"2026-11-06T00:10:46+00:00","updated_at":"2026-11-06T00:19:08+00:00","category":"package","confidence":0.6,"triage":{"at":"2026-11-06T00:10:46Z","model":"claude-haiku-5-5","output":{"fix":"- Move `github.com/xuri/excelize/v2` to `2.11.1-0.20260820023833-99903a3240e5` or later.","reach":"unknown","title":"Excelize: crafted spreadsheet with empty merge ref crashes Go program","impact":"low","summary":"Excelize, a Go library for reading and writing Excel files, panics in cellInRange when a worksheet contains a mergeCell with an empty ref. A panic crashes the process that is handling the file, which can cause denial of service.","audience":"developers","category":"package","nicknames":[],"who_cares":["go","excelize","spreadsheet-parsing"],"confidence":0.6,"fix_status":"available","needs_auth":"unknown","worst_case":"Process panics and crashes, causing denial of service","analyst_note":"","how_to_check":"- Check whether the module `github.com/xuri/excelize/v2` appears in `go.mod` and at what version.\n- Run `go list -m github.com/xuri/excelize/v2` in the project to see the resolved version.","plain_action":"","needs_analyst":false,"not_affected_if":["trusted-input-only","no-xlsx-parsing"],"who_is_affected":"Applications that use Excelize to open or parse untrusted or user-supplied .xlsx files are exposed, since a malformed worksheet can trigger the panic. Programs that only generate spreadsheets from trusted data, or never parse external files, are unlikely to be reached.","user_interaction":"unknown","needs_special_setup":"unknown","severity_if_unknown":"low"},"cost_usd":0.000697,"input_tokens":4264,"output_tokens":541,"severity_before":"unknown"},"analyst_status":null,"analyst_note":null,"search":"'-03':383C '-107220':15A,26A '-125':32B '-129':34B '-20':384C '-2026':14A,22A,25A '-6680':23A '/xuri/excelize/v2':29A '0':114C,117C,140C,142C,144C,184C,478C,481C,586C '1':148C,150C,152C,189C '1500':381C '1656':504C '2':146C,390C '2023':382C '3':154C '38':525C '6pff':20A '8qhm':19A 'a1':463C,506C 'a2':509C 'a34c81e':379C 'affect':292C,432C 'along':412C 'ancestor':419C,425C 'api':107C 'arpitjain099':591C 'attack':261C 'attacker-control':260C 'b1':507C 'base':416C 'batteri':523C 'block':225C 'bool':137C 'bound':163C 'branch':305C 'build':443C 'byte':505C 'bytes.newreader':454C 'cach':88C,401C 'cached-rect':400C 'call':235C,366C 'caller':166C,363C 'caus':76B 'cell':106C,121C,134C,139C,143C,147C,151C,200C,274C,281C,284C,304C,359C,533C,552C 'cell.go':130C 'cell.go:1512':491C 'cell.go:1660':487C 'cell.go:1691':483C 'cell.go:72':495C 'cellinrang':4A,53B,92C,131C,133C,209C,234C 'check':101C,164C 'checkcellinrangeref':386C 'clean':331C,393C,558C 'col':211C,539C 'come':248C,411C 'commit':378C,422C 'concept':439C 'condit':218C,531C 'confirm':420C 'consum':265C 'contain':57B,83C,449C 'context':514C 'continu':587C 'control':262C 'cover':528C 'craft':526C 'crash':66B 'credit':588C 'cve':13A,24A 'cwe':31B,33B 'data':455C,529C 'denial':77B 'dimens':541C 'els':555C 'empti':10A,62B,90C,96C,172C,221C,256C,396C,549C,576C 'empty-ref':548C 'encoding/xml':253C 'entir':259C 'entri':293C,571C 'err':193C,197C,201C,452C,457C,460C 'error':330C,473C,500C,559C 'everi':102C,279C 'everyth':554C 'excel':1A,35B,41B,49B,484C,488C,492C 'excelize':30A 'excelize.cellinrange':482C 'excelize.openreader':453C 'execut':440C 'f':451C 'f.getcellvalue':461C 'fast':403C 'file':50B,73B,126C,350C,489C,493C,502C,527C 'fire':355C 'first':120C,358C 'fix':568C 'flag':346C 'format':532C,538C 'four':159C 'func':132C 'g27h':18A 'getcellformula':298C 'getcellstringfunc':490C 'getcelltyp':297C 'getcellvalu':296C,494C 'getmergecel':325C 'getpictur':307C 'getrow':312C 'ghsa':17A 'ghsa-g27h-8qhm-6pff':16A 'git':413C 'github.com':28A 'github.com/xuri/excelize/v2':27A 'go':21A,36B,43B,320C 'goe':589C 'guard':391C,408C,566C 'hand':169C 'handl':71B 'head':436C,442C,470C 'hyperlink':540C 'impact':263C 'in-cel':302C 'includ':295C 'index':94C,110C,157C,474C 'int':136C,210C 'involv':347C 'is-ancestor':417C 'leav':86C 'len':180C,388C,582C 'length':100C,116C,480C 'librari':44B 'line':239C 'look':564C 'loop':277C 'make':84C 'mean':219C 'merg':280C,415C,551C 'merge-bas':414C 'mergecel':11A,59B 'mergecellspars':85C,174C,486C 'minim':445C 'next':238C 'nil':198C,232C,245C,458C 'non':104C 'non-stream':103C 'number':537C 'observ':464C 'one':291C 'open':124C,267C,348C 'openread':496C 'option':344C 'packag':37B 'panic':2A,51B,65B,108C,275C,354C,471C 'panick':553C 'pars':40B,562C 'parser':323C 'path':316C,404C,563C 'point':294C 'popul':227C 'posit':160C 'pr':380C 'probabl':334C 'process':68B,372C 'proof':437C 'ran':521C 'rang':113C,477C,579C 'rangereftocoordin':194C 'read':46B,122C,272C,360C 'receiv':243C 'recov':368C 'rect':183C,192C,203C,206C,207C,215C,228C,230C,328C,402C,585C 'rectangl':89C,574C 'ref':12A,63B,135C,141C,145C,149C,153C,155C,176C,179C,185C,188C,190C,191C,195C,217C,222C,247C,397C,550C 'refer':285C,536C 'regress':377C 'releas':429C 'replac':385C 'repositori':468C 'reproduc':511C 'return':138C,199C,392C,498C,556C 'rng':389C 'rout':326C 'row':212C,310C,535C 'run':465C 'runtim':472C 'sax':315C 'scan':278C 'servic':79B 'setcellvalu':299C 'share':542C 'sheet1':462C 'skip':223C,569C 'slice':97C,173C,246C 'sortcoordin':202C 'specif':290C 'spreadsheet':39B 'spreadsheet-pars':38B 'stay':231C 'straight':249C 'stream':105C,309C 'string':257C,543C 'strings.count':187C 'succeed':351C 'suggest':567C 'summari':80C 'surfac':339C 't.fatal':459C 'tabl':545C 'take':370C 'target':517C 'test':580C 'trigger':286C 'uncondit':241C 'unless':361C 'untrust':269C 'use':313C 'v2.11.0':427C 'valid':530C 'version':430C 'well':434C,565C 'whose':387C,447C 'without':98C 'workbook':270C 'worksheet':7A,56B,82C 'wrap':364C 'write':48B 'ws.mergecells.cells':177C,181C,204C,213C,583C 'xl/worksheets/sheet1.xml':448C 'xl/worksheets/sheetn.xml':251C 'xlsx':446C 'xlsxworksheet':485C","nicknames":[],"mitigations":[],"who_affected":"Applications that use Excelize to open or parse untrusted or user-supplied .xlsx files are exposed, since a malformed worksheet can trigger the panic. Programs that only generate spreadsheets from trusted data, or never parse external files, are unlikely to be reached.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["excelize","githubcomxuriexcelizev2"],"ptokens":["com","excelize","github","v2","xuri"],"labels":["cwe125","cwe129","excelize","go","medium","package","spreadsheetparsing","vendor"],"campaign_type":null,"first_public_at":"2026-08-20T02:37:17+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"govuln","external_id":"GO-2026-6680","url":"https://pkg.go.dev/vuln/GO-2026-6680","severity":null,"published_at":"2026-11-05T22:53:46+00:00","modified_at":"2026-11-05T22:53:46+00:00","first_seen_at":"2026-11-06T00:09:03+00:00","fetched_at":"2026-11-06T00:09:03+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-107220","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107220","severity":null,"published_at":"2026-10-07T19:17:34+00:00","modified_at":"2026-10-13T14:23:03+00:00","first_seen_at":"2026-11-06T00:19:01+00:00","fetched_at":"2026-11-06T00:19:01+00:00","note":null},{"source":"ghsa","external_id":"GHSA-g27h-8qhm-6pff","url":"https://github.com/advisories/GHSA-g27h-8qhm-6pff","severity":"medium","published_at":"2026-10-07T20:23:17+00:00","modified_at":"2026-10-07T20:23:18+00:00","first_seen_at":"2026-11-06T00:19:08+00:00","fetched_at":"2026-11-06T00:19:08+00:00","note":null}],"documents":[{"id":81824,"url":"https://github.com/qax-os/excelize/commit/99903a3240e58a47ff28fb8e05a03bd9d496ec24","title":"Patch: github.com","kind":"other","source":"nvd","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":81825,"url":"https://github.com/qax-os/excelize/pull/2379","title":"Patch: github.com","kind":"other","source":"nvd","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":81826,"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-g27h-8qhm-6pff","title":"Vendor advisory: github.com","kind":"vendor","source":"nvd","publisher":"vendor","text":null,"published_at":"2026-08-20T02:37:17+00:00","relation":"advisory","confidence":null,"note":"Vendor advisory: github.com"},{"id":81922,"url":"https://api.github.com/repos/qax-os/excelize/security-advisories/GHSA-g27h-8qhm-6pff","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"}],"exploit_sources":[{"type":"epss","score":0.00264,"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-107220"}],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}