Skip to documentation

package

@stackline/cfb 1.1.1 — installation and API

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

Install @stackline/cfb

npm install @stackline/[email protected]

Package version: 1.1.1. Metadata verified: 2026-11-12. This is separate from the framework version and demonstration runtime.

Node.js requirement
{"node": ">=0.8"}
Accepted peer dependencies (declared ranges)
{}

Tested environments are described in the versioned README and linked source checks. A declared peer range is not evidence that every version in it was tested.

Versioned reference

This reference comes from the published @stackline/[email protected] README. Its upstream attribution and license remain with the source. Benchmark and security statements describe the cited tests and advisories, not a guarantee for every application.

@stackline/cfb

Drop-in replacement for cfb 1.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.1


Why this package?

Run application compatibility tests before replacing a dependency. Review the version-specific behavior and limits below.

[email protected] 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 [email protected] loop forever.

@stackline/cfb is built for the swap:

  • Same results. For intact files, every read result matches [email protected]: 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 [email protected] rejects (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 browser field, 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/[email protected]
Replaces [email protected] 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 [email protected] @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

[email protected] 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 the cfb, 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 nodejs zlib instance.

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:

  • .FullPaths is 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)

  • .FileIndex is 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 [email protected] 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 [email protected] 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

  1. Update the package version, lockfile, generated version fields, and changelog together.
  2. Run the development checks above and audit both npm audit and npm audit --omit=dev.
  3. Use the GitHub publish workflow with its Prod environment to publish the exact CI tarball.
  4. 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 copyright, license notices and contributor acknowledgements remain part of this distribution. Stackline maintenance does not replace authorship of the original work.

Use this repository's issue tracker for reproducible bugs and feature requests. Join r/Stackline for examples, usage questions and release discussions.