From 92297ad65998a027c625a21ae1d0ae05d1680287 Mon Sep 17 00:00:00 2001 From: FUJI Goro Date: Fri, 26 Dec 2025 16:33:20 +0900 Subject: [PATCH 1/5] modernize code --- test/decode-blob.test.ts | 4 +--- test/readme.test.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/decode-blob.test.ts b/test/decode-blob.test.ts index 0c8c5b4..a18fc9e 100644 --- a/test/decode-blob.test.ts +++ b/test/decode-blob.test.ts @@ -18,8 +18,6 @@ import { encode, decode, decodeAsync } from "../src/index.ts"; this.skip(); } - // use any because the type of Blob#stream() in @types/node does not make sense here. - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - assert.deepStrictEqual(await decodeAsync(blob.stream() as any), "Hello!"); + assert.deepStrictEqual(await decodeAsync(blob.stream()), "Hello!"); }); }); diff --git a/test/readme.test.ts b/test/readme.test.ts index 0ee9fd5..9aff528 100644 --- a/test/readme.test.ts +++ b/test/readme.test.ts @@ -1,5 +1,5 @@ import { deepStrictEqual } from "assert"; -import { encode, decode } from "../src/index"; +import { encode, decode } from "../src/index.ts"; describe("README", () => { context("## Synopsis", () => { From 3934a8cc88c226b9d8955854958c31270538cf23 Mon Sep 17 00:00:00 2001 From: FUJI Goro Date: Sat, 27 Dec 2025 15:18:36 +0900 Subject: [PATCH 2/5] add exports for ESM & CJS dual packages --- package.json | 17 ++++++++++++++-- tools/fix-ext.mts | 46 +++++++++++++++++++++++++++--------------- tsconfig.dist.cjs.json | 2 +- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 5441842..67e4dd4 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,24 @@ "module": "./dist.esm/index.mjs", "cdn": "./dist.umd/msgpack.min.js", "unpkg": "./dist.umd/msgpack.min.js", - "types": "./dist.esm/index.d.ts", + "types": "./dist.esm/index.d.mts", + "exports": { + ".": { + "import": { + "types": "./dist.esm/index.d.mts", + "default": "./dist.esm/index.mjs" + }, + "require": { + "types": "./dist.cjs/index.d.cts", + "default": "./dist.cjs/index.cjs" + } + }, + "./package.json": "./package.json" + }, "sideEffects": false, "scripts": { "build": "npm publish --dry-run", - "prepare": "npm run clean && webpack --bail && tsgo --build tsconfig.dist.cjs.json tsconfig.dist.esm.json && tsimp tools/fix-ext.mts --mjs dist.esm/*.js dist.esm/*/*.js && tsimp tools/fix-ext.mts --cjs dist.cjs/*.js dist.cjs/*/*.js", + "prepare": "npm run clean && webpack --bail && tsgo --build tsconfig.dist.cjs.json tsconfig.dist.esm.json && tsimp tools/fix-ext.mts --mjs dist.esm/*.js dist.esm/*/*.js dist.esm/*.d.ts dist.esm/*/*.d.ts && tsimp tools/fix-ext.mts --cjs dist.cjs/*.js dist.cjs/*/*.js dist.cjs/*.d.ts dist.cjs/*/*.d.ts", "prepublishOnly": "npm run test:dist", "clean": "rimraf build dist dist.*", "test": "mocha 'test/**/*.test.ts'", diff --git a/tools/fix-ext.mts b/tools/fix-ext.mts index bd6f737..d7cd283 100644 --- a/tools/fix-ext.mts +++ b/tools/fix-ext.mts @@ -4,25 +4,39 @@ const mode = process.argv[2]; // --cjs or --mjs const files = process.argv.slice(3); const ext = mode === "--cjs" ? "cjs" : "mjs"; +const dtsExt = mode === "--cjs" ? "d.cts" : "d.mts"; console.info(`Fixing ${mode} files with extension ${ext}`); for (const file of files) { - const fileMjs = file.replace(/\.js$/, `.${ext}`); - console.info(`Processing ${file} => ${fileMjs}`); - // .js => .mjs - const content = fs.readFileSync(file).toString("utf-8"); - const newContent = content - .replace(/\bfrom "(\.\.?\/[^"]+)\.js";/g, `from "$1.${ext}";`) - .replace(/\bimport "(\.\.?\/[^"]+)\.js";/g, `import "$1.${ext}";`) - .replace(/\brequire\("(\.\.?\/[^"]+)\.js"\)/g, `require("$1.${ext}");`) - .replace(/\/\/# sourceMappingURL=(.+)\.js\.map$/, `//# sourceMappingURL=$1.${ext}.map`); - fs.writeFileSync(fileMjs, newContent); - fs.unlinkSync(file); + if (file.endsWith(".d.ts")) { + // Handle declaration files: .d.ts => .d.mts or .d.cts + const newFile = file.replace(/\.d\.ts$/, `.${dtsExt}`); + console.info(`Processing ${file} => ${newFile}`); + const content = fs.readFileSync(file).toString("utf-8"); + // Fix import paths: .ts => .mjs or .cjs + const newContent = content + .replace(/\bfrom "(\.\.?\/[^"]+)\.ts";/g, `from "$1.${ext}";`) + .replace(/\bimport "(\.\.?\/[^"]+)\.ts";/g, `import "$1.${ext}";`); + fs.writeFileSync(newFile, newContent); + fs.unlinkSync(file); + } else if (file.endsWith(".js")) { + // Handle JS files: .js => .mjs or .cjs + const fileMjs = file.replace(/\.js$/, `.${ext}`); + console.info(`Processing ${file} => ${fileMjs}`); + const content = fs.readFileSync(file).toString("utf-8"); + const newContent = content + .replace(/\bfrom "(\.\.?\/[^"]+)\.js";/g, `from "$1.${ext}";`) + .replace(/\bimport "(\.\.?\/[^"]+)\.js";/g, `import "$1.${ext}";`) + .replace(/\brequire\("(\.\.?\/[^"]+)\.js"\)/g, `require("$1.${ext}");`) + .replace(/\/\/# sourceMappingURL=(.+)\.js\.map$/, `//# sourceMappingURL=$1.${ext}.map`); + fs.writeFileSync(fileMjs, newContent); + fs.unlinkSync(file); - // .js.map => .mjs.map - const mapping = JSON.parse(fs.readFileSync(`${file}.map`).toString("utf-8")); - mapping.file = mapping.file.replace(/\.js$/, ext); - fs.writeFileSync(`${fileMjs}.map`, JSON.stringify(mapping)); - fs.unlinkSync(`${file}.map`); + // .js.map => .mjs.map + const mapping = JSON.parse(fs.readFileSync(`${file}.map`).toString("utf-8")); + mapping.file = mapping.file.replace(/\.js$/, ext); + fs.writeFileSync(`${fileMjs}.map`, JSON.stringify(mapping)); + fs.unlinkSync(`${file}.map`); + } } diff --git a/tsconfig.dist.cjs.json b/tsconfig.dist.cjs.json index 8225a84..3a5c60d 100644 --- a/tsconfig.dist.cjs.json +++ b/tsconfig.dist.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "module": "CommonJS", "outDir": "./dist.cjs", - "declaration": false, + "declaration": true, "noEmitOnError": true, "noEmit": false, "rewriteRelativeImportExtensions": true, From 23344ab0f3eee88772d51c7d8aed7ffc4f2bc719 Mon Sep 17 00:00:00 2001 From: FUJI Goro Date: Sat, 27 Dec 2025 15:26:13 +0900 Subject: [PATCH 3/5] update action versions --- .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/codeql.yml | 6 +++--- .github/workflows/fuzz.yml | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 294c86e..0f7e6df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,9 +18,9 @@ jobs: - '22' - '24' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: cache: npm node-version: ${{ matrix.node-version }} @@ -38,9 +38,9 @@ jobs: matrix: browser: [ChromeHeadless, FirefoxHeadless] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: cache: npm node-version: '22' @@ -51,9 +51,9 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: cache: npm node-version: '22' @@ -64,7 +64,7 @@ jobs: deno: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Deno uses: denoland/setup-deno@v2 with: @@ -75,7 +75,7 @@ jobs: bun: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Bun uses: oven-sh/setup-bun@v2 - run: bun install @@ -84,9 +84,9 @@ jobs: node_with_strip_types: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: cache: npm node-version: '24' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 578c615..685134e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,12 +19,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: typescript - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 79f9e5c..1d4dd37 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -14,9 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: cache: npm node-version: "20" From 582961977d5a5b1e8f91863677568dd6770b12a9 Mon Sep 17 00:00:00 2001 From: FUJI Goro Date: Tue, 30 Dec 2025 10:58:01 +0900 Subject: [PATCH 4/5] fix benchmark scripts for node, bun, and deno --- benchmark/benchmark-from-msgpack-lite.ts | 2 +- benchmark/decode-string.ts | 2 +- benchmark/encode-string.ts | 2 +- benchmark/key-decoder.ts | 8 +++++--- benchmark/package.json | 1 + benchmark/profile-decode.ts | 2 +- benchmark/profile-encode.ts | 2 +- benchmark/string.ts | 2 +- benchmark/sync-vs-async.ts | 12 +++++++----- benchmark/timestamp-ext.ts | 2 +- 10 files changed, 20 insertions(+), 15 deletions(-) diff --git a/benchmark/benchmark-from-msgpack-lite.ts b/benchmark/benchmark-from-msgpack-lite.ts index c03cd0a..40ac6f9 100644 --- a/benchmark/benchmark-from-msgpack-lite.ts +++ b/benchmark/benchmark-from-msgpack-lite.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // original: https://raw.githubusercontent.com/kawanet/msgpack-lite/master/lib/benchmark.js -var msgpack_msgpack = require("../src"); +var msgpack_msgpack = require("../src/index.ts"); var msgpack_node = try_require("msgpack"); var msgpack_lite = try_require("msgpack-lite"); diff --git a/benchmark/decode-string.ts b/benchmark/decode-string.ts index a6ea146..3e6bfbb 100644 --- a/benchmark/decode-string.ts +++ b/benchmark/decode-string.ts @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import { utf8EncodeJs, utf8Count, utf8DecodeJs, utf8DecodeTD } from "../src/utils/utf8"; +import { utf8EncodeJs, utf8Count, utf8DecodeJs, utf8DecodeTD } from "../src/utils/utf8.ts"; // @ts-ignore import Benchmark from "benchmark"; diff --git a/benchmark/encode-string.ts b/benchmark/encode-string.ts index 3f6aac6..df1b283 100644 --- a/benchmark/encode-string.ts +++ b/benchmark/encode-string.ts @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import { utf8EncodeJs, utf8Count, utf8EncodeTE } from "../src/utils/utf8"; +import { utf8EncodeJs, utf8Count, utf8EncodeTE } from "../src/utils/utf8.ts"; // @ts-ignore import Benchmark from "benchmark"; diff --git a/benchmark/key-decoder.ts b/benchmark/key-decoder.ts index 594bbab..dec38f3 100644 --- a/benchmark/key-decoder.ts +++ b/benchmark/key-decoder.ts @@ -1,9 +1,11 @@ /* eslint-disable no-console */ -import { utf8EncodeJs, utf8Count, utf8DecodeJs } from "../src/utils/utf8"; +import { utf8EncodeJs, utf8Count, utf8DecodeJs } from "../src/utils/utf8.ts"; +import { CachedKeyDecoder } from "../src/CachedKeyDecoder.ts"; + +import data from "./benchmark-from-msgpack-lite-data.json" with { type: "json" }; // @ts-ignore import Benchmark from "benchmark"; -import { CachedKeyDecoder } from "../src/CachedKeyDecoder"; type InputType = { bytes: Uint8Array; @@ -11,7 +13,7 @@ type InputType = { str: string; }; -const keys: Array = Object.keys(require("./benchmark-from-msgpack-lite-data.json")).map((str) => { +const keys: Array = Object.keys(data).map((str) => { const byteLength = utf8Count(str); const bytes = new Uint8Array(new ArrayBuffer(byteLength)); utf8EncodeJs(str, bytes, 0); diff --git a/benchmark/package.json b/benchmark/package.json index 161581b..ddc30b8 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -2,6 +2,7 @@ "name": "@msgpack/msgpack-benchmark", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "update-dependencies": "npx rimraf node_modules/ package-lock.json ; npm install ; npm audit fix --force ; git restore package.json ; npm install" }, diff --git a/benchmark/profile-decode.ts b/benchmark/profile-decode.ts index 081ddab..512d87e 100644 --- a/benchmark/profile-decode.ts +++ b/benchmark/profile-decode.ts @@ -1,4 +1,4 @@ -import { encode, decode, decodeAsync } from "../src"; +import { encode, decode, decodeAsync } from "../src/index.ts"; // @ts-ignore import _ from "lodash"; const data = require("./benchmark-from-msgpack-lite-data.json"); diff --git a/benchmark/profile-encode.ts b/benchmark/profile-encode.ts index 6efcf01..245e71b 100644 --- a/benchmark/profile-encode.ts +++ b/benchmark/profile-encode.ts @@ -1,4 +1,4 @@ -import { encode } from "../src"; +import { encode } from "../src/index.ts"; // @ts-ignore import _ from "lodash"; diff --git a/benchmark/string.ts b/benchmark/string.ts index b801394..4f11f30 100644 --- a/benchmark/string.ts +++ b/benchmark/string.ts @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import { encode, decode } from "../src"; +import { encode, decode } from "../src/index.ts"; const ascii = "A".repeat(40000); const emoji = "🌏".repeat(20000); diff --git a/benchmark/sync-vs-async.ts b/benchmark/sync-vs-async.ts index 6981932..620438e 100644 --- a/benchmark/sync-vs-async.ts +++ b/benchmark/sync-vs-async.ts @@ -1,12 +1,14 @@ -#!ts-node +#!/usr/bin/env node /* eslint-disable no-console */ -import { encode, decode, decodeAsync, decodeArrayStream } from "../src"; -import { writeFileSync, unlinkSync, readFileSync, createReadStream } from "fs"; -import { deepStrictEqual } from "assert"; +import { encode, decode, decodeAsync, decodeArrayStream } from "../src/index.ts"; +import { writeFileSync, unlinkSync, readFileSync, createReadStream } from "node:fs"; +import { deepStrictEqual } from "node:assert"; + +type Data = { id: number; score: number; title: string; content: string; createdAt: Date }; (async () => { - const data = []; + const data: Data[] = []; for (let i = 0; i < 1000; i++) { const id = i + 1; data.push({ diff --git a/benchmark/timestamp-ext.ts b/benchmark/timestamp-ext.ts index ba96a22..318b445 100644 --- a/benchmark/timestamp-ext.ts +++ b/benchmark/timestamp-ext.ts @@ -1,4 +1,4 @@ -import { encode, decode } from "../src"; +import { encode, decode } from "../src/index.ts"; const data = new Array(100).fill(new Date()); From 72c849023ad2b89070693aec8b90a00619473d35 Mon Sep 17 00:00:00 2001 From: Zach Date: Tue, 20 Jan 2026 12:05:42 -0700 Subject: [PATCH 5/5] enhance prototype pollution protection by disallowing 'constructor' and 'prototype' as map keys --- src/Decoder.ts | 4 ++-- test/prototype-pollution.test.ts | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/Decoder.ts b/src/Decoder.ts index bba8804..5b3c884 100644 --- a/src/Decoder.ts +++ b/src/Decoder.ts @@ -651,8 +651,8 @@ export class Decoder { continue DECODE; } } else if (state.type === STATE_MAP_KEY) { - if (object === "__proto__") { - throw new DecodeError("The key __proto__ is not allowed"); + if (object === "__proto__" || object === "constructor" || object === "prototype") { + throw new DecodeError(`The key ${object} is not allowed`); } state.key = this.mapKeyConverter(object); diff --git a/test/prototype-pollution.test.ts b/test/prototype-pollution.test.ts index 46e293e..29c1b53 100644 --- a/test/prototype-pollution.test.ts +++ b/test/prototype-pollution.test.ts @@ -19,4 +19,40 @@ describe("prototype pollution", () => { }, DecodeError); }); }); + + context("constructor exists as a map key", () => { + it("raises DecodeError in decoding", () => { + const o = { + foo: "bar", + }; + // override constructor as an enumerable property + Object.defineProperty(o, "constructor", { + value: new Date(0), + enumerable: true, + }); + const encoded = encode(o); + + throws(() => { + decode(encoded); + }, DecodeError); + }); + }); + + context("prototype exists as a map key", () => { + it("raises DecodeError in decoding", () => { + const o = { + foo: "bar", + }; + // override prototype as an enumerable property + Object.defineProperty(o, "prototype", { + value: new Date(0), + enumerable: true, + }); + const encoded = encode(o); + + throws(() => { + decode(encoded); + }, DecodeError); + }); + }); });