Version 1.1.0
@stackline/cfb
Drop-in replacement for cfb 1.2.2: read and write compound files (OLE/CFB), ZIP and MHT containers, with fixes for hangs, ZIP64, CVE-2024-22363 and bugs the original never released
Installation
npm install @stackline/cfb@1.1.0To replace cfb without changing application code:
npm install cfb@npm:@stackline/cfb@1.1.0Node.js: >=0.8. Read the compatibility notes before migrating.
Usage and API
@stackline/cfb
Drop-in replacement for
cfb1.2.2 that ends the endless loops on damaged files, fixes the CVE-2024-22363 regular expression, and reads ZIP64 archives, with identical results everywhere else. Zero dependencies; Node.js 0.8 and newer, browsers, and TypeScript.
Documentation | npm | Issues | Repository
Current package version: 1.1.0
Why this package?
Change one line in
package.json. Keep every result. Stop the hangs.
cfb@1.2.2 reads and writes OLE compound files (XLS, DOC, PPT, MSG, MSI), ZIP
containers, and MIME aggregate documents. It was last published in April 2022.
Since then SheetJS has fixed several bugs in the copy of this code embedded in
xlsx, including the regular expression behind CVE-2024-22363 and ZIP64
support, but never released those fixes to the cfb package. A damaged file
can still make cfb@1.2.2 loop forever.
@stackline/cfb is built for the swap:
- Same results. For intact files, every read result matches
cfb@1.2.2: paths, entry fields, dates, and content bytes. Written CFB and ZIP files are byte-identical, except that names of 32 or more characters are written as 31 characters instead of an invalid name record. - Fixed by default. Damaged files raise errors instead of hanging or
leaking uninitialized memory into content, the MIME parser runs in linear
time, and valid files that
cfb@1.2.2rejects (ZIP64, stored entries with data descriptors) are read. - Verified against the original. A differential harness runs both packages on the SheetJS test corpus and on deterministic corruptions of it, and compares every observable result.
- Same runtimes. No runtime dependencies, the same entry points and
browserfield, and CI on every Node.js line from 0.8 to 24.
Trust and maintenance
- No runtime dependencies.
- Every release is built from the public repository by GitHub Actions and published with npm provenance.
- CI runs the upstream test suite, regression tests for every fix, the differential harness, type checks, a packed-consumer test, and a smoke test on Node.js 0.8.28 through 24.20.0.
- Security reports have a private process in SECURITY.md.
- Release history is recorded in CHANGELOG.md.
Compatibility
| Item | Value |
|---|---|
| Package | @stackline/cfb@1.1.0 |
| Replaces | cfb@1.2.2 and earlier 1.x releases |
| Node.js runtime | >=0.8 |
| Module entry | ./cfb (CommonJS) |
| Browser builds | dist/cfb.js, dist/cfb.min.js, dist/xlscfb.js |
| Type declarations | types/index.d.ts |
| Runtime dependencies | None |
Installation
Replace cfb without changing application code:
npm install cfb@npm:@stackline/cfb
Or install under the package's public name:
npm install @stackline/cfb
In the browser:
<script src="dist/cfb.min.js" type="text/javascript"></script>
The xlscfb.js file is designed to be embedded in
js-xlsx.
Usage
const CFB = require('cfb'); // or require('@stackline/cfb')
const container = CFB.read('report.xls', { type: 'file' });
const workbook = CFB.find(container, 'Workbook');
const data = workbook.content;
Create a ZIP file:
const archive = CFB.utils.cfb_new();
CFB.utils.cfb_add(archive, 'hello.txt', Buffer.from('Hello'));
const bytes = CFB.write(archive, { type: 'buffer', fileType: 'zip' });
What the swap fixes
| Input | cfb@1.2.2 |
@stackline/cfb |
|---|---|---|
| A ZIP entry whose DEFLATE stream is shorter than its declared size | Rest of the content is uninitialized process memory | Rest is zero-filled |
| A FAT chain that loops (js-cfb#11) | Never returns | Throws Cycle detected in FAT chain at sector N |
| A DEFLATE stream that ends early, or has a zero-bit code | Never returns | Throws Unexpected end of DEFLATE stream or Invalid DEFLATE literal/length code |
| A MIME header line with many colons | Quadratic time (CVE-2024-22363 pattern) | Linear time, same matches |
| Directory parent links that loop | RangeError: Invalid string length |
Throws Directory tree cycle at entry N |
| ZIP64 archives (sheetjs#2766) | Throws | Read |
| Stored ZIP entries with a data descriptor | Throws Bad compressed size |
Read |
| An entry listed before its parent storage (js-cfb#15) | Path without its storage | Full path |
| ZIP dates read on the 29th-31st of a month | Can land in the wrong month | Correct date |
| Names of 32 or more characters written to CFB (js-cfb#18) | Invalid name record | 31 characters |
| String content written to ZIP | Throws | Written |
A parsed container passed to read (js-cfb#20) |
TypeError from inside the parser |
TypeError that explains the mistake |
See Compatibility
for the full contract and how it is verified, and
Upstream issues
for every upstream issue, pull request, embedded xlsx fix, advisory, and
fork.
Write without the seed stream
cfb@1.2.2 adds a \u0001Sh33tJ5 stream to every CFB file it writes. Set
noseed to leave it out
(js-cfb#22):
const bytes = CFB.write(container, { type: 'buffer', noseed: true });
Without the option, output is unchanged.
Add many entries
cfb_add rebuilds the directory after every call. For bulk additions, skip
the checks with unsafe and rebuild once with cfb_gc
(js-cfb#2):
const archive = CFB.utils.cfb_new();
for (const [name, content] of entries) {
CFB.utils.cfb_add(archive, `/data/${name}`, content, { unsafe: true });
}
CFB.utils.cfb_gc(archive);
With unsafe, adding a name that already exists creates a duplicate entry.
Replace a stream
cfb_add replaces the content of an existing entry
(js-cfb#21):
const container = CFB.read('report.xls', { type: 'file' });
CFB.utils.cfb_add(container, '/Workbook', updatedBytes);
CFB.writeFile(container, 'report-updated.xls');
API
TypeScript definitions are maintained in types/index.d.ts.
The CFB object exposes the following methods and properties:
CFB.parse(blob) takes a nodejs Buffer or an array of bytes and returns a
parsed representation of the data.
CFB.read(blob, opts) wraps parse.
CFB.find(cfb, path) performs a case-insensitive match for the path (or file
name, if there are no slashes) and returns an entry object or null if not found.
CFB.write(cfb, opts) generates a file based on the container.
CFB.writeFile(cfb, filename, opts) creates a file with the specified name.
Parse Options
CFB.read takes an options argument. opts.type controls the behavior:
type |
expected input |
|---|---|
"base64" |
string: Base64 encoding of the file |
"binary" |
string: binary string (byte n is data.charCodeAt(n)) |
"buffer" |
nodejs Buffer |
"file" |
string: path of file that will be read (nodejs only) |
| (default) | buffer or array of 8-bit unsigned int (byte n is data[n]) |
type: "file" needs the Node.js fs module. In browsers, read the file first
and pass its bytes
(js-cfb#16).
Write Options
CFB.write and CFB.writeFile take an options argument.
opts.type controls the behavior:
type |
output |
|---|---|
"base64" |
string: Base64 encoding of the file |
"binary" |
string: binary string (byte n is data.charCodeAt(n)) |
"buffer" |
nodejs Buffer |
"file" |
string: path of file that will be created (nodejs only) |
| (default) | buffer if available, array of 8-bit unsigned int otherwise |
opts.fileType controls the output file type:
fileType |
output |
|---|---|
'cfb' (default) |
CFB container |
'zip' |
ZIP file |
'mad' |
MIME aggregate document |
opts.compression enables DEFLATE compression for ZIP file type.
opts.noseed leaves the \u0001Sh33tJ5 stream out of CFB output.
Utility Functions
The utility functions are available in the CFB.utils object. Functions that
accept a name argument strictly deal with absolute file names:
.cfb_new(?opts)creates a new container object..cfb_add(cfb, name, ?content, ?opts)adds a new file to thecfb, or replaces the content of an existing one. Set the option{unsafe:true}to skip existence checks (for bulk additions).cfb_del(cfb, name)deletes the specified file.cfb_mov(cfb, old_name, new_name)moves the old file to new path and name.cfb_gc(cfb)rebuilds the directory, for example after bulk additions.use_zlib(require("zlib"))loads a nodejszlibinstance.
By default, the library uses a pure JS inflate/deflate implementation. NodeJS
zlib.InflateRaw exposes the number of bytes read in versions after 8.11.0.
If a supplied zlib does not support the required features, a warning will be
displayed in the console and the pure JS fallback will be used.
Container Object Description
The objects returned by parse and read have the following properties:
.FullPathsis an array of the names of all of the streams (files) and storages (directories) in the container. The paths are properly prefixed from the root entry (so the entries are unique).FileIndexis an array, in the same order as.FullPaths, whose values are objects following the schema:
interface CFBEntry {
name: string; /** Case-sensitive internal name */
type: number; /** 1 = dir, 2 = file, 5 = root ; see [MS-CFB] 2.6.1 */
content: Buffer | number[] | Uint8Array; /** Raw Content */
ct?: Date; /** Creation Time */
mt?: Date; /** Modification Time */
ctype?: String; /** Content-Type (for MAD) */
}
Command-Line Utility
The cfb-cli module ships with a CLI
tool for manipulating and inspecting supported files.
References
MS-CFB: Compound File Binary File Format- ZIP
APPNOTE.TXT: .ZIP File Format Specification - RFC1951: https://www.ietf.org/rfc/rfc1951.txt
- RFC2045: https://www.ietf.org/rfc/rfc2045.txt
- RFC2557: https://www.ietf.org/rfc/rfc2557.txt
Security
Damaged or hostile input raises an error instead of hanging: looping FAT
chains, looping directory links, and truncated or invalid DEFLATE data are
detected, and the MIME header parser runs in linear time. Parsed content never
includes uninitialized memory, which cfb@1.2.2 returns for a DEFLATE stream
shorter than its declared size.
The parser does not cap sizes. A valid archive can still declare a large decompressed size, as with any ZIP reader. Limit input sizes and run untrusted files in a worker or process with resource limits.
See the security policy.
Local Development
Clone the repository and run the following commands from its root:
npm ci
npm test
npm test builds the CommonJS and browser files from bits/, then runs the
upstream API suite, the regression tests, the Node.js 0.8-compatible smoke test,
and the type checks. Source changes belong in bits/; the build regenerates
cfb.js, xlscfb.js, and dist/.
Differential testing
Compare this package with cfb@1.2.2 on any directory of CFB, ZIP, and MIME
files:
npm run test:differential -- <corpus-directory>
npm run test:differential -- <corpus-directory> --mutations 10 --max-size 65536
Each input runs in child processes with time and memory limits. The harness
reports each input as identical, a long-name write difference, newly readable,
or a regression, and exits with 1 on a regression or on a failure that only
this package has. To reproduce a finding, pass its label from the report to
--match and add --save <directory> to keep the corrupted input. See
how compatibility is verified.
Consumer Smoke Test
npm run test:package packs the library and exercises an isolated consumer
using the repository fixture.
Release Checklist
- Update the package version, lockfile, generated version fields, and changelog together.
- Run the development checks above and audit both
npm auditandnpm audit --omit=dev. - Use the GitHub publish workflow
with its
Prodenvironment to publish the exact CI tarball. - Verify public npm bytes, package identity, provenance, and the immutable GitHub release evidence.
License
Apache-2.0. Original copyright notices and upstream attribution are retained.
Please consult the attached LICENSE file for details. All rights not explicitly granted by the Apache 2.0 License are reserved by the Original Author.
@stackline/cfb is an independent project and is not affiliated with or
endorsed by SheetJS LLC.
Credits and original authors
- Original project: js-cfb.
- sheetjs.
- Copyright (C) 2013-present SheetJS LLC.
- Stackline maintenance: Alexandro Paixao Marques and Stackline contributors.
Original copyright, license notices and contributor acknowledgements remain part of this distribution. Stackline maintenance does not replace authorship of the original work.
Community and Links
Use this repository's issue tracker for reproducible bugs and feature requests. Join r/Stackline for examples, usage questions and release discussions.
Compatibility with cfb 1.2.2
Compatibility
@stackline/cfb is a drop-in replacement for cfb@1.2.2. Code that works with
cfb 1.2.2 reads the same containers and writes the same bytes after the swap.
Behavior changes only where cfb@1.2.2 never returns, cannot read a valid
file, or produces an invalid or wrong result.
npm install cfb@npm:@stackline/cfb
Contract
- The API is unchanged:
read,parse,find,write,writeFile,utils, and every option ofcfb@1.2.2. - Read results for intact files are identical:
FullPaths, everyFileIndexfield, entry key order, dates, and content bytes. Damaged input differs only as listed under intentional differences below. - Write results are byte-identical for CFB, ZIP, and ZIP with compression, except for names of 32 or more characters (see below).
- The package keeps the files, entry points,
browserfield, and type declarations ofcfb@1.2.2. - It has no runtime dependencies.
cfb@1.2.2declaredadler-32andcrc-32but embeds the code it uses. - It runs on Node.js 0.8 and newer, as
cfb@1.2.2declares; CI exercises 0.8.28 through 24. CFB.versionreports this package's version,1.1.0, instead of1.2.2.
How compatibility is verified
scripts/differential.mjs runs cfb@1.2.2 and this package side by side, each
input in its own child process with time and memory limits, and compares every
observable result: the read container, find, CFB output, ZIP output with and
without compression, the container after each write, and a write-read round
trip. With --mutations N it also corrupts each small input N times with a
fixed seed.
npm run test:differential -- <corpus-directory>
Each input is reported in one class:
| Class | Meaning | Fails the run |
|---|---|---|
identical |
Every result matches | No |
long-name |
Only CFB writes differ, and only because a name of 32 or more characters is written as 31; the round trip matches except that each such name reads back one or two characters shorter | No |
local-accepts |
cfb@1.2.2 throws and this package returns a result |
No |
upstream-failure-fixed |
cfb@1.2.2 times out, runs out of memory, or crashes, and this package finishes |
No |
error-text |
Both throw, with different messages | No |
both-failed |
Both time out or run out of memory | No; listed in the output |
local-failure |
Only this package times out, runs out of memory, or crashes | Yes |
regression |
cfb@1.2.2 returns a result and this package returns a different one or throws |
Yes |
CI runs the harness on the repository fixtures. Results for release 1.1.0, against the SheetJS test corpus (1,284 compound files and 1,674 ZIP containers):
| Run | Inputs | Result |
|---|---|---|
| Files up to 16 MB, all operations | 2,951 | 2,924 identical; 24 long-name; 2 encrypted ODS files that cfb@1.2.2 cannot read now read (local-accepts); 1 both-failed (memory limit of the test) |
| Files above 8 MB, reading only | 9 | 8 identical; 1 both-failed (memory limit of the test) |
| Corrupted copies of the files up to 64 KB, 10 each (seed 24301, 5-second limit) | 29,095 | No input failed only in this package; details below |
The corruption run:
| Class | Inputs | What it is |
|---|---|---|
identical |
25,194 | |
upstream-failure-fixed |
1,442 | cfb@1.2.2 hangs or exceeds the memory limit; this package throws, mostly Unexpected end of DEFLATE stream |
error-text |
2,411 | Both throw, with different messages; 2,190 differ only in the numbers of a checksum or size error |
long-name |
19 | |
local-accepts |
19 | |
both-failed |
3 | Both exceed the 700 MB memory limit of the test |
regression |
7 | 5 entries where cfb@1.2.2 returned uninitialized memory, and 2 entries that cfb@1.2.2 leaves without a parent; both are intentional differences |
cfb@1.2.2 also varies from run to run on damaged input: it returns
uninitialized memory, and its Huffman tables keep entries from earlier streams.
Replayed one at a time against cfb@1.2.2 with zero-filled buffers, the 5
memory cases are identical, and of 20 error-text inputs sampled evenly, 17
are identical, 2 hang in cfb@1.2.2, and 1 decodes about 4 billion bits past
the end of its data in cfb@1.2.2 before failing its checksum.
test/fixes.test.cjs covers each intentional difference with a reproduction,
and asserts what cfb@1.2.2 does where it finishes.
Intentional differences
| Input | cfb@1.2.2 |
@stackline/cfb |
|---|---|---|
| A ZIP entry whose DEFLATE stream is shorter than its declared size, with a data descriptor | The rest of the content is uninitialized memory, which can hold data left by earlier work in the process and differ between runs | The rest is zero-filled |
| A FAT chain that loops (js-cfb#11) | Never returns | Cycle detected in FAT chain at sector N |
| A DEFLATE stream that ends before its final block | Keeps decoding zero bits past the end: never returns, or after about 4 billion bits its 32-bit bit offset wraps around and it decodes the data again from the start | Unexpected end of DEFLATE stream |
| A DEFLATE code of zero bits (invalid Huffman table) | Never returns; memory passed 2 GB within 10 seconds in testing | Invalid DEFLATE literal/length code |
| A MIME header line with many colons | Quadratic time (CVE-2024-22363 pattern) | Linear time, same matches |
| Directory parent links that loop | RangeError: Invalid string length |
Directory tree cycle at entry N |
An entry that cfb@1.2.2 leaves without a parent: listed before its parent storage (js-cfb#15), or a sibling of a stream whose child link points at itself |
Path without its storage or the root | Full path from the directory tree |
| ZIP64 archives | Throws | Read |
| Stored ZIP entries with a data descriptor | Bad compressed size |
Read |
| ZIP dates read on the 29th-31st of a month | Month can shift by one | Correct date |
| Names of 32 or more characters, written to CFB | 32 characters with a 66-byte length field; reads back with a corrupt 33rd character and is rejected by strict readers | 31 characters, as on upstream master (js-cfb#18) |
| String content written to ZIP | Throws | Written |
A parsed container passed to read (js-cfb#20) |
TypeError: file.slice is not a function or similar |
TypeError explaining the mistake |
console.error reports a truncated name in both versions; the message now
names the 31-character result.
Opt-in additions
| Option | Default | Purpose |
|---|---|---|
CFB.write(cfb, {noseed: true}) |
false |
Write CFB output without the \u0001Sh33tJ5 seed stream (js-cfb#22) |
The type declarations also describe utils.use_zlib, utils._deflateRaw,
utils._inflateRaw, and utils.consts, which cfb@1.2.2 exports without
types.
See UPSTREAM.md
for every upstream issue, pull request, embedded xlsx fix, advisory, and
fork.
Upstream issues, pull requests, and forks
Upstream issues, pull requests, and forks
This page maps the original project and everything reported against it to how
@stackline/cfb handles each item. It was last reviewed on 2026-10-13.
The rule for every decision: results for existing cfb@1.2.2 code do not
change. A fix becomes the default only when cfb@1.2.2 never returns, fails
on a valid file, or produces a wrong or invalid result. Everything else is
opt-in or declined.
| Status | Meaning |
|---|---|
| Fixed | Works without configuration |
| Opt-in | Available through an option; the default is unchanged |
| Supported | Already worked in cfb@1.2.2 and still does |
| Declined | Would change cfb@1.2.2 results or break the format; the reason is given |
Where the original stands
- npm
cfbstops at 1.2.2 (2022-04-06). It is not deprecated. - SheetJS/js-cfb and its
git.sheetjs.com mirror were last
updated on 2022-11-10.
masteris two commits past 1.2.2: a README link change and the 31-character name limit (PR #19), never published. - SheetJS kept fixing its embedded copy of this code in
xlsx(bits/18_cfb.js). Those fixes never reached thecfbpackage; they are listed below.
Issues (SheetJS/js-cfb)
| Issue | Problem | Status | How |
|---|---|---|---|
| #1 | Cannot find name 'Buffer' in the types |
Supported | Fixed in cfb 1.0.4 |
| #2 | cfb_add rebuilds the container on every call |
Supported | cfb_add(..., {unsafe: true}) then one cfb_gc; documented in the README |
| #3 | MSI entry names look garbled | Supported | MSI compresses names; not a container bug |
| #4 | How to decompress VBA streams | Declined | MS-OVBA is outside the container format |
| #5 | Missing root prefix when a child precedes its parent | Supported | Fixed upstream (PR #6) |
| #7 | Endless loop in build_full_paths |
Fixed | A cycle of parent links now raises Directory tree cycle at entry N; cfb@1.2.2 raises RangeError: Invalid string length |
| #8 | Endless loop in make_sector_list |
Supported | Fixed in cfb 1.1.4 |
| #9 | Memory use on large XLS files | Supported | Closed upstream; large corpus files read the same as in cfb 1.2.2 |
| #11 | Endless loop in get_sector_list on a damaged file |
Fixed | Cyclic FAT chains raise Cycle detected in FAT chain at sector N |
| #12 | Quadratic rebuild_cfb |
Supported | Fixed in cfb 1.2.1 |
| #14 | DIFAT recursion past its count | Supported | Bounded by the count in cfb 1.2.2 |
| #15 | An entry listed before its parent storage loses its path | Fixed | Entries left without a parent take it from the directory tree; no other path changes |
| #16 | fs.readFileSync is not a function in browsers |
Supported | type: "file" is Node-only; documented |
| #18 | Names must fit 31 characters plus a terminator | Fixed | Names are truncated to 31 characters, as on upstream master |
| #20 | file.slice is not a function |
Fixed | Passing a parsed container to read raises a TypeError that says so |
| #21 | How to update a stream inside a file | Supported | cfb_add replaces existing content; documented |
| #22 | Write without the \u0001Sh33tJ5 seed stream |
Opt-in | CFB.write(cfb, {noseed: true}) |
| #24 | Keep stream order instead of sorting | Declined | [MS-CFB] 2.6.4 requires sibling order for binary search; unsorted files fail lookups in other readers |
Pull requests (SheetJS/js-cfb)
| Pull request | Change | Status |
|---|---|---|
| #6 | Fix for #5 | Supported |
| #10 | Move the CLI to cfb-cli |
Supported |
| #13 | Buffer.copy on write |
Supported |
| #17 | Remove the unused printj dependency |
Supported |
| #19 | Truncate names to 31 characters | Fixed |
Fixes from the copy embedded in xlsx
xlsx change |
Problem in cfb@1.2.2 |
Status |
|---|---|---|
| a0bed2a9 (CVE-2024-22363) | MIME header regular expression backtracks quadratically | Fixed: /^([^:]*?):\s*([^\s].*)$/ matches the same lines in linear time |
| 045adba8 (sheetjs#2766) | ZIP64 archives cannot be read | Fixed, following APPNOTE 4.5.3: ZIP64 end records, central directory fields, local fields, and 8-byte data descriptors |
| 5a36cb42 | Stored entries with a data descriptor are misread | Fixed; streamed stored entries also take their size from the central directory |
| 81b231d8 | String content breaks ZIP output | Fixed |
| 16dd8a6e | CRC tables assume Int32Array#subarray |
Fixed |
| 050f66ce | Message when the directory start is out of range | Declined: both versions throw; only the message would change |
| 2022f7f4 | require('fs') replaced by an injected module |
Declined: changes how Node loads the module |
Security advisories
- No advisory targets the
cfbpackage (GitHub Advisory Database, OSV, andnpm audit). - CVE-2024-22363 (ReDoS) is
filed against
xlsx; its MIME header expression is present verbatim incfb@1.2.2and is fixed here. - CVE-2023-30533 (prototype
pollution) and CVE-2021-32012/32013/32014 are in
xlsxworkbook parsing, not in this code.
Problems found during this review
Problem in cfb@1.2.2 |
Status |
|---|---|
inflate returns uninitialized process memory after the end of a stream that is shorter than its declared size; through read, a ZIP entry with a data descriptor carries that memory as content, because the CRC mismatch is only a warning |
Fixed: the rest of the output is zero-filled |
| Huffman tables are shared between calls and never cleared, so damaged DEFLATE data that reaches an unused bit pattern decodes with entries left by an earlier block or stream; the result can depend on what the process decoded before | Declined: those leftover entries let cfb@1.2.2 read some damaged files. Clearing them changed 12 results in the corruption run, 10 of them from content into errors. Valid streams never reach unused patterns |
| A stream whose child link points at itself leaves its siblings without a parent, so their paths lose the root | Fixed, with js-cfb#15 |
inflate never returns on a stream that ends before its final block (a CPU loop over zero bits past the end, which can end only when its 32-bit bit offset wraps around after about 4 billion bits), or on a code of zero bits (memory passed 2 GB within 10 seconds) |
Fixed: Unexpected end of DEFLATE stream or Invalid DEFLATE literal/length code |
| ZIP dates read on the 29th-31st of a month can land in the wrong month, because fields were set one by one on today's date | Fixed |
Names of 32 or more characters were written with a 66-byte length field; the file reads back with a corrupt name and strict readers such as olefile reject it |
Fixed (31-character limit) |
CFB.version reported 1.0.2 in @stackline/cfb 1.0.4 |
Fixed: the build writes the package version |
engines required Node.js 20.19 although the code runs on 0.8 |
Fixed: >=0.8, tested from 0.8.28 to 24 |
Downstream reports in SheetJS/sheetjs about this code
| Report | Status |
|---|---|
| #2766 ZIP64 workbooks | Fixed |
| #3215, #3244 endless CPU and memory on corrupt archives | The endless loops in inflate are fixed; the samples attached to these reports already fail quickly in standalone cfb@1.2.2 |
| #2745, #2965, #3274 "Bad compressed size" with data descriptors | Fixed for stored entries, including two encrypted ODS files in the test corpus; other cases were not reproduced |
#569, #1832, #1872 crashes in make_sector_list |
Supported (guarded in cfb 1.2.2) |
| #1419, #1442 CLSID check on WPS files | Supported (cfb 1.1.1) |
Forks
All 16 GitHub forks of SheetJS/js-cfb were compared with upstream master.
Two carry their own code:
| Fork | Change | Status |
|---|---|---|
albanm/js-cfb (@albanm/cfb) |
FAT cycle guard in make_sector_list |
Supported: upstream added a stronger guard in 1.1.4 |
YuryAlkevich/js-cfb |
Recursive directory add in the CLI | Declined: CLI only, not library behavior |
isabella232/js-cfb could not be inspected (the API returns 404). The others
have no commits of their own.
Release changes
Changelog
1.1.0 - 2026-10-13
A drop-in replacement for cfb@1.2.2. On the SheetJS test corpus every read
result is identical, and written bytes differ only where cfb@1.2.2 wrote
invalid names. On damaged input this release finishes where cfb@1.2.2 never
returns, and it never returns uninitialized memory. See
docs/COMPATIBILITY.md
and docs/UPSTREAM.md.
Fixed
- A ZIP entry whose DEFLATE stream is shorter than its declared size no longer
returns uninitialized process memory in the rest of its content; the rest is
zero-filled.
cfb@1.2.2returns that memory when a data descriptor turns the CRC mismatch into a warning, and fromutils._inflateRawwith a size. inflateno longer loops forever on a DEFLATE stream that ends before its final block, and no longer allocates without bound on a zero-bit code.- The MIME header regular expression no longer backtracks quadratically
(the
cfbpart of CVE-2024-22363). - ZIP64 archives are read: end records, central directory and local fields, and 8-byte data descriptors (sheetjs#2766).
- Stored ZIP entries followed by a data descriptor are read, including streamed entries whose local sizes are zero; two encrypted ODS files in the test corpus now open.
- An entry listed before its parent storage keeps its full path (js-cfb#15), and so do the siblings of a stream whose child link points at itself.
- ZIP dates read on the 29th-31st of a month no longer shift by a month.
- Names of 32 or more characters are written as 31 characters, as on upstream
master(js-cfb#18).cfb@1.2.2wrote a 66-byte name length, which read back as a corrupt name and is rejected by strict readers. - String content is written correctly to ZIP output.
- Directory parent links that loop raise
Directory tree cycle at entry N. - Passing a parsed container to
readraises aTypeErrorthat explains the mistake (js-cfb#20). - CRC tables no longer require
Int32Array#subarray. CFB.versionreports the package version again; 1.0.3 and 1.0.4 reported1.0.2.enginesis>=0.8again, as incfb@1.2.2; 1.0.x required Node.js 20.19 although the code runs on 0.8.
Added
CFB.write(cfb, {noseed: true})writes CFB output without the\u0001Sh33tJ5seed stream (js-cfb#22).- Type declarations for
noseed,utils.use_zlib,utils._deflateRaw,utils._inflateRaw, andutils.consts. scripts/differential.mjs: compares every observable result withcfb@1.2.2over a corpus, with optional deterministic corruption.- Regression tests for each fix, a Node.js 0.8-compatible smoke test, and CI jobs for Node.js 0.8.28 through 18.
1.0.4 - 2026-09-28
- Validate documentation badges and synchronize version labels. Published from the 1.0.2 tarball with only the README and package metadata changed.
1.0.3 - 2026-09-28
- Standardize the README, credits, and discovery metadata. Published from the 1.0.2 tarball with only the README and package metadata changed.
1.0.2 - 2026-09-28
- Reuse the verified Stackline crc-32 and uglify-js forks for compatibility validation and the distribution build.
1.0.1 (2026-09-28)
- Standardize package documentation, preserve the API reference and upstream attribution, and add Stackline community links.
- Add focused npm discovery keywords and consistent repository metadata.
- Keep runtime behavior and dependency versions unchanged.
- Correct the pinned artifact-upload action commit while preserving the publish.yml workflow and Prod environment.
1.0.0
- Forked cfb 1.2.2 at ad93f76a36f893362efe96f738a327c290c1f2a9.
- Reject cyclic FAT chains in get_sector_list, covering upstream issue #11.
- Remove unused declared checksum runtime dependencies; keep the embedded code.
- Add bounded child-process regression, browser bundle regression and CFB/ZIP tests.
- Replace legacy development tooling with a portable build and current test tools.
Upstream history
CHANGELOG
This log is intended to keep track of backwards-incompatible changes, including but not limited to API changes and file location changes. Minor behavioral changes may not be included if they are not expected to break existing code.
1.2.1 (2021-09-06)
- CFB write optimizations (h/t @rossj Ross Johnson)
readin NodeJS will treatBufferinput as type"buffer"by defaultdeflate/ ZIP support fixed Huffman compressioninflatemore aggressive reallocs
1.2.0 (2020-07-09)
- Support for MAD file format (MIME aggregate document)
- Spun off the CLI tool to the
cfb-climodule
1.1.0 (2018-09-04)
- Support for ZIP file format
1.0.6 (2018-04-09)
lastIndexOfin FAT builder enables larger file parsing at minor cost
1.0.0 (2017-11-05)
- Actually walk mini-fat
0.14.0 (2017-11-04)
- Completely removed
FullPathDir
- Adapt the build to the existing UglifyJS 2-compatible Stackline API with named input, ES3/IE8 output, and external source maps; retain the minified-browser regression tests.
Release files and references
- README.md
- CHANGELOG.md
- LICENSE
- Package and publication metadata
- Full text documentation
- Compatibility with cfb 1.2.2
- Upstream issues, pull requests, and forks
Package bytes, npm provenance and the immutable GitHub release were verified for this version. Security checks describe the reviewed release; documented compatibility risks and upstream reports are not blanket claims of resolution.