{"schema":"threatline.record/2","record":{"id":"CVE-2026-107225","aliases":["GHSA-5h23-36rv-pm65"],"summary":"Excelize: GetStyle panics on a negative fillId, borderId or fontId in styles.xml","details":"## Summary\n\n`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.\n\nSame defect class as GHSA-48hm-4h8j-58fg, the negative shared-string index, in a different file. That one was fixed by adding the missing lower bound; these three sites still lack it.\n\n## Where it is\n\n`styles.go`, in `GetStyle`, lines 1683, 1686 and 1689:\n\n```go\nxf := s.CellXfs.Xf[idx]\nif extractStyleCondFuncs[\"fill\"](xf, s) {\n    f.extractFills(s.Fills.Fill[*xf.FillID], s, style)\n}\nif extractStyleCondFuncs[\"border\"](xf, s) {\n    f.extractBorders(s.Borders.Border[*xf.BorderID], s, style)\n}\nif extractStyleCondFuncs[\"font\"](xf, s) {\n    style.Font = extractFont(s.Fonts.Font[*xf.FontID])\n}\n```\n\nThe conditions, at lines 1171 to 1185, bound only the top:\n\n```go\n\"fill\": func(xf xlsxXf, s *xlsxStyleSheet) bool {\n    return (xf.ApplyFill == nil || (xf.ApplyFill != nil && *xf.ApplyFill)) &&\n        xf.FillID != nil && s.Fills != nil &&\n        *xf.FillID < len(s.Fills.Fill)\n},\n```\n\n`*xf.FillID < len(...)` is satisfied by any negative value. `FillID`, `BorderID` and `FontID` are `*int` unmarshalled directly from the `fillId`, `borderId` and `fontId` attributes, so the value is whatever the file says. The border and font conditions have the same shape.\n\nThe correct pattern is already in the same function, six lines above at 1677:\n\n```go\nif idx < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= idx {\n    return style, newInvalidStyleID(idx)\n}\n```\n\nand again at 1783. So the style index itself is guarded on both sides while the three ids it leads to are not.\n\n## Proof of concept\n\nExecuted against `master` at `f98df08`, which is the merge of #2366, so this is current rather than historical. The harness builds a normal styled workbook with excelize, rewrites one attribute of the `cellXfs` entry inside the zip to `-1`, reopens it and calls `GetCellStyle` then `GetStyle`:\n\n```\ncontrol (all >= 0)           ok style=true err=<nil>\nfillId=-1                    PANIC: runtime error: index out of range [-1]\nborderId=-1                  PANIC: runtime error: index out of range [-1]\nfontId=-1                    PANIC: runtime error: index out of range [-1]\n```\n\nThe control confirms the harness reads a valid file correctly, so the three panics are the negative ids rather than a broken fixture. Worth mentioning because my first attempt at this harness patched only `fillId` and appeared to show the other two were fine; they are not, the replacement had simply missed them.\n\n## Impact\n\nAny application that opens an untrusted `.xlsx` and reads cell styling crashes. `GetStyle` is reached through `GetCellStyle` and from the style-copying and rendering paths, so it sits on the ordinary read path. With no `recover()` anywhere in excelize, a CLI or worker exits and a service returns 500 per request unless the caller installed its own recovery middleware.\n\nNo memory corruption and no information disclosure. Availability only.\n\n## Suggested fix\n\nAdd the lower bound to each of the three conditions, matching what line 1677 already does for the style index:\n\n```go\n*xf.FillID >= 0 && *xf.FillID < len(s.Fills.Fill)\n```\n\nand the same for `BorderID` and `FontID`. Three one-line changes in `extractStyleCondFuncs`.","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-5h23-36rv-pm65","source_modified_at":"2026-10-07T20:22:41Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"github.com/xuri/excelize/v2"},"range":">= 2.8.0, < 2.11.1-0.20260731010303-ae2113b410e5"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-107225","source_modified_at":"2026-10-10T02:42:21.546Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"excelize","vendor":"qax-os","versions":[{"status":"affected","version":">= 2.8.0, <= 2.11.0"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-5h23-36rv-pm65","source_modified_at":"2026-10-07T20:30:06.927051800Z","fetched_at":"2026-10-23T18:52:38.922436Z","package":{"name":"github.com/xuri/excelize/v2","ecosystem":"Go","purl":"pkg:golang/github.com/xuri/excelize/v2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"2.8.0"},{"fixed":"2.11.1-0.20260731010303-ae2113b410e5"}]}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-5h23-36rv-pm65","source_modified_at":"2026-10-07T20:22:41Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"github.com/xuri/excelize/v2"},"version":"2.11.1-0.20260731010303-ae2113b410e5"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-5h23-36rv-pm65","source_modified_at":"2026-10-07T20:30:06.927051800Z","fetched_at":"2026-10-23T18:52:38.922436Z","package":{"name":"github.com/xuri/excelize/v2","ecosystem":"Go","purl":"pkg:golang/github.com/xuri/excelize/v2"},"version":"2.11.1-0.20260731010303-ae2113b410e5"}],"cvss":6.5,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00252,"percentile":0.15747,"score_date":"2026-10-23"},"published_at":"2026-10-07T18:55:55.603Z","modified_at":"2026-10-10T02:42:21.546Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-5h23-36rv-pm65","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-07T20:22:41Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-107225","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-10T02:42:21.546Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-5h23-36rv-pm65","fetched_at":"2026-10-23T18:52:38.922436Z","source_modified_at":"2026-10-07T20:30:06.927051800Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-107225","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-5h23-36rv-pm65","tags":["exploit"]},{"source":"CVE List","url":"https://github.com/qax-os/excelize/pull/2367","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/qax-os/excelize/commit/ae2113b410e51f6a141c396a59eda8c42b91bc22","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-5h23-36rv-pm65","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:41:58.921948Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-10T02:42:21.546Z","content_hash":"9bdd741163d15f04ed77c25412f7016f3fcbe5ee88b78e0392b2a41c1928ae1b"}}
