From d3d1f7bfda83dc257c6c63205b32e5e488acc6f3 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Fri, 18 Sep 2026 12:33:36 +0200 Subject: [PATCH 1/8] restrict global scope --- packages/shell-bson-parser/src/index.spec.ts | 51 ++++++++++++++ packages/shell-bson-parser/src/worker.ts | 74 ++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index 0902ba3a..8180543f 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as api from './index.js'; import { terminateWorker } from './worker-client.js'; +import { restrictGlobalScope, ALLOWED_GLOBALS } from './worker.js'; import { PARSE_TEST_CASES } from './../test/parse-test-cases.js'; describe('shell-bson-parser with webworker processing', function () { @@ -29,6 +30,56 @@ describe('shell-bson-parser with webworker processing', function () { } }); + describe('restrictGlobalScope', function () { + it('strips capabilities not needed for parsing, keeping the JS intrinsics parsing needs', function () { + const scope: Record = Object.create(null); + scope.fetch = function fetch() {}; + scope.require = function require() {}; + scope.process = Object.create(null); + scope.importScripts = function importScripts() {}; + scope.XMLHttpRequest = function XMLHttpRequest() {}; + scope.Object = Object; + scope.Array = Array; + scope.Math = Math; + + restrictGlobalScope(scope); + + expect(scope).to.not.have.property('fetch'); + expect(scope).to.not.have.property('require'); + expect(scope).to.not.have.property('process'); + expect(scope).to.not.have.property('importScripts'); + expect(scope).to.not.have.property('XMLHttpRequest'); + expect(scope).to.have.property('Object', Object); + expect(scope).to.have.property('Array', Array); + expect(scope).to.have.property('Math', Math); + }); + + it('keeps every allowlisted global untouched', function () { + const scope: Record = Object.create(null); + for (const key of ALLOWED_GLOBALS) { + scope[key] = key; + } + + restrictGlobalScope(scope); + + for (const key of ALLOWED_GLOBALS) { + expect(scope).to.have.property(key, key); + } + }); + + it('does not throw on non-configurable properties', function () { + const scope: Record = Object.create(null); + Object.defineProperty(scope, 'nonConfigurable', { + value: 'danger', + configurable: false, + enumerable: true, + }); + + expect(() => restrictGlobalScope(scope)).to.not.throw(); + expect(scope).to.have.property('nonConfigurable', 'danger'); + }); + }); + describe('terminateWorker', function () { it('starts a new worker after termination', async function () { const res1 = await api.parse('{code: "BER"}'); diff --git a/packages/shell-bson-parser/src/worker.ts b/packages/shell-bson-parser/src/worker.ts index 3331c94e..2fd5df15 100644 --- a/packages/shell-bson-parser/src/worker.ts +++ b/packages/shell-bson-parser/src/worker.ts @@ -3,6 +3,79 @@ import { markBSON, unmarkBSON } from './structured-clone-bson.js'; import type { WorkerRequest, WorkerResponse } from './worker-types.js'; +export const ALLOWED_GLOBALS = new Set([ + // Used by this file. + 'self', + 'onmessage', + 'postMessage', + + // Needed for parsing. + 'Object', + 'Array', + 'Function', + 'String', + 'Number', + 'Boolean', + 'Symbol', + 'BigInt', + 'Math', + 'Date', + 'RegExp', + 'JSON', + 'Map', + 'Set', + 'WeakMap', + 'WeakSet', + 'Promise', + 'Proxy', + 'Reflect', + 'Error', + 'TypeError', + 'RangeError', + 'SyntaxError', + 'ReferenceError', + 'EvalError', + 'URIError', + 'ArrayBuffer', + 'SharedArrayBuffer', + 'DataView', + 'Uint8Array', + 'Int8Array', + 'Uint8ClampedArray', + 'Uint16Array', + 'Int16Array', + 'Uint32Array', + 'Int32Array', + 'Float32Array', + 'Float64Array', + 'BigInt64Array', + 'BigUint64Array', + 'TextEncoder', + 'TextDecoder', + 'undefined', + 'NaN', + 'Infinity', + 'isNaN', + 'isFinite', + 'parseFloat', + 'parseInt', + 'encodeURIComponent', + 'decodeURIComponent', + 'Buffer', +]); + +// Exported for test +export function restrictGlobalScope(scope: object): void { + for (const key of Object.getOwnPropertyNames(scope)) { + if (ALLOWED_GLOBALS.has(key)) continue; + try { + delete (scope as any)[key]; + } catch { + // Non-configurable in this environment; nothing more we can do. + } + } +} + // Exported for test export function handleRequest(request: WorkerRequest): WorkerResponse { const { id, args } = request; @@ -16,6 +89,7 @@ export function handleRequest(request: WorkerRequest): WorkerResponse { } if (typeof self !== 'undefined') { + restrictGlobalScope(self); self.onmessage = (event: MessageEvent) => { (self as unknown as Worker).postMessage(handleRequest(event.data)); }; From 31f7d9243906b2b2f3694d6984abfaca42fd24c0 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Fri, 18 Sep 2026 13:06:47 +0200 Subject: [PATCH 2/8] restrict object prototype --- packages/shell-bson-parser/src/index.spec.ts | 38 +++++++++++++++++++- packages/shell-bson-parser/src/worker.ts | 29 +++++++++++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index 8180543f..6b87b730 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -2,7 +2,12 @@ import { expect } from 'chai'; import * as api from './index.js'; import { terminateWorker } from './worker-client.js'; -import { restrictGlobalScope, ALLOWED_GLOBALS } from './worker.js'; +import { + restrictObjectPrototype, + DISALLOWED_PROTOTYPE_PROPS, + restrictGlobalScope, + ALLOWED_GLOBALS, +} from './worker.js'; import { PARSE_TEST_CASES } from './../test/parse-test-cases.js'; describe('shell-bson-parser with webworker processing', function () { @@ -80,6 +85,37 @@ describe('shell-bson-parser with webworker processing', function () { }); }); + describe('lockdownObjectPrototype', function () { + let originalDescriptors: Record; + beforeEach(function () { + originalDescriptors = Object.create(null); + for (const key of DISALLOWED_PROTOTYPE_PROPS) { + originalDescriptors[key] = Object.getOwnPropertyDescriptor( + Object.prototype, + key, + ); + } + }); + + afterEach(function () { + for (const key of DISALLOWED_PROTOTYPE_PROPS) { + const descriptor = originalDescriptors[key]; + if (descriptor) { + Object.defineProperty(Object.prototype, key, descriptor); + } + } + }); + + it('removes every disallowed accessor from Object.prototype', function () { + restrictObjectPrototype(); + + for (const key of DISALLOWED_PROTOTYPE_PROPS) { + expect(Object.prototype).to.not.have.property(key); + expect(({} as Record)[key]).to.equal(undefined); + } + }); + }); + describe('terminateWorker', function () { it('starts a new worker after termination', async function () { const res1 = await api.parse('{code: "BER"}'); diff --git a/packages/shell-bson-parser/src/worker.ts b/packages/shell-bson-parser/src/worker.ts index 2fd5df15..63355842 100644 --- a/packages/shell-bson-parser/src/worker.ts +++ b/packages/shell-bson-parser/src/worker.ts @@ -3,6 +3,7 @@ import { markBSON, unmarkBSON } from './structured-clone-bson.js'; import type { WorkerRequest, WorkerResponse } from './worker-types.js'; +// Exported for tests export const ALLOWED_GLOBALS = new Set([ // Used by this file. 'self', @@ -64,19 +65,40 @@ export const ALLOWED_GLOBALS = new Set([ 'Buffer', ]); -// Exported for test +// Exported for tests export function restrictGlobalScope(scope: object): void { for (const key of Object.getOwnPropertyNames(scope)) { if (ALLOWED_GLOBALS.has(key)) continue; try { delete (scope as any)[key]; } catch { - // Non-configurable in this environment; nothing more we can do. + // Non-configurable in this environment } } } -// Exported for test +// Exported for tests +export const DISALLOWED_PROTOTYPE_PROPS = [ + '__proto__', + '__defineGetter__', + '__defineSetter__', + '__lookupGetter__', + '__lookupSetter__', + 'constructor', +] as const; + +// Exported for tests +export function restrictObjectPrototype(): void { + for (const key of DISALLOWED_PROTOTYPE_PROPS) { + try { + delete (Object.prototype as any)[key]; + } catch { + // Non-configurable in this environment + } + } +} + +// Exported for tests export function handleRequest(request: WorkerRequest): WorkerResponse { const { id, args } = request; try { @@ -89,6 +111,7 @@ export function handleRequest(request: WorkerRequest): WorkerResponse { } if (typeof self !== 'undefined') { + restrictObjectPrototype(); restrictGlobalScope(self); self.onmessage = (event: MessageEvent) => { (self as unknown as Worker).postMessage(handleRequest(event.data)); From 666023fd9be1a715eb205728793ef24beb844656 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 23 Sep 2026 11:24:14 +0200 Subject: [PATCH 3/8] drop web-worker package and add tests --- package-lock.json | 18 +--- packages/shell-bson-parser/package.json | 3 +- packages/shell-bson-parser/src/index.spec.ts | 98 ++++++++++++++++++-- packages/shell-bson-parser/src/worker.ts | 11 +-- 4 files changed, 99 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 125b5c38..12598b40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26838,12 +26838,6 @@ "node": ">= 8" } }, - "node_modules/web-worker": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", - "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", - "license": "Apache-2.0" - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -30096,7 +30090,8 @@ "prettier": "^3.8.1", "sinon": "^9.2.3", "typescript": "^5.9.3", - "webpack": "^5.82.0" + "webpack": "^5.82.0", + "webpack-cli": "^5.1.4" }, "peerDependencies": { "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0" @@ -35555,8 +35550,8 @@ "prettier": "^3.8.1", "sinon": "^9.2.3", "typescript": "^5.9.3", - "web-worker": "^1.5.0", - "webpack": "^5.82.0" + "webpack": "^5.82.0", + "webpack-cli": "^5.1.4" }, "dependencies": { "@types/estree": { @@ -51262,11 +51257,6 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" }, - "web-worker": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", - "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==" - }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/packages/shell-bson-parser/package.json b/packages/shell-bson-parser/package.json index 8d6e7f35..844b1901 100644 --- a/packages/shell-bson-parser/package.json +++ b/packages/shell-bson-parser/package.json @@ -74,6 +74,7 @@ "prettier": "^3.8.1", "sinon": "^9.2.3", "typescript": "^5.9.3", - "webpack": "^5.82.0" + "webpack": "^5.82.0", + "webpack-cli": "^5.1.4" } } diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index 6b87b730..a35f9d03 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -1,28 +1,52 @@ import { expect } from 'chai'; +import sinon from 'sinon'; +import vm from 'vm'; +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; import * as api from './index.js'; import { terminateWorker } from './worker-client.js'; +import type { WorkerRequest } from './worker-types.js'; import { - restrictObjectPrototype, - DISALLOWED_PROTOTYPE_PROPS, + handleRequest, restrictGlobalScope, + restrictObjectPrototype, ALLOWED_GLOBALS, + DISALLOWED_PROTOTYPE_PROPS, } from './worker.js'; import { PARSE_TEST_CASES } from './../test/parse-test-cases.js'; +class FakeWorker { + // Exposed so tests can assert on every worker instance ever created. + static instances: FakeWorker[] = []; + onmessage: ((event: { data: unknown }) => void) | null = null; + terminate = sinon.spy(); + + constructor() { + FakeWorker.instances.push(this); + } + + postMessage(message: WorkerRequest) { + queueMicrotask(() => { + const response = handleRequest(structuredClone(message)); + this.onmessage?.({ data: structuredClone(response) }); + }); + } +} + describe('shell-bson-parser with webworker processing', function () { const initialWorkerScriptUrl = process.env.TEST_WORKER_SCRIPT_URL; + const initialGlobalWorker = (globalThis as any).Worker; before(function () { process.env.TEST_WORKER_SCRIPT_URL = '../dist/worker.js'; + (globalThis as any).Worker = FakeWorker; }); after(function () { - if (initialWorkerScriptUrl) { - process.env.TEST_WORKER_SCRIPT_URL = initialWorkerScriptUrl; - } else { - delete process.env.TEST_WORKER_SCRIPT_URL; - } + process.env.TEST_WORKER_SCRIPT_URL = initialWorkerScriptUrl; + (globalThis as any).Worker = initialGlobalWorker; terminateWorker(); }); @@ -117,14 +141,72 @@ describe('shell-bson-parser with webworker processing', function () { }); describe('terminateWorker', function () { - it('starts a new worker after termination', async function () { + beforeEach(function () { + terminateWorker(); + FakeWorker.instances.length = 0; + }); + + it('actually calls terminate() on the underlying worker, then spins up a new one', async function () { const res1 = await api.parse('{code: "BER"}'); expect(res1).to.deep.equal({ code: 'BER' }); + expect(FakeWorker.instances).to.have.lengthOf(1); + const firstWorker = FakeWorker.instances[0]; + expect(firstWorker.terminate.called).to.equal(false); terminateWorker(); + expect(firstWorker.terminate.calledOnce).to.equal(true); const res2 = await api.parse('{city: "berlin"}'); expect(res2).to.deep.equal({ city: 'berlin' }); + expect(FakeWorker.instances).to.have.lengthOf(2); + + expect(FakeWorker.instances[1]).to.not.equal(firstWorker); + expect(FakeWorker.instances[1].terminate.called).to.equal(false); }); }); + + it('strips dangerous globals and locks down Object.prototype when the real worker starts', async function () { + const workerBundlePath = path.join( + path.dirname(fileURLToPath(import.meta.url)), + '..', + 'dist', + 'worker.js', + ); + const code = await fs.readFile(workerBundlePath, 'utf8'); + + const sandbox: Record = Object.create(null); + sandbox.postMessage = function postMessage() {}; + sandbox.fetch = function fetch() {}; + sandbox.require = function require() {}; + sandbox.importScripts = function importScripts() {}; + sandbox.XMLHttpRequest = function XMLHttpRequest() {}; + sandbox.self = sandbox; + sandbox.global = sandbox; + sandbox.globalThis = sandbox; + + vm.createContext(sandbox); + + expect(sandbox).to.have.property('fetch'); + expect(sandbox).to.have.property('require'); + expect(sandbox).to.have.property('importScripts'); + expect(sandbox).to.have.property('XMLHttpRequest'); + + vm.runInContext(code, sandbox, { filename: 'worker.js' }); + + expect(sandbox).to.not.have.property('fetch'); + expect(sandbox).to.not.have.property('require'); + expect(sandbox).to.not.have.property('importScripts'); + expect(sandbox).to.not.have.property('XMLHttpRequest'); + + expect(typeof sandbox.onmessage).to.equal('function'); + + const stillHasProtoAccessor = vm.runInContext( + `Object.prototype.hasOwnProperty('__proto__')`, + sandbox, + ); + expect(stillHasProtoAccessor).to.equal(false); + + // It should not modify the default object proto + expect(Object.prototype).to.have.property('__proto__'); + }); }); diff --git a/packages/shell-bson-parser/src/worker.ts b/packages/shell-bson-parser/src/worker.ts index 63355842..c3923da7 100644 --- a/packages/shell-bson-parser/src/worker.ts +++ b/packages/shell-bson-parser/src/worker.ts @@ -3,14 +3,10 @@ import { markBSON, unmarkBSON } from './structured-clone-bson.js'; import type { WorkerRequest, WorkerResponse } from './worker-types.js'; +const { self } = globalThis; + // Exported for tests export const ALLOWED_GLOBALS = new Set([ - // Used by this file. - 'self', - 'onmessage', - 'postMessage', - - // Needed for parsing. 'Object', 'Array', 'Function', @@ -84,7 +80,6 @@ export const DISALLOWED_PROTOTYPE_PROPS = [ '__defineSetter__', '__lookupGetter__', '__lookupSetter__', - 'constructor', ] as const; // Exported for tests @@ -112,7 +107,7 @@ export function handleRequest(request: WorkerRequest): WorkerResponse { if (typeof self !== 'undefined') { restrictObjectPrototype(); - restrictGlobalScope(self); + restrictGlobalScope(globalThis); self.onmessage = (event: MessageEvent) => { (self as unknown as Worker).postMessage(handleRequest(event.data)); }; From a8d8344c771706732098632fe7adca631796cd3e Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 23 Sep 2026 16:39:27 +0200 Subject: [PATCH 4/8] fix tests --- packages/shell-bson-parser/src/index.spec.ts | 45 ++++++-------------- packages/shell-bson-parser/src/worker.ts | 3 ++ 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index a35f9d03..3a95da2c 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -4,12 +4,11 @@ import vm from 'vm'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; +import * as WebWorkerModule from 'web-worker'; import * as api from './index.js'; import { terminateWorker } from './worker-client.js'; -import type { WorkerRequest } from './worker-types.js'; import { - handleRequest, restrictGlobalScope, restrictObjectPrototype, ALLOWED_GLOBALS, @@ -17,36 +16,18 @@ import { } from './worker.js'; import { PARSE_TEST_CASES } from './../test/parse-test-cases.js'; -class FakeWorker { - // Exposed so tests can assert on every worker instance ever created. - static instances: FakeWorker[] = []; - onmessage: ((event: { data: unknown }) => void) | null = null; - terminate = sinon.spy(); - - constructor() { - FakeWorker.instances.push(this); - } - - postMessage(message: WorkerRequest) { - queueMicrotask(() => { - const response = handleRequest(structuredClone(message)); - this.onmessage?.({ data: structuredClone(response) }); - }); - } -} +const WebWorker = (WebWorkerModule as unknown as { default: typeof Worker }) + .default; describe('shell-bson-parser with webworker processing', function () { const initialWorkerScriptUrl = process.env.TEST_WORKER_SCRIPT_URL; - const initialGlobalWorker = (globalThis as any).Worker; before(function () { process.env.TEST_WORKER_SCRIPT_URL = '../dist/worker.js'; - (globalThis as any).Worker = FakeWorker; }); after(function () { process.env.TEST_WORKER_SCRIPT_URL = initialWorkerScriptUrl; - (globalThis as any).Worker = initialGlobalWorker; terminateWorker(); }); @@ -75,9 +56,9 @@ describe('shell-bson-parser with webworker processing', function () { expect(scope).to.not.have.property('fetch'); expect(scope).to.not.have.property('require'); - expect(scope).to.not.have.property('process'); expect(scope).to.not.have.property('importScripts'); expect(scope).to.not.have.property('XMLHttpRequest'); + expect(scope).to.have.property('process'); expect(scope).to.have.property('Object', Object); expect(scope).to.have.property('Array', Array); expect(scope).to.have.property('Math', Math); @@ -141,27 +122,29 @@ describe('shell-bson-parser with webworker processing', function () { }); describe('terminateWorker', function () { + let terminateSpy: sinon.SinonSpy; + beforeEach(function () { + terminateSpy = sinon.spy(WebWorker.prototype, 'terminate'); + }); + + afterEach(function () { + terminateSpy.restore(); terminateWorker(); - FakeWorker.instances.length = 0; }); it('actually calls terminate() on the underlying worker, then spins up a new one', async function () { const res1 = await api.parse('{code: "BER"}'); expect(res1).to.deep.equal({ code: 'BER' }); - expect(FakeWorker.instances).to.have.lengthOf(1); - const firstWorker = FakeWorker.instances[0]; - expect(firstWorker.terminate.called).to.equal(false); + expect(terminateSpy.called).to.equal(false); terminateWorker(); - expect(firstWorker.terminate.calledOnce).to.equal(true); + expect(terminateSpy.calledOnce).to.equal(true); const res2 = await api.parse('{city: "berlin"}'); expect(res2).to.deep.equal({ city: 'berlin' }); - expect(FakeWorker.instances).to.have.lengthOf(2); - expect(FakeWorker.instances[1]).to.not.equal(firstWorker); - expect(FakeWorker.instances[1].terminate.called).to.equal(false); + expect(terminateSpy.calledOnce).to.equal(true); }); }); diff --git a/packages/shell-bson-parser/src/worker.ts b/packages/shell-bson-parser/src/worker.ts index c3923da7..1dfa169c 100644 --- a/packages/shell-bson-parser/src/worker.ts +++ b/packages/shell-bson-parser/src/worker.ts @@ -59,6 +59,9 @@ export const ALLOWED_GLOBALS = new Set([ 'encodeURIComponent', 'decodeURIComponent', 'Buffer', + + // web-worker module relies on process as it supports both node and browser. + 'process', ]); // Exported for tests From 6a6da6d02cd4efe5bac536836cc1e348875b7ed1 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 23 Sep 2026 16:47:35 +0200 Subject: [PATCH 5/8] check --- package-lock.json | 18 ++++++++++++++---- packages/shell-bson-parser/package.json | 3 +-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12598b40..125b5c38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26838,6 +26838,12 @@ "node": ">= 8" } }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -30090,8 +30096,7 @@ "prettier": "^3.8.1", "sinon": "^9.2.3", "typescript": "^5.9.3", - "webpack": "^5.82.0", - "webpack-cli": "^5.1.4" + "webpack": "^5.82.0" }, "peerDependencies": { "bson": "^4.6.3 || ^5 || ^6.10.3 || ^7.0.0" @@ -35550,8 +35555,8 @@ "prettier": "^3.8.1", "sinon": "^9.2.3", "typescript": "^5.9.3", - "webpack": "^5.82.0", - "webpack-cli": "^5.1.4" + "web-worker": "^1.5.0", + "webpack": "^5.82.0" }, "dependencies": { "@types/estree": { @@ -51257,6 +51262,11 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" }, + "web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==" + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/packages/shell-bson-parser/package.json b/packages/shell-bson-parser/package.json index 844b1901..8d6e7f35 100644 --- a/packages/shell-bson-parser/package.json +++ b/packages/shell-bson-parser/package.json @@ -74,7 +74,6 @@ "prettier": "^3.8.1", "sinon": "^9.2.3", "typescript": "^5.9.3", - "webpack": "^5.82.0", - "webpack-cli": "^5.1.4" + "webpack": "^5.82.0" } } From c10762a8299b8fab12e1bf2e144e2344b327f431 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 28 Sep 2026 14:11:32 +0200 Subject: [PATCH 6/8] use bson utils instead of buffer --- packages/shell-bson-parser/src/scope.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/shell-bson-parser/src/scope.ts b/packages/shell-bson-parser/src/scope.ts index dfb1167c..11ea0146 100644 --- a/packages/shell-bson-parser/src/scope.ts +++ b/packages/shell-bson-parser/src/scope.ts @@ -73,7 +73,7 @@ const SCOPE_ANY: { [x: string]: Function } = lookupMap({ String.prototype.substring.call(lsb, 0, 2); hex = msb + lsb; - const hexBuffer = Buffer.from(hex, 'hex'); + const hexBuffer = bson.ByteUtils.fromHex(hex); return new bson.Binary(hexBuffer, 3); }, LegacyCSharpUUID: function (u: any) { @@ -97,7 +97,7 @@ const SCOPE_ANY: { [x: string]: Function } = lookupMap({ const d = String.prototype.substring.call(hex, 16, 32); hex = a + b + c + d; - const hexBuffer = Buffer.from(hex, 'hex'); + const hexBuffer = bson.ByteUtils.fromHex(hex); return new bson.Binary(hexBuffer, 3); }, LegacyPythonUUID: function (u: any) { @@ -106,21 +106,20 @@ const SCOPE_ANY: { [x: string]: Function } = lookupMap({ } return new bson.Binary( - Buffer.from( + bson.ByteUtils.fromHex( String.prototype.replace.call(u, /[{}-]/g, () => ''), - 'hex', ), 3, ); }, BinData: function (t: any, d: any) { - return new bson.Binary(Buffer.from(d, 'base64'), t); + return new bson.Binary(bson.ByteUtils.fromBase64(d), t); }, UUID: function (u: any) { if (u === undefined) { return new bson.UUID().toBinary(); } - return new bson.Binary(Buffer.from(u.replace(/-/g, ''), 'hex'), 4); + return new bson.Binary(bson.ByteUtils.fromHex(u.replace(/-/g, '')), 4); }, Code: function (c: any, s: any) { return new bson.Code(c, s); From d2a87bade80334c75e354b95067c575fb22767a2 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 28 Sep 2026 14:15:23 +0200 Subject: [PATCH 7/8] clean up postMessage --- packages/shell-bson-parser/src/worker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shell-bson-parser/src/worker.ts b/packages/shell-bson-parser/src/worker.ts index 1dfa169c..2beefb3d 100644 --- a/packages/shell-bson-parser/src/worker.ts +++ b/packages/shell-bson-parser/src/worker.ts @@ -3,7 +3,7 @@ import { markBSON, unmarkBSON } from './structured-clone-bson.js'; import type { WorkerRequest, WorkerResponse } from './worker-types.js'; -const { self } = globalThis; +const { self, postMessage } = globalThis; // Exported for tests export const ALLOWED_GLOBALS = new Set([ @@ -112,6 +112,6 @@ if (typeof self !== 'undefined') { restrictObjectPrototype(); restrictGlobalScope(globalThis); self.onmessage = (event: MessageEvent) => { - (self as unknown as Worker).postMessage(handleRequest(event.data)); + postMessage(handleRequest(event.data)); }; } From 9ca9089e05ae006ec1d9e8e73065a21c3a6274ab Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 28 Sep 2026 14:38:10 +0200 Subject: [PATCH 8/8] clean up --- packages/shell-bson-parser/src/index.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index 3a95da2c..34f3964a 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -27,7 +27,11 @@ describe('shell-bson-parser with webworker processing', function () { }); after(function () { - process.env.TEST_WORKER_SCRIPT_URL = initialWorkerScriptUrl; + if (initialWorkerScriptUrl) { + process.env.TEST_WORKER_SCRIPT_URL = initialWorkerScriptUrl; + } else { + delete process.env.TEST_WORKER_SCRIPT_URL; + } terminateWorker(); });