high Patched

Excelize Decrypt: unrecoverable panics on malformed OLE/CFB encrypted workbooks

CVE-2026-107214 · GHSA-2j4c-ffch-9f23 · GO-2026-6684 · published 8 Oct 2026

How serious is it

  • Severity: high.
  • No exploit prediction (EPSS) score yet.
  • Not on the CISA list of exploited vulnerabilities.
  • No public exploit in Exploit-DB.

Is your app affected by CVE-2026-107214?

Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.

Who is affected

  • github.com/xuri/excelize/v2 (Go) >= 2.3.1, < 2.11.1-0.20260915055537-22f76f9acb94

How to check

  1. Run go list -m github.com/xuri/excelize/v2 in your project to see the installed version, including copies pulled in by other packages.
  2. Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.

How to fix

Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
  1. Upgrade github.com/xuri/excelize/v2 to 2.11.1-0.20260915055537-22f76f9acb94 or later, commit the lockfile and redeploy.
  2. If a vulnerable version was live, check your logs and rotate the secrets that server could read.

The advisory

### Summary Any file whose first 8 bytes are the OLE compound-file signature (`D0 CF 11 E0 A1 B1 1A E1`) is routed by `OpenFile`/`OpenReader`/`OpenBytes` → `openReaderAt` → `Decrypt`. The version dispatch only guarantees `len(EncryptionInfo) >= 4` before handing attacker-controlled `EncryptionInfo`/`EncryptedPackage` buffers to `standardDecrypt`/`agileDecrypt`, and no callee validates structure. Malformed but version-valid content therefore fails as an **unrecovered runtime panic** instead of an error, terminating the calling process. ### Details All panic classes below were execution-confirmed against pristine master (`ecd99d761fe0`, 2026-09-08). `excelize.go:211-213` maps `Decrypt` *errors* to `ErrWorkbookFileFormat`, but panics bypass that path and kill the process. | # | Malformed input | Panic | Site | |---|---|---|---| | 1 | standard, `len(EncryptionInfo)` 4–11 | slice bounds `[:12]` | `crypt.go:238` | | 2 | standard, attacker-controlled `headerSize` uint32 | slice bounds `[12:12+headerSize]` / fixed-offset header reads | `crypt.go:238-249` | | 3 | standard, verifier remainder < 72 (AES) / 60 (RC4) bytes | slice bounds in `standardEncryptionVerifier` | `crypt.go:282-295` | | 4 | standard, `header.KeySize` = 0xFFFFFFFF | slice bounds `[:536870911]` with capacity 48 | `crypt.go:321` | | 5 | standard, `EncryptedPackage` stream missing/short | slice bounds `[8:0]` | `crypt.go:268` | | 6 | agile, `len(EncryptionInfo)` 4–7 | slice bounds `[8:4]` | `crypt.go:407` | | 7 | agile, valid XML without `<keyEncryptors>` | index out of range `[0]` with length 0 | `crypt.go:416`, `433` | | 8 | agile, `saltValue` decoded length ≠ AES block | `cipher.NewCBCDecrypter: IV length must equal block size` | `crypt.go:425` → `512` | | 9 | any, `keyData blockSize="0"` | integer divide by zero | `crypt.go:539` | Note the asymmetry pinpointing the missing constraint: the agile path already checks `len(EncryptedPackage) >= 8` (`crypt.go:520-523`) but the standard path does not (#5). Existing tests only cover the error paths (short `<4` bytes → `ErrUnknownEncryptMechanism`, bad XML, base64 errors), never these panic paths. ### PoC Standalone programs (public API only, inputs built in memory) were provided to the maintainer by email: `1-decrypt-panic` builds seven malformed CFB containers and shows each panic escaping the public `Decrypt` API plus one end-to-end `OpenReader` crash. All cases print PANIC on master and BLOCKED with the proposed patch. A regression guard proves legitimate decryption is unaffected: a workbook encrypted with the package's own `Encrypt()` still opens through the same code path. (A separate advisory covers the unbounded/negative allocation in `extractPart`.) ### Impact Any service that calls `OpenFile`/`OpenReader`/`OpenBytes` on untrusted input (upload processing, mail scanning, spreadsheet conversion) can be killed remotely and without authentication by a file of ~100 bytes to ~3 KB. No password is required — panics occur during structural/parameter handling before successful decryption. Site variety means filtering one pattern does not help. ### Proposed fix A `recover()` boundary in `Decrypt` mapping any panic to `ErrWorkbookFileFormat` (restores the documented error-routing contract; legitimate standard/agile decryption unaffected). A complete patch has been provided to the maintainer; per-site length validation is recommended as defense in depth.

Check your whole app

Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.

References

Merged from OSV. Last updated 24 Oct 2026. All vulnerabilities · RSS · JSON