---
id: "CVE-2026-107225"
page: "https://infrastack.ai/live/CVE-2026-107225"
kind: "vulnerability"
aliases: ["CVE-2026-107225", "GHSA-5h23-36rv-pm65", "GO-2026-6674"]
title: "Excelize: GetStyle panics on a negative fillId, borderId or fontId in styles.xml"
severity: "medium"
cvss: 6.5
epss: 0.0027
exploited: false
status: "open"
category: "package"
products: [{"name": "github.com/xuri/excelize/v2", "fixed": ["2.11.1-0.20260731010303-ae2113b410e5"], "affected": ">= 2.8.0, < 2.11.1-0.20260731010303-ae2113b410e5", "ecosystem": "go"}, {"name": "excelize", "fixed": [], "affected": ">= 2.8.0, <= 2.11.0", "ecosystem": "vendor"}]
ecosystems: ["go", "vendor"]
tags: ["cwe-129", "cwe-20", "excelize", "go", "package", "spreadsheet-parsing"]
not_affected_if: ["no-untrusted-xlsx", "trusted-input-only"]
published_at: "2026-10-07T19:17:35+00:00"
first_seen_at: "2026-11-06T00:09:02+00:00"
updated_at: "2026-11-06T00:19:08+00:00"
sources: [{"source": "govuln", "url": "https://pkg.go.dev/vuln/GO-2026-6674", "first_seen_at": "2026-11-06T00:09:02+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-107225", "first_seen_at": "2026-11-06T00:19:01+00:00"}, {"source": "ghsa", "url": "https://github.com/advisories/GHSA-5h23-36rv-pm65", "first_seen_at": "2026-11-06T00:19:08+00:00"}]
exploit_sources: [{"type": "epss", "score": 0.0027, "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-107225"}]
---

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

Link for people: https://infrastack.ai/live/CVE-2026-107225 (this .md is for agents)

The Excelize Go library panics in GetStyle when a spreadsheet's styles.xml contains a negative fillId, borderId or fontId. An attacker who supplies a crafted .xlsx file can crash a program that reads it, causing a denial of service.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- go:github.com/xuri/excelize/v2 affected >= 2.8.0, < 2.11.1-0.20260731010303-ae2113b410e5 fixed 2.11.1-0.20260731010303-ae2113b410e5
- vendor:excelize affected >= 2.8.0, <= 2.11.0 fixed no fixed version listed

## How to check

- Check the version of `github.com/xuri/excelize/v2` in `go.mod` or with `go list -m github.com/xuri/excelize/v2`
- Look for code that opens untrusted .xlsx files with Excelize and calls GetStyle

## Fix

- `github.com/xuri/excelize/v2` `2.11.1-0.20260731010303-ae2113b410e5`

## Timeline

- 2026-11-06T00:09:02+00:00 govuln https://pkg.go.dev/vuln/GO-2026-6674
- 2026-11-06T00:19:01+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-107225
- 2026-11-06T00:19:08+00:00 ghsa https://github.com/advisories/GHSA-5h23-36rv-pm65

## Documents

- [advisory] Vendor advisory: github.com https://github.com/qax-os/excelize/security/advisories/GHSA-5h23-36rv-pm65
- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/qax-os/excelize/security-advisories/GHSA-5h23-36rv-pm65
- [fix] Patch: github.com https://github.com/qax-os/excelize/commit/ae2113b410e51f6a141c396a59eda8c42b91bc22
- [fix] Patch: github.com https://github.com/qax-os/excelize/pull/2367

## From the sources

## Summary

`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 different file. That one was fixed by adding the missing lower bound; these three sites still lack it.

## Where it is

`styles.go`, in `GetStyle`, lines 1683, 1686 and 1689:

```go
xf := s.CellXfs.Xf[idx]
if extractStyleCondFuncs["fill"](xf, s) {
    f.extractFills(s.Fills.Fill[*xf.FillID], s, style)
}
if extractStyleCondFuncs["border"](xf, s) {
    f.extractBorders(s.Borders.Border[*xf.BorderID], s, style)
}
if extractStyleCondFuncs["font"](xf, s) {
    style.Font = extractFont(s.Fonts.Font[*xf.FontID])
}
```

The conditions, at lines 1171 to 1185, bound only the top:

```go
"fill": func(xf xlsxXf, s *xlsxStyleSheet) bool {
    return (xf.ApplyFill == nil || (xf.ApplyFill != nil && *xf.ApplyFill)) &&
        xf.FillID != nil && s.Fills != nil &&
        *xf.FillID < len(s.Fills.Fill)
},
```

`*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.

The correct pattern is already in the same function, six lines above at 1677:

```go
if idx < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= idx {
    return style, newInvalidStyleID(idx)
}
```

and again at 1783. So the style index itself is guarded on both sides while the three ids it leads to are not.

## Proof of concept

Executed 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`:

```
control (all >= 0)           ok style=true err=<nil>
fillId=-1                    PANIC: runtime error: index out of range [-1]
borderId=-1                  PANIC: runtime error: index out of range [-1]
fontId=-1                    PANIC: runtime error: index out of range [-1]
```

The 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.

## Impact

Any 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.

No memory corruption and no information disclosure. Availability only.

## Suggested fix

Add the lower bound to each of the three conditions, matching what line 1677 already does for the style index:

```go
*xf.FillID >= 0 && *xf.FillID < len(s.Fills.Fill)
```

and the same for `BorderID` and `FontID`. Three one-line changes in `extractStyleCondFuncs`.

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
