3759 lines · 104.4 KB · TypeScript
RawDownload
import {	cbc as nobleAesCbc,	ctr as nobleAesCtr,	gcm as nobleAesGcm,} from "@noble/ciphers/aes.js";import { hmac as nobleHmac } from "@noble/hashes/hmac.js";import { md5, sha1 } from "@noble/hashes/legacy.js";import { pbkdf2 as noblePbkdf2 } from "@noble/hashes/pbkdf2.js";import { scrypt as nobleScrypt } from "@noble/hashes/scrypt.js";import { sha224, sha256, sha384, sha512 } from "@noble/hashes/sha2.js";import { transform } from "sucrase";import type {	BrowserChildProcessPollEvent,	BrowserChildProcessSpawnRequest,} from "./child-process-bridge.js";import { createBrowserNetworkAdapter } from "./driver.js";import { base64ToBytes, toUint8Array } from "./encoding.js";import { posixErrno } from "./errno.js";import type {	ExecResult,	NetworkAdapter,	RunResult,	StdioChannel,	TimingMitigation,	VirtualDirEntry,	VirtualStat,} from "./runtime.js";import {	createNetworkStub,	exposeCustomGlobal,	exposeMutableRuntimeStateGlobal,	getIsolateRuntimeSource,	getRequireSetupCode,	getRuntimePolyfillCode,	isESM,	transformDynamicImport,} from "./runtime.js";import {	defaultSignalExitCode,	PROCESS_SIGNAL_NUMBERS,	signalNumberForEvent,} from "./signals.js";import {	assertBrowserSyncBridgeSupport,	type BrowserSyncBridgeErrorPayload,	type BrowserSyncBridgePayload,	type BrowserWorkerSyncOperation,	SYNC_BRIDGE_KIND_BINARY,	SYNC_BRIDGE_KIND_JSON,	SYNC_BRIDGE_KIND_NONE,	SYNC_BRIDGE_KIND_TEXT,	SYNC_BRIDGE_SIGNAL_KIND_INDEX,	SYNC_BRIDGE_SIGNAL_LENGTH_INDEX,	SYNC_BRIDGE_SIGNAL_STATE_IDLE,	SYNC_BRIDGE_SIGNAL_STATE_INDEX,	SYNC_BRIDGE_SIGNAL_STATUS_INDEX,	SYNC_BRIDGE_STATUS_ERROR,} from "./sync-bridge.js";import type {	BrowserWorkerExecOptions,	BrowserWorkerInitPayload,	BrowserWorkerOutboundMessage,	BrowserWorkerRequestMessage,} from "./worker-protocol.js"; let networkAdapter: NetworkAdapter | null = null;let initialized = false;let controlToken: string | null = null;let runtimeTimingMitigation: TimingMitigation = "freeze";let runtimeProcessConfig: Record<string, unknown> | null = null;let activeProcessRequestId: number | null = null;let activeExecutionId: string | null = null;let activeCaptureStdio = false;let activeSyncBridge: ReturnType<typeof createSyncBridgeClient> | null = null;const pendingExecutionSignals = new Map<string, (signal: number) => void>(); const dynamicImportCache = new Map<string, unknown>();// For a PERSISTENT execution (ExecOptions.persistent): process.exit resolves this// instead of throwing, so a long-running stdio program's async exit (e.g. an ACP agent// exiting on stdin EOF, from an async callback that can't be caught by the sync exec// wrapper) cleanly ends the run. Null in run-to-completion mode (exit throws as before).let persistentExitResolver: ((code: number) => void) | null = null;// Streaming stdin for a persistent execution: the host feeds more stdin while the program// runs (and ends it explicitly), rather than the one-shot exec stdin that auto-ends. Lets// the host drive a long-running stdio program (e.g. an ACP agent: write a request, read// the reply, write the next) as a proper external client.let streamingStdinEnabled = false;let activeStdinPush: ((data: string) => void) | null = null;let activeStdinEnd: (() => void) | null = null;// Safety bound so a persistent program that never exits can't hang the worker.const PERSISTENT_EXEC_TIMEOUT_MS = 120_000;const MAX_ERROR_MESSAGE_CHARS = 8192;const MAX_STDIO_MESSAGE_CHARS = 8192; function eventForSignalNumber(signal: number): string {	return (		Object.entries(PROCESS_SIGNAL_NUMBERS).find(			([, value]) => value === signal,		)?.[0] ?? `SIG${signal}`	);} // Payload size defaults matching the Node runtime pathconst DEFAULT_BASE64_TRANSFER_BYTES = 16 * 1024 * 1024;const DEFAULT_JSON_PAYLOAD_BYTES = 4 * 1024 * 1024;const PAYLOAD_LIMIT_ERROR_CODE = "ERR_SANDBOX_PAYLOAD_TOO_LARGE";const DEFAULT_SCRYPT_COST = 16_384;const DEFAULT_SCRYPT_BLOCK_SIZE = 8;const DEFAULT_SCRYPT_PARALLELIZATION = 1; let base64TransferLimitBytes = DEFAULT_BASE64_TRANSFER_BYTES;let jsonPayloadLimitBytes = DEFAULT_JSON_PAYLOAD_BYTES; const encoder = new TextEncoder();const decoder = new TextDecoder();// biome-ignore lint/security/noGlobalEval: the browser worker intentionally evaluates isolated runtime source strings.const globalEval = eval as (source: string) => unknown;const SHARED_ARRAY_BUFFER_FREEZE_KEYS = [	"byteLength",	"slice",	"grow",	"maxByteLength",	"growable",] as const; type TimingGlobalsSnapshot = {	captured: boolean;	dateDescriptor?: PropertyDescriptor;	dateValue?: DateConstructor;	performanceDescriptor?: PropertyDescriptor;	performanceValue?: Performance;	sharedArrayBufferDescriptor?: PropertyDescriptor;	sharedArrayBufferValue?: typeof SharedArrayBuffer;	sharedArrayBufferPrototypeDescriptors: Map<		string,		PropertyDescriptor | undefined	>;}; const timingGlobals: TimingGlobalsSnapshot = {	captured: false,	sharedArrayBufferPrototypeDescriptors: new Map(),}; function getUtf8ByteLength(text: string): number {	return encoder.encode(text).byteLength;} function getRequiredControlToken(): string {	if (!controlToken) {		throw new Error(			"Browser runtime worker control channel is not initialized",		);	}	return controlToken;} function captureTimingGlobals(): void {	if (timingGlobals.captured) {		return;	} 	timingGlobals.captured = true;	timingGlobals.dateDescriptor = Object.getOwnPropertyDescriptor(		globalThis,		"Date",	);	timingGlobals.dateValue = globalThis.Date;	timingGlobals.performanceDescriptor = Object.getOwnPropertyDescriptor(		globalThis,		"performance",	);	timingGlobals.performanceValue = globalThis.performance;	timingGlobals.sharedArrayBufferDescriptor = Object.getOwnPropertyDescriptor(		globalThis,		"SharedArrayBuffer",	);	timingGlobals.sharedArrayBufferValue = globalThis.SharedArrayBuffer; 	const sharedArrayBufferCtor = globalThis.SharedArrayBuffer;	if (typeof sharedArrayBufferCtor !== "function") {		return;	} 	const prototype = sharedArrayBufferCtor.prototype as Record<string, unknown>;	for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {		timingGlobals.sharedArrayBufferPrototypeDescriptors.set(			key,			Object.getOwnPropertyDescriptor(prototype, key),		);	}} function restoreGlobalProperty(	name: "Date" | "performance" | "SharedArrayBuffer",	descriptor?: PropertyDescriptor,): void {	if (descriptor) {		try {			Object.defineProperty(globalThis, name, descriptor);			return;		} catch {			if ("value" in descriptor) {				(globalThis as Record<string, unknown>)[name] = descriptor.value;				return;			}		}	} 	Reflect.deleteProperty(globalThis, name);} function restoreSharedArrayBufferPrototype(): void {	const sharedArrayBufferCtor = timingGlobals.sharedArrayBufferValue;	if (typeof sharedArrayBufferCtor !== "function") {		return;	} 	const prototype = sharedArrayBufferCtor.prototype as Record<string, unknown>;	for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {		const descriptor =			timingGlobals.sharedArrayBufferPrototypeDescriptors.get(key);		try {			if (descriptor) {				Object.defineProperty(prototype, key, descriptor);			} else {				delete prototype[key];			}		} catch {			// Ignore non-configurable SharedArrayBuffer prototype properties.		}	}} function restoreTimingMitigationOff(): void {	captureTimingGlobals();	restoreGlobalProperty("Date", timingGlobals.dateDescriptor);	restoreGlobalProperty("performance", timingGlobals.performanceDescriptor);	restoreSharedArrayBufferPrototype();	restoreGlobalProperty(		"SharedArrayBuffer",		timingGlobals.sharedArrayBufferDescriptor,	); 	if (		typeof globalThis.performance === "undefined" ||		globalThis.performance === null	) {		Object.defineProperty(globalThis, "performance", {			value: {				now: () => Date.now(),			},			configurable: true,			writable: true,		});	}} function applyTimingMitigation(	timingMitigation: TimingMitigation,	frozenTimeMs?: number,): number | undefined {	captureTimingGlobals();	restoreTimingMitigationOff();	if (timingMitigation !== "freeze") {		return undefined;	} 	const frozenTimeValue =		typeof frozenTimeMs === "number" && Number.isFinite(frozenTimeMs)			? Math.trunc(frozenTimeMs)			: Date.now();	const originalDate =		timingGlobals.dateValue ?? timingGlobals.dateDescriptor?.value ?? Date;	const frozenDateNow = () => frozenTimeValue;	const FrozenDate = function (...args: unknown[]) {		if (new.target) {			if (args.length === 0) {				return new originalDate(frozenTimeValue);			}			return new originalDate(				...(args as ConstructorParameters<DateConstructor>),			);		}		return originalDate();	} as unknown as DateConstructor;	Object.defineProperty(FrozenDate, "prototype", {		value: originalDate.prototype,		writable: false,		configurable: false,	});	Object.defineProperty(FrozenDate, "now", {		value: frozenDateNow,		configurable: true,		writable: false,	});	FrozenDate.parse = originalDate.parse;	FrozenDate.UTC = originalDate.UTC;	try {		Object.defineProperty(globalThis, "Date", {			value: FrozenDate,			configurable: true,			writable: false,		});	} catch {		(globalThis as Record<string, unknown>).Date = FrozenDate;	} 	const frozenPerformance = Object.create(null) as Record<string, unknown>;	const originalPerformance = timingGlobals.performanceValue;	if (		typeof originalPerformance !== "undefined" &&		originalPerformance !== null	) {		const source = originalPerformance as unknown as Record<string, unknown>;		for (const key of Object.getOwnPropertyNames(			Object.getPrototypeOf(originalPerformance) ?? originalPerformance,		)) {			if (key === "now") {				continue;			}			try {				const value = source[key];				frozenPerformance[key] =					typeof value === "function" ? value.bind(originalPerformance) : value;			} catch {				// Ignore performance accessors that throw in this host.			}		}	}	Object.defineProperty(frozenPerformance, "now", {		value: () => 0,		configurable: true,		writable: false,	});	Object.freeze(frozenPerformance);	try {		Object.defineProperty(globalThis, "performance", {			value: frozenPerformance,			configurable: true,			writable: false,		});	} catch {		(globalThis as Record<string, unknown>).performance = frozenPerformance;	} 	const sharedArrayBufferCtor = timingGlobals.sharedArrayBufferValue;	if (typeof sharedArrayBufferCtor === "function") {		const prototype = sharedArrayBufferCtor.prototype as Record<			string,			unknown		>;		for (const key of SHARED_ARRAY_BUFFER_FREEZE_KEYS) {			try {				Object.defineProperty(prototype, key, {					get() {						throw new TypeError(							"SharedArrayBuffer is not available in sandbox",						);					},					configurable: true,				});			} catch {				// Ignore non-configurable SharedArrayBuffer prototype properties.			}		}	}	try {		Object.defineProperty(globalThis, "SharedArrayBuffer", {			value: undefined,			configurable: true,			writable: false,			enumerable: false,		});	} catch {		Reflect.deleteProperty(globalThis, "SharedArrayBuffer");	} 	return frozenTimeValue;} function assertPayloadByteLength(	payloadLabel: string,	actualBytes: number,	maxBytes: number,): void {	if (actualBytes <= maxBytes) return;	const error = new Error(		`[${PAYLOAD_LIMIT_ERROR_CODE}] ${payloadLabel}: payload is ${actualBytes} bytes, limit is ${maxBytes} bytes`,	);	(error as { code?: string }).code = PAYLOAD_LIMIT_ERROR_CODE;	throw error;} function assertTextPayloadSize(	payloadLabel: string,	text: string,	maxBytes: number,): void {	assertPayloadByteLength(payloadLabel, getUtf8ByteLength(text), maxBytes);} interface BrowserScryptOptions {	cost?: unknown;	N?: unknown;	blockSize?: unknown;	r?: unknown;	parallelization?: unknown;	p?: unknown;} function parseScryptOptions(value: unknown): BrowserScryptOptions {	if (value == null) return {};	if (typeof value === "string") {		const parsed = JSON.parse(value);		return typeof parsed === "object" && parsed !== null			? (parsed as BrowserScryptOptions)			: {};	}	return typeof value === "object" ? (value as BrowserScryptOptions) : {};} function normalizeScryptPositiveInteger(	value: unknown,	fallback: number,	label: string,): number {	const normalized = value == null ? fallback : Number(value);	if (!Number.isInteger(normalized) || normalized <= 0) {		throw new Error(`crypto.scrypt ${label} must be a positive integer`);	}	return normalized;} function normalizeScryptOptions(value: unknown, keyLength: unknown) {	const options = parseScryptOptions(value);	const length = Number(keyLength);	if (!Number.isInteger(length) || length < 0) {		throw new Error("crypto.scrypt key length must be a non-negative integer");	}	const cost = normalizeScryptPositiveInteger(		options.cost ?? options.N,		DEFAULT_SCRYPT_COST,		"cost",	);	if ((cost & (cost - 1)) !== 0) {		throw new Error("crypto.scrypt cost must be a positive power of two");	}	return {		N: cost,		r: normalizeScryptPositiveInteger(			options.blockSize ?? options.r,			DEFAULT_SCRYPT_BLOCK_SIZE,			"block size",		),		p: normalizeScryptPositiveInteger(			options.parallelization ?? options.p,			DEFAULT_SCRYPT_PARALLELIZATION,			"parallelization",		),		dkLen: length,	};} const BROWSER_CRYPTO_HASHES = {	md5,	sha1,	sha224,	sha256,	sha384,	sha512,}; type BrowserCryptoHashName = keyof typeof BROWSER_CRYPTO_HASHES; function normalizeCryptoHashName(algorithm: unknown): BrowserCryptoHashName {	const normalized = String(algorithm)		.trim()		.toLowerCase()		.replace(/[-_]/g, "");	if (Object.hasOwn(BROWSER_CRYPTO_HASHES, normalized)) {		return normalized as BrowserCryptoHashName;	}	throw new Error(`Unsupported browser crypto digest algorithm: ${algorithm}`);} const RSA_PKCS1_DIGEST_PREFIXES: Record<BrowserCryptoHashName, string> = {	md5: "3020300c06082a864886f70d020505000410",	sha1: "3021300906052b0e03021a05000414",	sha224: "302d300d06096086480165030402040500041c",	sha256: "3031300d060960864801650304020105000420",	sha384: "3041300d060960864801650304020205000430",	sha512: "3051300d060960864801650304020305000440",}; function browserCryptoHash(algorithm: unknown) {	return BROWSER_CRYPTO_HASHES[normalizeCryptoHashName(algorithm)];} function hashDigestBytes(algorithm: unknown, data: unknown): Uint8Array {

Lines 1–500 of 3759.