diff --git a/docs/architecture/README.md b/docs/architecture/README.md index e0f6990..5f36941 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -94,7 +94,7 @@ Key implications: - The provider/tool surface is the capability boundary, not the JavaScript syntax itself. - Fresh runtimes, schema validation, JSON-only boundaries, timeouts, memory limits, and bounded logs are defense-in-depth features. -- In-process execution still shares the host process. Use a separate process, container, VM, or similar boundary when the code source is hostile or multi-tenant. +- In-process and worker-hosted execution still share the host process. Use `@execbox/remote` behind a separate process, container, VM, or similar boundary when the code source is hostile or multi-tenant. - Wrapping third-party MCP servers is a separate dependency-trust decision from letting end users author guest code. ## Architecture In One Paragraph diff --git a/docs/architecture/execbox-executors.md b/docs/architecture/execbox-executors.md index 8a82011..9a8ae0f 100644 --- a/docs/architecture/execbox-executors.md +++ b/docs/architecture/execbox-executors.md @@ -114,7 +114,7 @@ If all shells are busy and the pool is already at `maxSize`, the next `acquire() - Successful executions return the shell to the pool. - Normal guest/runtime/tool failures also return the shell, because they do not imply a poisoned host shell. -- `timeout` and `internal_error` results evict the shell, because those outcomes mean the worker/child or transport state may no longer be trustworthy. +- `timeout` and `internal_error` results evict the shell, because those outcomes mean the worker or transport state may no longer be trustworthy. - Idle pooled shells are evicted after `idleTimeoutMs`, down to `minSize`. - `dispose()` tears down the executor-owned pool and any idle shells it still owns. diff --git a/docs/architecture/index.md b/docs/architecture/index.md index 35d2b08..1960dff 100644 --- a/docs/architecture/index.md +++ b/docs/architecture/index.md @@ -55,5 +55,5 @@ Key implications: - The provider/tool surface is the capability boundary, not the JavaScript syntax itself. - Fresh runtimes, schema validation, JSON-only boundaries, timeouts, memory limits, and bounded logs are defense-in-depth features. -- In-process execution still shares the host process. Use a separate process, container, VM, or similar boundary when the code source is hostile or multi-tenant. +- In-process and worker-hosted execution still share the host process. Use `@execbox/remote` behind a separate process, container, VM, or similar boundary when the code source is hostile or multi-tenant. - Wrapping third-party MCP servers is a separate dependency-trust decision from letting end users author guest code. diff --git a/package.json b/package.json index b8e122a..406990c 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "package:check": "npm_config_cache=$PWD/.npm-cache CI=1 npm run build", "test": "vitest run", "test:dist-smoke": "node --import tsx scripts/test-dist-smoke.ts", - "test:security": "node ./node_modules/vitest/vitest.mjs run packages/core/__tests__/security/isJsonSerializable.test.ts packages/core/__tests__/core/createToolCallDispatcher.test.ts packages/core/__tests__/protocol/hostSession.test.ts packages/quickjs/__tests__/protocolEndpoint.test.ts packages/quickjs/__tests__/remoteEndpoint.test.ts && node ./node_modules/vitest/vitest.mjs run packages/core/__tests__/mcp/penetration.test.ts packages/remote/__tests__/penetration.test.ts packages/quickjs/__tests__/hostedPenetration.test.ts && node ./node_modules/vitest/vitest.mjs run packages/quickjs/__tests__/workerHostLifecycle.test.ts", + "test:security": "vitest run packages/*/__tests__/security", "test:watch": "vitest", "typecheck": "tsc --noEmit && node --import tsx scripts/check-workspace-entrypoints.ts" }, diff --git a/packages/core/__tests__/core/createToolCallDispatcher.test.ts b/packages/core/__tests__/security/createToolCallDispatcher.test.ts similarity index 100% rename from packages/core/__tests__/core/createToolCallDispatcher.test.ts rename to packages/core/__tests__/security/createToolCallDispatcher.test.ts diff --git a/packages/core/__tests__/mcp/penetration.test.ts b/packages/core/__tests__/security/mcp/penetration.test.ts similarity index 65% rename from packages/core/__tests__/mcp/penetration.test.ts rename to packages/core/__tests__/security/mcp/penetration.test.ts index 53ee105..871f89c 100644 --- a/packages/core/__tests__/mcp/penetration.test.ts +++ b/packages/core/__tests__/security/mcp/penetration.test.ts @@ -1,6 +1,6 @@ import { QuickJsExecutor } from "@execbox/quickjs"; -import { runWrappedMcpPenetrationSuite } from "../../test-support/runWrappedMcpPenetrationSuite"; +import { runWrappedMcpPenetrationSuite } from "../../../test-support/runWrappedMcpPenetrationSuite"; runWrappedMcpPenetrationSuite( "QuickJS wrapped MCP penetration tests", diff --git a/packages/core/__tests__/protocol/hostSession.test.ts b/packages/core/__tests__/security/protocol/hostSession.test.ts similarity index 100% rename from packages/core/__tests__/protocol/hostSession.test.ts rename to packages/core/__tests__/security/protocol/hostSession.test.ts diff --git a/packages/core/src/protocol/nodeBootstrap.ts b/packages/core/src/protocol/nodeBootstrap.ts index 6ff5259..3e72e9a 100644 --- a/packages/core/src/protocol/nodeBootstrap.ts +++ b/packages/core/src/protocol/nodeBootstrap.ts @@ -1,7 +1,7 @@ const SOURCE_MODE_EXEC_ARGV = ["--conditions=source", "--import", "tsx"]; /** - * Returns the extra Node flags needed to launch transport-backed child entries + * Returns the extra Node flags needed to launch transport-backed worker entries * directly from source during local development and tests. */ export function getNodeTransportExecArgv( diff --git a/packages/quickjs/__tests__/hostedPenetration.test.ts b/packages/quickjs/__tests__/security/hostedPenetration.test.ts similarity index 53% rename from packages/quickjs/__tests__/hostedPenetration.test.ts rename to packages/quickjs/__tests__/security/hostedPenetration.test.ts index f124be3..e6d00d5 100644 --- a/packages/quickjs/__tests__/hostedPenetration.test.ts +++ b/packages/quickjs/__tests__/security/hostedPenetration.test.ts @@ -1,5 +1,5 @@ -import { QuickJsExecutor } from "../src/index"; -import { runWrappedMcpPenetrationSuite } from "../../core/test-support/runWrappedMcpPenetrationSuite"; +import { runWrappedMcpPenetrationSuite } from "../../../core/test-support/runWrappedMcpPenetrationSuite"; +import { QuickJsExecutor } from "../../src/index"; runWrappedMcpPenetrationSuite( "QuickJsExecutor worker host wrapped MCP", diff --git a/packages/quickjs/__tests__/protocolEndpoint.test.ts b/packages/quickjs/__tests__/security/protocolEndpoint.test.ts similarity index 97% rename from packages/quickjs/__tests__/protocolEndpoint.test.ts rename to packages/quickjs/__tests__/security/protocolEndpoint.test.ts index cad885d..669f319 100644 --- a/packages/quickjs/__tests__/protocolEndpoint.test.ts +++ b/packages/quickjs/__tests__/security/protocolEndpoint.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import type { DispatcherMessage, RunnerMessage } from "@execbox/core/protocol"; -import { attachQuickJsProtocolEndpoint } from "../src/runner/protocolEndpoint"; +import { attachQuickJsProtocolEndpoint } from "../../src/runner/protocolEndpoint"; const runtimeOptions = { maxLogChars: 64_000, diff --git a/packages/quickjs/__tests__/remoteEndpoint.test.ts b/packages/quickjs/__tests__/security/remoteEndpoint.test.ts similarity index 98% rename from packages/quickjs/__tests__/remoteEndpoint.test.ts rename to packages/quickjs/__tests__/security/remoteEndpoint.test.ts index b716b62..d305df7 100644 --- a/packages/quickjs/__tests__/remoteEndpoint.test.ts +++ b/packages/quickjs/__tests__/security/remoteEndpoint.test.ts @@ -6,7 +6,7 @@ import type { TransportCloseReason, } from "@execbox/core/protocol"; -import { attachQuickJsRemoteEndpoint } from "../src/remoteEndpoint"; +import { attachQuickJsRemoteEndpoint } from "../../src/remoteEndpoint"; type MessageHandler = (message: DispatcherMessage) => void; type CloseHandler = (reason?: TransportCloseReason) => void; diff --git a/packages/quickjs/__tests__/workerHostLifecycle.test.ts b/packages/quickjs/__tests__/security/workerHostLifecycle.test.ts similarity index 91% rename from packages/quickjs/__tests__/workerHostLifecycle.test.ts rename to packages/quickjs/__tests__/security/workerHostLifecycle.test.ts index 4873fba..322f277 100644 --- a/packages/quickjs/__tests__/workerHostLifecycle.test.ts +++ b/packages/quickjs/__tests__/security/workerHostLifecycle.test.ts @@ -39,7 +39,7 @@ describe("QuickJsExecutor worker host lifecycle", () => { }); it("returns internal_error when the worker exits before sending a result", async () => { - const { QuickJsExecutor } = await import("../src/index"); + const { QuickJsExecutor } = await import("../../src/index"); const executor = new QuickJsExecutor({ host: "worker" }); const result = await executor.execute("1 + 1", []); @@ -54,7 +54,7 @@ describe("QuickJsExecutor worker host lifecycle", () => { }); it("uses explicit source bootstrap conditions in repo source mode", async () => { - const { QuickJsExecutor } = await import("../src/index"); + const { QuickJsExecutor } = await import("../../src/index"); const executor = new QuickJsExecutor({ host: "worker" }); await executor.execute("1 + 1", []); @@ -71,7 +71,7 @@ describe("QuickJsExecutor worker host lifecycle", () => { it("terminates a silent worker only once when execution times out", async () => { vi.useFakeTimers(); state.autoExitOnStart = false; - const { QuickJsExecutor } = await import("../src/index"); + const { QuickJsExecutor } = await import("../../src/index"); const executor = new QuickJsExecutor({ host: "worker", cancelGraceMs: 0, @@ -92,7 +92,7 @@ describe("QuickJsExecutor worker host lifecycle", () => { }); it("does not create a worker when the caller signal is already aborted", async () => { - const { QuickJsExecutor } = await import("../src/index"); + const { QuickJsExecutor } = await import("../../src/index"); const executor = new QuickJsExecutor({ host: "worker" }); const controller = new AbortController(); controller.abort(); @@ -113,7 +113,7 @@ describe("QuickJsExecutor worker host lifecycle", () => { it("terminates a silent worker only once when the caller aborts", async () => { vi.useFakeTimers(); state.autoExitOnStart = false; - const { QuickJsExecutor } = await import("../src/index"); + const { QuickJsExecutor } = await import("../../src/index"); const executor = new QuickJsExecutor({ host: "worker", cancelGraceMs: 0, diff --git a/packages/remote/__tests__/penetration.test.ts b/packages/remote/__tests__/penetration.test.ts deleted file mode 100644 index 45cd942..0000000 --- a/packages/remote/__tests__/penetration.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { runWrappedMcpPenetrationSuite } from "../../core/test-support/runWrappedMcpPenetrationSuite"; -import { createLoopbackTransport } from "../test-support/createLoopbackTransport"; -import { RemoteExecutor } from "../src/index"; - -runWrappedMcpPenetrationSuite("RemoteExecutor wrapped MCP", (options) => { - return new RemoteExecutor({ - ...options, - connectTransport: () => createLoopbackTransport(), - }); -}); diff --git a/packages/remote/__tests__/security/penetration.test.ts b/packages/remote/__tests__/security/penetration.test.ts new file mode 100644 index 0000000..b033ed4 --- /dev/null +++ b/packages/remote/__tests__/security/penetration.test.ts @@ -0,0 +1,10 @@ +import { runWrappedMcpPenetrationSuite } from "../../../core/test-support/runWrappedMcpPenetrationSuite"; +import { RemoteExecutor } from "../../src/index"; +import { createLoopbackTransport } from "../../test-support/createLoopbackTransport"; + +runWrappedMcpPenetrationSuite("RemoteExecutor wrapped MCP", (options) => { + return new RemoteExecutor({ + ...options, + connectTransport: () => createLoopbackTransport(), + }); +});