Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
MEDIUM severity

CVE-2026-107224 — excelize

MEDIUMFix: qax-os/excelize@db93f8d

CVE-2026-107224 is a medium-severity (CVSS 6.5) CWE-190 vulnerability in github.com/xuri/excelize/v2. A fix is available for github.com/xuri/excelize/v2 — see the affected versions and patch details below.

Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader

Also known asGHSA-fw94-4wwp-w8pw
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 22, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-107224.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs22th percentile — riskier than 22% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-107224 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 394,415 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
🐹github.com/xuri/excelize/v2

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

A Zip64 uncompressed-size of 2^63 casts to a negative int64 that bypasses the unzip-size guard and reaches make([]byte, 0, negativeCap)

A Zip64 uncompressed-size in the range [2^63, 2^64) casts to a negative int64 in zip.File.FileInfo().Size(), and ReadZipReader does signed arithmetic on that value, so the total-decompression guard is bypassed and the negative size reaches make([]byte, 0, size) in readFile. A 159-byte crafted file panics OpenFile and OpenReader with runtime error: makeslice: cap out of range.

The guard, at lib.go:44-46 on HEAD ae2113b:

fileSize := v.FileInfo().Size()
unzipSize += fileSize
if unzipSize > f.options.UnzipSizeLimit {
	return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit)
}

FileInfo().Size() returns int64(UncompressedSize64). UncompressedSize64 is read from the Zip64 extended-information extra record in the central directory and is fully attacker controlled, so any value with the top bit set arrives as a negative int64. unzipSize then goes negative and the comparison against UnzipSizeLimit (default 1000 << 24, templates.go:193) is false no matter how many such entries the archive contains.

The same negative value also fails the two stream-to-temp checks at lib.go:53 and lib.go:64 (fileSize > f.options.UnzipXMLSizeLimit), so the entry is not diverted to a temp file and control reaches readFile:

// lib.go:150
dat := make([]byte, 0, file.FileInfo().Size())

make with a negative capacity panics. There is no recover() in any non-test file in the library, so the panic leaves the public API and takes down the calling goroutine.

The asymmetry inside this same function is the clearest way to see it: unzipToTemp (lib.go:83) handles an entry with a plain io.Copy and never pre-allocates from the declared size, so it is indifferent to whatever the header claims. Only the pre-allocating branch trusts that number, and it trusts it after a signed comparison that a wrapped value walks straight through.

Reproduction, executed against HEAD ae2113b (go1.26.5)

A 159-byte archive was built with one stored entry named xl/worksheets/sheet1.xml: truthful 1-byte local header, central-directory 32-bit uncompressed size set to the 0xFFFFFFFF Zip64 sentinel, and a Zip64 extra record (tag 0x0001, data size 8) declaring UncompressedSize64 = 0x8000000000000000.

entry "xl/worksheets/sheet1.xml" FileInfo().Size() = -9223372036854775808
excelize.OpenReader(bytes.NewReader(raw))  ->  panic: runtime error: makeslice: cap out of range

Control, the identical archive with UncompressedSize64 = 0x7FFFFFFFFFFFFFFF:

entry "xl/worksheets/sheet1.xml" FileInfo().Size() = 9223372036854775807
excelize.OpenReader(bytes.NewReader(raw))  ->  err = "unzip size exceeds the 16777216000 bytes limit"

The control isolates the defect to the sign flip rather than to size magnitude: the guard behaves correctly for any positive declared size and fails only once the value wraps. archive/zip itself accepts the archive without complaint, so nothing upstream of excelize rejects it.

What I verified and what I did not

I ran both cases above myself and confirmed each cited line at HEAD. I caught the panic with a deliberate recover in my harness so I could print it; nothing in excelize recovers it, which I checked by grepping every non-test .go file. I did not separately run the OpenFile variant, since OpenFile reaches the same ReadZipReader loop, and I did not attempt to turn the bypassed size cap into a separate decompression-bomb primitive.

Attacker model

Any service that opens a spreadsheet it did not produce. The payload is 159 bytes, needs no authentication, and is deterministic.

Suggested fix

Compare against the limit in unsigned space, and bound the allocation independently rather than trusting the header:

if v.UncompressedSize64 > uint64(f.options.UnzipSizeLimit) {
	return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit)
}

and in readFile, reject or clamp a declared size that is negative or larger than the limit before calling make. The same unsigned treatment applies to the two UnzipXMLSizeLimit comparisons, which currently route a wrapped size away from the safe streaming path and into the pre-allocating one.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/xuri/excelize/v2≥ 2.1.0&&< 2.11.1-0.20260805032953-db93f8d89de72.11.1-0.20260805032953-db93f8d89de7go get github.com/xuri/excelize/v2@v2.11.1-0.20260805032953-db93f8d89de7

Affected Products

1 product · 1 configurations
Application
excelizeexcelize
≥ 2.1.0 && ≤ 2.11.0
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/xuri/excelize/v2, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/xuri/excelize/v2 to 2.11.1-0.20260805032953-db93f8d89de7 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-107224 is resolved across your whole dependency graph.

  3. Workarounds

    Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.

Frequently Asked Questions

### Summary A Zip64 uncompressed-size of 2^63 casts to a negative int64 that bypasses the unzip-size guard and reaches make([]byte, 0, negativeCap) A Zip64 uncompressed-size in the range [2^63, 2^64) casts to a negative `int64` in `zip.File.FileInfo().Size()`, and `ReadZipReader` does signed arithmetic on that value, so the total-decompression guard is bypassed and the negative size reaches `make([]byte, 0, size)` in `readFile`. A 159-byte crafted file panics `OpenFile` and `OpenReader` with `runtime error: makeslice: cap out of range`. The guard, at `lib.go:44-46` on HEAD `ae2113b`: ```go
O3 Security · Impact-Aware SCA

Is CVE-2026-107224 in your dependencies?

Find it across Go, including transitive dependencies.

CVE-2026-107224: excelize — Fix Available