Skip to content

fix: load file-type via ESM-safe shim to allow bump to ^21 (CVE-2026-31808)#127

Open
hhorton-onebrief wants to merge 1 commit into
sillsdev:mainfrom
Onebrief:fix/file-type-esm
Open

fix: load file-type via ESM-safe shim to allow bump to ^21 (CVE-2026-31808)#127
hhorton-onebrief wants to merge 1 commit into
sillsdev:mainfrom
Onebrief:fix/file-type-esm

Conversation

@hhorton-onebrief
Copy link
Copy Markdown

@hhorton-onebrief hhorton-onebrief commented Apr 24, 2026

Summary

Fixes CVE-2026-31808 (file-type DoS via crafted ASF header, CVSS 5.3) by upgrading file-type from 16.5.3 to ^21.0.0.

The existing "//file-type" comment in package.json documents why this bump was previously blocked: file-type@17+ is ESM-only, and a static import from "file-type" under tsconfig module: commonjs gets rewritten to require("file-type"), which fails on ESM-only packages. Downstream consumers who add a resolution forcing file-type to the CVE-fixed 21.3.3 currently hit a runtime crash in src/images.ts because FileType.fromBuffer no longer exists on the v21 API.

This PR resolves the CJS↔ESM boundary without changing the project's compile target.

Approach

Added src/fileType.ts, a 24-line shim that wraps file-type's ESM API behind a new Function("return import('file-type')") trampoline. TypeScript's commonjs transform does not inspect strings inside new Function(...), so the import() expression reaches Node's native dynamic import at runtime. src/images.ts is rewired to call detectFileType(buffer) from the shim instead of the old static FileType.fromBuffer.

The shim is quarantined to a single file with a comment explaining the rationale. If the project later migrates to ESM, the shim becomes a one-file delete plus inlining a static import in src/images.ts.

Changes

  • package.json
    • Bump file-type from 16.5.3 to ^21.0.0.
    • Remove the stale "//file-type" workaround comment (workaround is now documented at its point of use).
    • Add "engines": { "node": ">=20" }file-type@21 requires Node 20+, so the project's floor shifts. Matches the existing volta.node pin.
  • src/fileType.ts (new, 24 lines) — typed detectFileType(buffer) helper and local FileTypeResult = { ext: string; mime: string } type.
  • src/images.ts — two-line diff: replace the static file-type import with one from ./fileType, and replace the FileType.fromBuffer(...) call with detectFileType(...). ImageSet.fileType?: FileTypeResult is unchanged (identifier resolves to the local re-export, same shape).

Breaking change

Minimum Node version moves from 14+ (implicit, via file-type@16) to 20+ (required by file-type@21). The "engines" field makes this a loud EBADENGINE warning at install time rather than a silent runtime crash.

Test plan

  • npm run build passes (vitest + tsc).
  • Compiled dist/*.js contains no require("file-type") — the only reference is the new Function string literal in dist/fileType.js.
  • npm run pull-test-tagged completes successfully against a live Notion workspace. Downloaded images land with correct buffer-detected extensions (no ERR_REQUIRE_ESM, no fromBuffer is not a function).

Notes for reviewers

  • Version range: ^21.0.0 rather than ^22.0.0, since file-type@22 raises the Node floor to 22. ^21.0.0 satisfies the CVE fix (shipped in 21.3.3), accepts forward patch updates in the 21.x line, and keeps Node 20 in the compatibility matrix.
  • Why new Function rather than await import("file-type") directly: under tsc --module commonjs, await import("file-type") compiles to Promise.resolve().then(() => require("file-type")), which crashes on ESM-only modules. The new Function escape hatch is the standard, widely-used CJS→ESM bridge (see also: tsimportlib, dynamic-import-function). Inlined here to avoid adding a tiny runtime dep.

This change is Reviewable

…31808)

file-type@17+ is ESM-only. docu-notion's commonjs build rewrites
`import` and `await import()` into `require()`, which crashes on
ESM-only packages. This blocked security upgrades past file-type@16.5.3.

Downstream consumers who apply a security resolution pinning
`file-type` to ^21.x currently crash in src/images.ts because
`FileType.fromBuffer` no longer exists on the v21 API.

This change adds src/fileType.ts, a thin wrapper that uses
`new Function("return import('file-type')")` to hide the dynamic
import from TypeScript's commonjs transform. src/images.ts is
rewired to use the shim.

Node 20+ is now required (file-type@21 engines constraint).
@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@hhorton-onebrief
Copy link
Copy Markdown
Author

fixes: #128

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant