From 71d35eddf7d68b09dea0e26035191c7b2b4b9050 Mon Sep 17 00:00:00 2001 From: suyao Date: Wed, 8 Oct 2025 07:11:39 +0800 Subject: [PATCH] Add JavaScript execution safety limits and improve error handling - Add 1MB code size limit to prevent memory issues - Improve timeout error handling with proper cleanup logging - Remove host environment exposure and fix file descriptor leaks in worker --- src/main/mcpServers/js.ts | 2 +- src/main/services/JsService.ts | 10 +++++++++- src/main/services/workers/JsWorker.ts | 11 +++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/mcpServers/js.ts b/src/main/mcpServers/js.ts index b83b16da48..3a3cbb0bc7 100644 --- a/src/main/mcpServers/js.ts +++ b/src/main/mcpServers/js.ts @@ -107,7 +107,7 @@ class JsServer { timeout: timeout ?? DEFAULT_TIMEOUT }) - const { combinedOutput, isError } = formatExecutionResult(result as any) + const { combinedOutput, isError } = formatExecutionResult(result) return { content: [ diff --git a/src/main/services/JsService.ts b/src/main/services/JsService.ts index 9435fceeb0..949591b82a 100644 --- a/src/main/services/JsService.ts +++ b/src/main/services/JsService.ts @@ -41,6 +41,12 @@ export class JsService { throw new Error('JavaScript code must be a non-empty string') } + // Limit code size to 1MB to prevent memory issues + const MAX_CODE_SIZE = 1_000_000 + if (code.length > MAX_CODE_SIZE) { + throw new Error(`JavaScript code exceeds maximum size of ${MAX_CODE_SIZE / 1_000_000}MB`) + } + return new Promise((resolve, reject) => { const worker = createJsWorker({ workerData: { code }, @@ -98,7 +104,9 @@ export class JsService { timeoutId = setTimeout(() => { logger.warn(`JavaScript execution timed out after ${timeout}ms`) - void settleError(new Error('JavaScript execution timed out')) + settleError(new Error('JavaScript execution timed out')).catch((err) => { + logger.error('Error during timeout cleanup:', err instanceof Error ? err : new Error(String(err))) + }) }, timeout) }) } diff --git a/src/main/services/workers/JsWorker.ts b/src/main/services/workers/JsWorker.ts index a6acdb68e9..70ba0c24a6 100644 --- a/src/main/services/workers/JsWorker.ts +++ b/src/main/services/workers/JsWorker.ts @@ -1,7 +1,6 @@ import { mkdtemp, open, readFile, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { env } from 'node:process' import { WASI } from 'node:wasi' import { parentPort, workerData } from 'node:worker_threads' @@ -40,7 +39,7 @@ async function runQuickJsInSandbox(jsCode: string): Promise { const wasi = new WASI({ version: 'preview1', args: ['qjs', '-e', jsCode], - env, + env: {}, // Empty environment for security - don't expose host env vars stdin: 0, stdout: stdoutHandle.fd, stderr: stderrHandle.fd, @@ -60,10 +59,14 @@ async function runQuickJsInSandbox(jsCode: string): Promise { } } - await stdoutHandle.close() + // Close handles before reading files to prevent descriptor leak + const _stdoutHandle = stdoutHandle stdoutHandle = undefined - await stderrHandle.close() + await _stdoutHandle.close() + + const _stderrHandle = stderrHandle stderrHandle = undefined + await _stderrHandle.close() const capturedStdout = await readFile(stdoutPath, 'utf8') const capturedStderr = await readFile(stderrPath, 'utf8')