CVE-2026-59161: Excelize Streaming GetRows Row-bound Bypass Causes Vulnerability

HIGH
Advisory Identifier: GHSA-q5j5-6p94-4gwc
CWE-400 CWE-770
Published
Advisory Summary

CVE-2026-59161 is a HIGH streaming getrows row-bound bypass causes vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting Excelize libraries in the Go ecosystem. The issue involves streaming getrows row-bound bypass causes attacker-controlled allocation. Remediation is available by upgrading affected packages to version 2.11.0.

CVE-2026-59161 at a Glance

CVE Identifier CVE-2026-59161
GHSA Identifier GHSA-q5j5-6p94-4gwc
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-400: Uncontrolled Resource Consumption
Affected Product Excelize
Package Ecosystem Go
Published Date
Last Updated

How to Fix CVE-2026-59161

To remediate this vulnerability, upgrade all affected Excelize dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/xuri/excelize/v2 fixed: 2.11.0
Upgrade github.com/xuri/excelize/v2 to 2.11.0
github.com/xuri/excelize fixed: Latest secure release
Upgrade github.com/xuri/excelize to Latest secure release
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/xuri/excelize/v2

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-59161, inspect your project dependencies for Excelize:

inspect dependencies for github.com/xuri/excelize/v2

Analyze the terminal output to locate any version of Excelize in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.11.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In Excelize, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

# Streaming GetRows row-bound bypass causes attacker-controlled allocation ## Summary Excelize's prior row-bound fix for GHSA-h69g / CVE-2026-54063 protects the checked worksheet parser, but the streaming worksheet reader used by `Rows` and `GetRows` does not enforce the same `TotalRows` bound on the row `r` attribute. A small XLSX file can set a row number above Excelize's maximum row (`1048576`) and omit the cell coordinate. `GetRows` then appends empty rows up to the attacker-controlled row index and returns success. This was reproduced on the current default branch commit `1213a8bd7c5ab360554603ac5c995ccaf6eb4314` and the latest release tag `v2.10.1` (`5ad5ab3af0054c55bdce09f1530085600e9f2e45`). ## Affected package - Package: `github.com/xuri/excelize/v2` - Tested affected versions: current default branch at `1213a8bd7c5ab360554603ac5c995ccaf6eb4314`, and release `v2.10.1` - Fixed version: none known at the time of this report ## Impact An attacker who can provide an XLSX file to an application that calls `GetRows` can cause memory and CPU usage to scale with an attacker-controlled row number, even though the file itself is tiny. This is an availability issue and appears to be an incomplete coverage variant of the GHSA-h69g row-index allocation class. In the conservative PoC, `row r="2000000"` returned a `[][]string` with length 2,000,000 and allocated about 46 MB. Larger row numbers scale the allocation further. ## Root cause The checked parser path validates row numbers: - `excelize.go`: `checkRowNum(r int)` rejects negative rows and rows greater than `TotalRows`. - `excelize.go`: `checkSheet()` calls `checkRowNum(r.R)` before allocating sheet rows. - `workSheetReader()` invokes `checkSheet()` / `checkRow()` before returning a cached worksheet. The streaming path does not use that checked parser: - `rows.go`: `Rows(sheet)` opens an XML decoder directly. - `Rows.Next()` accepts the row `r` attribute and assigns it to the iterator's current row without applying `checkRowNum()`. - `Rows.Columns()` also assigns row `r` to the iterator state without applying `checkRowNum()`. - `GetRows()` appends empty row slices for the gap between the previous row and the current row. Because a cell without an `r` coordinate can still contain a value, the worksheet can avoid cell-coordinate row validation while still causing `GetRows()` to materialize rows up to the out-of-range row number. ## Minimal worksheet payload ```xml <?xml version="1.0" encoding="UTF-8"?> <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> <sheetData> <row r="2000000"><c t="s"><v>0</v></c></row> </sheetData> </worksheet> ``` The workbook also contains a normal `sharedStrings.xml` with one string (`ok`). ## Reproduction A minimal Go harness creates the XLSX in memory and calls `GetRows("Sheet1")`: ```go rows, err := f.GetRows("Sheet1") fmt.Println("rows_len:", len(rows)) if len(rows) > 0 { fmt.Println("last_row:", rows[len(rows)-1]) } fmt.Printf("returned error: %T %v\n", err, err) ``` Observed output on current default branch commit `1213a8bd7c5ab360554603ac5c995ccaf6eb4314`: ```text == streaming GetRows row r=2000000 cell without r == rows_len: 2000000 last_row: [ok] returned error: <nil> <nil> elapsed=21ms alloc_delta=46MB ``` Observed output on latest release tag `v2.10.1`: ```text == streaming GetRows row r=2000000 cell without r == rows_len: 2000000 last_row: [ok] returned error: <nil> <nil> elapsed=14ms alloc_delta=46MB ``` A control using the checked parser with `row r="1048577"` and `c r="A1048577"` correctly returns `row number exceeds maximum limit`, confirming this report is about inconsistent enforcement in the streaming path rather than a missing global constant. ## Expected behavior `Rows` / `GetRows` should reject row numbers greater than `TotalRows` with the same error behavior as the checked parser path. ## Suggested remediation - Apply the same row-bound validation in the streaming reader immediately after parsing a row `r` attribute. - Preserve and return row parsing errors from `GetRows()` instead of silently continuing or returning only `Rows.Close()` errors. - Add regression tests for `GetRows()` on a worksheet containing `row r="1048577"` with a cell value but no cell `r` coordinate.

CVE-2026-59161 Affected Excelize Versions

2 component(s)
Fixed Versions:
2.11.0
Product / Package Ecosystem Affected Versions Fixed Version
github.com/xuri/excelize/v2
Go
< 2.11.0
2.11.0
github.com/xuri/excelize
Go
≤ 1.4.0

References

Frequently Asked Questions

What is CVE-2026-59161?
CVE-2026-59161 is a security vulnerability in Excelize involving Streaming GetRows row-bound bypass causes attacker-controlled allocation under CWE-400: Uncontrolled Resource Consumption.
Which Excelize versions are affected?
Affected versions include releases prior to 2.11.0 across affected packages: github.com/xuri/excelize/v2, github.com/xuri/excelize.
What version fixes CVE-2026-59161?
Upgrading to 2.11.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/xuri/excelize/v2 affected?
Yes, github.com/xuri/excelize/v2 is affected in releases prior to 2.11.0. Upgrade your dependency to 2.11.0 or later.
Is github.com/xuri/excelize affected?
Yes, github.com/xuri/excelize is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What CWE is associated with CVE-2026-59161?
CVE-2026-59161 is classified under CWE-400: Uncontrolled Resource Consumption (The product does not properly control the allocation and maintenance of a limited resource.).
How can I check Go dependencies for CVE-2026-59161?
Run the dependency tree command: 'inspect dependencies for github.com/xuri/excelize/v2'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-q5j5-6p94-4gwc",
  "modified" : "2026-09-10T15:09:09Z",
  "published" : "2026-09-10T15:09:09Z",
  "aliases" : [ "CVE-2026-59161" ],
  "summary" : "Excelize: Streaming GetRows row-bound bypass causes attacker-controlled allocation",
  "details" : "# Streaming GetRows row-bound bypass causes attacker-controlled allocation\n\n## Summary\n\nExcelize's prior row-bound fix for GHSA-h69g / CVE-2026-54063 protects the checked worksheet parser, but the streaming worksheet reader used by `Rows` and `GetRows` does not enforce the same `TotalRows` bound on the row `r` attribute. A small XLSX file can set a row number above Excelize's maximum row (`1048576`) and omit the cell coordinate. `GetRows` then appends empty rows up to the attacker-controlled row index and returns success.\n\nThis was reproduced on the current default branch commit `1213a8bd7c5ab360554603ac5c995ccaf6eb4314` and the latest release tag `v2.10.1` (`5ad5ab3af0054c55bdce09f1530085600e9f2e45`).\n\n## Affected package\n\n- Package: `github.com/xuri/excelize/v2`\n- Tested affected versions: current default branch at `1213a8bd7c5ab360554603ac5c995ccaf6eb4314`, and release `v2.10.1`\n- Fixed version: none known at the time of this report\n\n## Impact\n\nAn attacker who can provide an XLSX file to an application that calls `GetRows` can cause memory and CPU usage to scale with an attacker-controlled row number, even though the file itself is tiny. This is an availability issue and appears to be an incomplete coverage variant of the GHSA-h69g row-index allocation class.\n\nIn the conservative PoC, `row r=\"2000000\"` returned a `[][]string` with length 2,000,000 and allocated about 46 MB. Larger row numbers scale the allocation further.\n\n## Root cause\n\nThe checked parser path validates row numbers:\n\n- `excelize.go`: `checkRowNum(r int)` rejects negative rows and rows greater than `TotalRows`.\n- `excelize.go`: `checkSheet()` calls `checkRowNum(r.R)` before allocating sheet rows.\n- `workSheetReader()` invokes `checkSheet()` / `checkRow()` before returning a cached worksheet.\n\nThe streaming path does not use that checked parser:\n\n- `rows.go`: `Rows(sheet)` opens an XML decoder directly.\n- `Rows.Next()` accepts the row `r` attribute and assigns it to the iterator's current row without applying `checkRowNum()`.\n- `Rows.Columns()` also assigns row `r` to the iterator state without applying `checkRowNum()`.\n- `GetRows()` appends empty row slices for the gap between the previous row and the current row.\n\nBecause a cell without an `r` coordinate can still contain a value, the worksheet can avoid cell-coordinate row validation while still causing `GetRows()` to materialize rows up to the out-of-range row number.\n\n## Minimal worksheet payload\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">\n  <sheetData>\n    <row r=\"2000000\"><c t=\"s\"><v>0</v></c></row>\n  </sheetData>\n</worksheet>\n```\n\nThe workbook also contains a normal `sharedStrings.xml` with one string (`ok`).\n\n## Reproduction\n\nA minimal Go harness creates the XLSX in memory and calls `GetRows(\"Sheet1\")`:\n\n```go\nrows, err := f.GetRows(\"Sheet1\")\nfmt.Println(\"rows_len:\", len(rows))\nif len(rows) > 0 {\n    fmt.Println(\"last_row:\", rows[len(rows)-1])\n}\nfmt.Printf(\"returned error: %T %v\\n\", err, err)\n```\n\nObserved output on current default branch commit `1213a8bd7c5ab360554603ac5c995ccaf6eb4314`:\n\n```text\n== streaming GetRows row r=2000000 cell without r ==\nrows_len: 2000000\nlast_row: [ok]\nreturned error: <nil> <nil>\nelapsed=21ms alloc_delta=46MB\n```\n\nObserved output on latest release tag `v2.10.1`:\n\n```text\n== streaming GetRows row r=2000000 cell without r ==\nrows_len: 2000000\nlast_row: [ok]\nreturned error: <nil> <nil>\nelapsed=14ms alloc_delta=46MB\n```\n\nA control using the checked parser with `row r=\"1048577\"` and `c r=\"A1048577\"` correctly returns `row number exceeds maximum limit`, confirming this report is about inconsistent enforcement in the streaming path rather than a missing global constant.\n\n## Expected behavior\n\n`Rows` / `GetRows` should reject row numbers greater than `TotalRows` with the same error behavior as the checked parser path.\n\n## Suggested remediation\n\n- Apply the same row-bound validation in the streaming reader immediately after parsing a row `r` attribute.\n- Preserve and return row parsing errors from `GetRows()` instead of silently continuing or returning only `Rows.Close()` errors.\n- Add regression tests for `GetRows()` on a worksheet containing `row r=\"1048577\"` with a cell value but no cell `r` coordinate.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/xuri/excelize/v2"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.11.0"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/xuri/excelize"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "last_affected" : "1.4.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/qax-os/excelize/security/advisories/GHSA-q5j5-6p94-4gwc"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-59161"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/qax-os/excelize/pull/2331"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/qax-os/excelize/commit/93f0b3caed37f21ef5079e3259c6c21dcfe68453"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/qax-os/excelize"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/qax-os/excelize/releases/tag/v2.11.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-400", "CWE-770" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-09-10T15:09:09Z",
    "nvd_published_at" : "2026-07-10T17:17:02Z"
  }
}