gitx.do 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -353
- package/dist/do/logger.d.ts +50 -0
- package/dist/do/logger.d.ts.map +1 -0
- package/dist/do/logger.js +122 -0
- package/dist/do/logger.js.map +1 -0
- package/dist/{durable-object → do}/schema.d.ts +3 -3
- package/dist/do/schema.d.ts.map +1 -0
- package/dist/{durable-object → do}/schema.js +4 -3
- package/dist/do/schema.js.map +1 -0
- package/dist/do/types.d.ts +267 -0
- package/dist/do/types.d.ts.map +1 -0
- package/dist/do/types.js +62 -0
- package/dist/do/types.js.map +1 -0
- package/dist/index.d.ts +15 -469
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -481
- package/dist/index.js.map +1 -1
- package/dist/mcp/auth.d.ts +77 -0
- package/dist/mcp/auth.d.ts.map +1 -0
- package/dist/mcp/auth.js +278 -0
- package/dist/mcp/auth.js.map +1 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +200 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +275 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tool-registry.d.ts +47 -0
- package/dist/mcp/tool-registry.d.ts.map +1 -0
- package/dist/mcp/tool-registry.js +284 -0
- package/dist/mcp/tool-registry.js.map +1 -0
- package/dist/mcp/tools.d.ts +103 -515
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +676 -3087
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts +124 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +9 -0
- package/dist/mcp/types.js.map +1 -0
- package/package.json +19 -21
- package/dist/cli/commands/add.d.ts +0 -176
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -979
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/blame.d.ts +0 -259
- package/dist/cli/commands/blame.d.ts.map +0 -1
- package/dist/cli/commands/blame.js +0 -609
- package/dist/cli/commands/blame.js.map +0 -1
- package/dist/cli/commands/branch.d.ts +0 -249
- package/dist/cli/commands/branch.d.ts.map +0 -1
- package/dist/cli/commands/branch.js +0 -693
- package/dist/cli/commands/branch.js.map +0 -1
- package/dist/cli/commands/checkout.d.ts +0 -73
- package/dist/cli/commands/checkout.d.ts.map +0 -1
- package/dist/cli/commands/checkout.js +0 -725
- package/dist/cli/commands/checkout.js.map +0 -1
- package/dist/cli/commands/commit.d.ts +0 -182
- package/dist/cli/commands/commit.d.ts.map +0 -1
- package/dist/cli/commands/commit.js +0 -457
- package/dist/cli/commands/commit.js.map +0 -1
- package/dist/cli/commands/diff.d.ts +0 -464
- package/dist/cli/commands/diff.d.ts.map +0 -1
- package/dist/cli/commands/diff.js +0 -959
- package/dist/cli/commands/diff.js.map +0 -1
- package/dist/cli/commands/log.d.ts +0 -239
- package/dist/cli/commands/log.d.ts.map +0 -1
- package/dist/cli/commands/log.js +0 -535
- package/dist/cli/commands/log.js.map +0 -1
- package/dist/cli/commands/merge.d.ts +0 -106
- package/dist/cli/commands/merge.d.ts.map +0 -1
- package/dist/cli/commands/merge.js +0 -852
- package/dist/cli/commands/merge.js.map +0 -1
- package/dist/cli/commands/review.d.ts +0 -457
- package/dist/cli/commands/review.d.ts.map +0 -1
- package/dist/cli/commands/review.js +0 -558
- package/dist/cli/commands/review.js.map +0 -1
- package/dist/cli/commands/stash.d.ts +0 -157
- package/dist/cli/commands/stash.d.ts.map +0 -1
- package/dist/cli/commands/stash.js +0 -655
- package/dist/cli/commands/stash.js.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -269
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/status.js +0 -492
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/web.d.ts +0 -199
- package/dist/cli/commands/web.d.ts.map +0 -1
- package/dist/cli/commands/web.js +0 -697
- package/dist/cli/commands/web.js.map +0 -1
- package/dist/cli/fs-adapter.d.ts +0 -656
- package/dist/cli/fs-adapter.d.ts.map +0 -1
- package/dist/cli/fs-adapter.js +0 -1177
- package/dist/cli/fs-adapter.js.map +0 -1
- package/dist/cli/fsx-cli-adapter.d.ts +0 -359
- package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
- package/dist/cli/fsx-cli-adapter.js +0 -619
- package/dist/cli/fsx-cli-adapter.js.map +0 -1
- package/dist/cli/index.d.ts +0 -387
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -579
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/ui/components/DiffView.d.ts +0 -12
- package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
- package/dist/cli/ui/components/DiffView.js +0 -11
- package/dist/cli/ui/components/DiffView.js.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -10
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.js +0 -11
- package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.d.ts +0 -15
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.js +0 -12
- package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -10
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.js +0 -10
- package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
- package/dist/cli/ui/components/NavigationList.d.ts +0 -14
- package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
- package/dist/cli/ui/components/NavigationList.js +0 -11
- package/dist/cli/ui/components/NavigationList.js.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.d.ts +0 -13
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.js +0 -11
- package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
- package/dist/cli/ui/components/index.d.ts +0 -7
- package/dist/cli/ui/components/index.d.ts.map +0 -1
- package/dist/cli/ui/components/index.js +0 -9
- package/dist/cli/ui/components/index.js.map +0 -1
- package/dist/cli/ui/terminal-ui.d.ts +0 -85
- package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
- package/dist/cli/ui/terminal-ui.js +0 -121
- package/dist/cli/ui/terminal-ui.js.map +0 -1
- package/dist/do/BashModule.d.ts +0 -871
- package/dist/do/BashModule.d.ts.map +0 -1
- package/dist/do/BashModule.js +0 -1143
- package/dist/do/BashModule.js.map +0 -1
- package/dist/do/FsModule.d.ts +0 -612
- package/dist/do/FsModule.d.ts.map +0 -1
- package/dist/do/FsModule.js +0 -1120
- package/dist/do/FsModule.js.map +0 -1
- package/dist/do/GitModule.d.ts +0 -635
- package/dist/do/GitModule.d.ts.map +0 -1
- package/dist/do/GitModule.js +0 -784
- package/dist/do/GitModule.js.map +0 -1
- package/dist/do/GitRepoDO.d.ts +0 -281
- package/dist/do/GitRepoDO.d.ts.map +0 -1
- package/dist/do/GitRepoDO.js +0 -479
- package/dist/do/GitRepoDO.js.map +0 -1
- package/dist/do/bash-ast.d.ts +0 -246
- package/dist/do/bash-ast.d.ts.map +0 -1
- package/dist/do/bash-ast.js +0 -888
- package/dist/do/bash-ast.js.map +0 -1
- package/dist/do/container-executor.d.ts +0 -491
- package/dist/do/container-executor.d.ts.map +0 -1
- package/dist/do/container-executor.js +0 -731
- package/dist/do/container-executor.js.map +0 -1
- package/dist/do/index.d.ts +0 -53
- package/dist/do/index.d.ts.map +0 -1
- package/dist/do/index.js +0 -91
- package/dist/do/index.js.map +0 -1
- package/dist/do/tiered-storage.d.ts +0 -403
- package/dist/do/tiered-storage.d.ts.map +0 -1
- package/dist/do/tiered-storage.js +0 -689
- package/dist/do/tiered-storage.js.map +0 -1
- package/dist/do/withBash.d.ts +0 -231
- package/dist/do/withBash.d.ts.map +0 -1
- package/dist/do/withBash.js +0 -244
- package/dist/do/withBash.js.map +0 -1
- package/dist/do/withFs.d.ts +0 -237
- package/dist/do/withFs.d.ts.map +0 -1
- package/dist/do/withFs.js +0 -387
- package/dist/do/withFs.js.map +0 -1
- package/dist/do/withGit.d.ts +0 -180
- package/dist/do/withGit.d.ts.map +0 -1
- package/dist/do/withGit.js +0 -271
- package/dist/do/withGit.js.map +0 -1
- package/dist/durable-object/object-store.d.ts +0 -633
- package/dist/durable-object/object-store.d.ts.map +0 -1
- package/dist/durable-object/object-store.js +0 -1164
- package/dist/durable-object/object-store.js.map +0 -1
- package/dist/durable-object/schema.d.ts.map +0 -1
- package/dist/durable-object/schema.js.map +0 -1
- package/dist/durable-object/wal.d.ts +0 -416
- package/dist/durable-object/wal.d.ts.map +0 -1
- package/dist/durable-object/wal.js +0 -445
- package/dist/durable-object/wal.js.map +0 -1
- package/dist/mcp/adapter.d.ts +0 -772
- package/dist/mcp/adapter.d.ts.map +0 -1
- package/dist/mcp/adapter.js +0 -895
- package/dist/mcp/adapter.js.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
- package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.js +0 -30
- package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
- package/dist/mcp/sandbox/template.d.ts +0 -17
- package/dist/mcp/sandbox/template.d.ts.map +0 -1
- package/dist/mcp/sandbox/template.js +0 -71
- package/dist/mcp/sandbox/template.js.map +0 -1
- package/dist/mcp/sandbox.d.ts +0 -764
- package/dist/mcp/sandbox.d.ts.map +0 -1
- package/dist/mcp/sandbox.js +0 -1362
- package/dist/mcp/sandbox.js.map +0 -1
- package/dist/mcp/sdk-adapter.d.ts +0 -835
- package/dist/mcp/sdk-adapter.d.ts.map +0 -1
- package/dist/mcp/sdk-adapter.js +0 -974
- package/dist/mcp/sdk-adapter.js.map +0 -1
- package/dist/mcp/tools/do.d.ts +0 -32
- package/dist/mcp/tools/do.d.ts.map +0 -1
- package/dist/mcp/tools/do.js +0 -117
- package/dist/mcp/tools/do.js.map +0 -1
- package/dist/ops/blame.d.ts +0 -551
- package/dist/ops/blame.d.ts.map +0 -1
- package/dist/ops/blame.js +0 -1037
- package/dist/ops/blame.js.map +0 -1
- package/dist/ops/branch.d.ts +0 -766
- package/dist/ops/branch.d.ts.map +0 -1
- package/dist/ops/branch.js +0 -950
- package/dist/ops/branch.js.map +0 -1
- package/dist/ops/commit-traversal.d.ts +0 -349
- package/dist/ops/commit-traversal.d.ts.map +0 -1
- package/dist/ops/commit-traversal.js +0 -821
- package/dist/ops/commit-traversal.js.map +0 -1
- package/dist/ops/commit.d.ts +0 -555
- package/dist/ops/commit.d.ts.map +0 -1
- package/dist/ops/commit.js +0 -826
- package/dist/ops/commit.js.map +0 -1
- package/dist/ops/merge-base.d.ts +0 -397
- package/dist/ops/merge-base.d.ts.map +0 -1
- package/dist/ops/merge-base.js +0 -691
- package/dist/ops/merge-base.js.map +0 -1
- package/dist/ops/merge.d.ts +0 -855
- package/dist/ops/merge.d.ts.map +0 -1
- package/dist/ops/merge.js +0 -1551
- package/dist/ops/merge.js.map +0 -1
- package/dist/ops/tag.d.ts +0 -247
- package/dist/ops/tag.d.ts.map +0 -1
- package/dist/ops/tag.js +0 -649
- package/dist/ops/tag.js.map +0 -1
- package/dist/ops/tree-builder.d.ts +0 -178
- package/dist/ops/tree-builder.d.ts.map +0 -1
- package/dist/ops/tree-builder.js +0 -271
- package/dist/ops/tree-builder.js.map +0 -1
- package/dist/ops/tree-diff.d.ts +0 -291
- package/dist/ops/tree-diff.d.ts.map +0 -1
- package/dist/ops/tree-diff.js +0 -705
- package/dist/ops/tree-diff.js.map +0 -1
- package/dist/pack/delta.d.ts +0 -248
- package/dist/pack/delta.d.ts.map +0 -1
- package/dist/pack/delta.js +0 -740
- package/dist/pack/delta.js.map +0 -1
- package/dist/pack/format.d.ts +0 -446
- package/dist/pack/format.d.ts.map +0 -1
- package/dist/pack/format.js +0 -572
- package/dist/pack/format.js.map +0 -1
- package/dist/pack/full-generation.d.ts +0 -612
- package/dist/pack/full-generation.d.ts.map +0 -1
- package/dist/pack/full-generation.js +0 -1378
- package/dist/pack/full-generation.js.map +0 -1
- package/dist/pack/generation.d.ts +0 -441
- package/dist/pack/generation.d.ts.map +0 -1
- package/dist/pack/generation.js +0 -707
- package/dist/pack/generation.js.map +0 -1
- package/dist/pack/index.d.ts +0 -502
- package/dist/pack/index.d.ts.map +0 -1
- package/dist/pack/index.js +0 -833
- package/dist/pack/index.js.map +0 -1
- package/dist/refs/branch.d.ts +0 -683
- package/dist/refs/branch.d.ts.map +0 -1
- package/dist/refs/branch.js +0 -881
- package/dist/refs/branch.js.map +0 -1
- package/dist/refs/storage.d.ts +0 -833
- package/dist/refs/storage.d.ts.map +0 -1
- package/dist/refs/storage.js +0 -1023
- package/dist/refs/storage.js.map +0 -1
- package/dist/refs/tag.d.ts +0 -860
- package/dist/refs/tag.d.ts.map +0 -1
- package/dist/refs/tag.js +0 -996
- package/dist/refs/tag.js.map +0 -1
- package/dist/storage/backend.d.ts +0 -425
- package/dist/storage/backend.d.ts.map +0 -1
- package/dist/storage/backend.js +0 -41
- package/dist/storage/backend.js.map +0 -1
- package/dist/storage/fsx-adapter.d.ts +0 -204
- package/dist/storage/fsx-adapter.d.ts.map +0 -1
- package/dist/storage/fsx-adapter.js +0 -518
- package/dist/storage/fsx-adapter.js.map +0 -1
- package/dist/storage/lru-cache.d.ts +0 -691
- package/dist/storage/lru-cache.d.ts.map +0 -1
- package/dist/storage/lru-cache.js +0 -813
- package/dist/storage/lru-cache.js.map +0 -1
- package/dist/storage/object-index.d.ts +0 -585
- package/dist/storage/object-index.d.ts.map +0 -1
- package/dist/storage/object-index.js +0 -532
- package/dist/storage/object-index.js.map +0 -1
- package/dist/storage/r2-pack.d.ts +0 -1257
- package/dist/storage/r2-pack.d.ts.map +0 -1
- package/dist/storage/r2-pack.js +0 -1773
- package/dist/storage/r2-pack.js.map +0 -1
- package/dist/tiered/cdc-pipeline.d.ts +0 -1888
- package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
- package/dist/tiered/cdc-pipeline.js +0 -1880
- package/dist/tiered/cdc-pipeline.js.map +0 -1
- package/dist/tiered/migration.d.ts +0 -1104
- package/dist/tiered/migration.d.ts.map +0 -1
- package/dist/tiered/migration.js +0 -1217
- package/dist/tiered/migration.js.map +0 -1
- package/dist/tiered/parquet-writer.d.ts +0 -1145
- package/dist/tiered/parquet-writer.d.ts.map +0 -1
- package/dist/tiered/parquet-writer.js +0 -1183
- package/dist/tiered/parquet-writer.js.map +0 -1
- package/dist/tiered/read-path.d.ts +0 -835
- package/dist/tiered/read-path.d.ts.map +0 -1
- package/dist/tiered/read-path.js +0 -487
- package/dist/tiered/read-path.js.map +0 -1
- package/dist/types/capability.d.ts +0 -1385
- package/dist/types/capability.d.ts.map +0 -1
- package/dist/types/capability.js +0 -36
- package/dist/types/capability.js.map +0 -1
- package/dist/types/index.d.ts +0 -13
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -18
- package/dist/types/index.js.map +0 -1
- package/dist/types/interfaces.d.ts +0 -673
- package/dist/types/interfaces.d.ts.map +0 -1
- package/dist/types/interfaces.js +0 -26
- package/dist/types/interfaces.js.map +0 -1
- package/dist/types/objects.d.ts +0 -692
- package/dist/types/objects.d.ts.map +0 -1
- package/dist/types/objects.js +0 -837
- package/dist/types/objects.js.map +0 -1
- package/dist/types/storage.d.ts +0 -603
- package/dist/types/storage.d.ts.map +0 -1
- package/dist/types/storage.js +0 -191
- package/dist/types/storage.js.map +0 -1
- package/dist/types/worker-loader.d.ts +0 -60
- package/dist/types/worker-loader.d.ts.map +0 -1
- package/dist/types/worker-loader.js +0 -62
- package/dist/types/worker-loader.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -198
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -272
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/sha1.d.ts +0 -325
- package/dist/utils/sha1.d.ts.map +0 -1
- package/dist/utils/sha1.js +0 -635
- package/dist/utils/sha1.js.map +0 -1
- package/dist/wire/capabilities.d.ts +0 -1044
- package/dist/wire/capabilities.d.ts.map +0 -1
- package/dist/wire/capabilities.js +0 -941
- package/dist/wire/capabilities.js.map +0 -1
- package/dist/wire/path-security.d.ts +0 -157
- package/dist/wire/path-security.d.ts.map +0 -1
- package/dist/wire/path-security.js +0 -307
- package/dist/wire/path-security.js.map +0 -1
- package/dist/wire/pkt-line.d.ts +0 -345
- package/dist/wire/pkt-line.d.ts.map +0 -1
- package/dist/wire/pkt-line.js +0 -381
- package/dist/wire/pkt-line.js.map +0 -1
- package/dist/wire/receive-pack.d.ts +0 -1059
- package/dist/wire/receive-pack.d.ts.map +0 -1
- package/dist/wire/receive-pack.js +0 -1414
- package/dist/wire/receive-pack.js.map +0 -1
- package/dist/wire/smart-http.d.ts +0 -799
- package/dist/wire/smart-http.d.ts.map +0 -1
- package/dist/wire/smart-http.js +0 -945
- package/dist/wire/smart-http.js.map +0 -1
- package/dist/wire/upload-pack.d.ts +0 -727
- package/dist/wire/upload-pack.d.ts.map +0 -1
- package/dist/wire/upload-pack.js +0 -1141
- package/dist/wire/upload-pack.js.map +0 -1
package/dist/mcp/sandbox.js
DELETED
|
@@ -1,1362 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview MCP Sandbox Execution Environment
|
|
3
|
-
*
|
|
4
|
-
* Provides an isolated execution environment for MCP tools with:
|
|
5
|
-
* - Resource limits (memory, CPU, time, file descriptors, disk)
|
|
6
|
-
* - Capability restrictions (file read/write, network, process spawning)
|
|
7
|
-
* - Safe git operation execution with permission checks
|
|
8
|
-
* - Audit logging for security violations
|
|
9
|
-
*
|
|
10
|
-
* SECURITY: Uses Node.js vm module concepts for proper isolation. The sandbox
|
|
11
|
-
* implements multi-layer security through:
|
|
12
|
-
* 1. Pre-execution static analysis to detect dangerous patterns
|
|
13
|
-
* 2. Runtime permission checks via Proxy-based module interception
|
|
14
|
-
* 3. Resource limit enforcement during execution
|
|
15
|
-
* 4. Permission violation recording for audit trails
|
|
16
|
-
*
|
|
17
|
-
* @module mcp/sandbox
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* // Create a sandbox with limited permissions
|
|
21
|
-
* import { createSandbox, SandboxState } from './sandbox'
|
|
22
|
-
*
|
|
23
|
-
* const sandbox = createSandbox({
|
|
24
|
-
* timeout: 5000,
|
|
25
|
-
* memoryLimit: 128 * 1024 * 1024,
|
|
26
|
-
* permissions: {
|
|
27
|
-
* fileRead: true,
|
|
28
|
-
* fileWrite: false,
|
|
29
|
-
* network: false,
|
|
30
|
-
* spawn: false
|
|
31
|
-
* }
|
|
32
|
-
* })
|
|
33
|
-
*
|
|
34
|
-
* await sandbox.start()
|
|
35
|
-
* const result = await sandbox.execute(() => {
|
|
36
|
-
* return 'Hello from sandbox!'
|
|
37
|
-
* })
|
|
38
|
-
*
|
|
39
|
-
* if (result.error) {
|
|
40
|
-
* console.error('Execution failed:', result.error.message)
|
|
41
|
-
* } else {
|
|
42
|
-
* console.log('Result:', result.value)
|
|
43
|
-
* }
|
|
44
|
-
*
|
|
45
|
-
* await sandbox.destroy()
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* // Using a sandbox pool for concurrent execution
|
|
49
|
-
* import { createSandboxPool } from './sandbox'
|
|
50
|
-
*
|
|
51
|
-
* const pool = createSandboxPool({ size: 4 })
|
|
52
|
-
* const sandbox = await pool.acquire()
|
|
53
|
-
*
|
|
54
|
-
* try {
|
|
55
|
-
* const result = await sandbox.execute(myFunction)
|
|
56
|
-
* } finally {
|
|
57
|
-
* await pool.release(sandbox)
|
|
58
|
-
* }
|
|
59
|
-
*
|
|
60
|
-
* await pool.shutdown()
|
|
61
|
-
*/
|
|
62
|
-
import { EventEmitter } from 'events';
|
|
63
|
-
/**
|
|
64
|
-
* Sandbox error codes.
|
|
65
|
-
*
|
|
66
|
-
* @description
|
|
67
|
-
* Enumeration of all possible error codes that can be returned by sandbox
|
|
68
|
-
* operations. These codes indicate the specific reason for execution failure.
|
|
69
|
-
*
|
|
70
|
-
* @enum {string}
|
|
71
|
-
*/
|
|
72
|
-
export var SandboxErrorCode;
|
|
73
|
-
(function (SandboxErrorCode) {
|
|
74
|
-
/** Execution exceeded the configured timeout */
|
|
75
|
-
SandboxErrorCode["TIMEOUT"] = "TIMEOUT";
|
|
76
|
-
/** Memory usage exceeded the configured limit */
|
|
77
|
-
SandboxErrorCode["MEMORY_LIMIT_EXCEEDED"] = "MEMORY_LIMIT_EXCEEDED";
|
|
78
|
-
/** CPU time exceeded the configured limit */
|
|
79
|
-
SandboxErrorCode["CPU_LIMIT_EXCEEDED"] = "CPU_LIMIT_EXCEEDED";
|
|
80
|
-
/** Operation was denied due to insufficient permissions */
|
|
81
|
-
SandboxErrorCode["PERMISSION_DENIED"] = "PERMISSION_DENIED";
|
|
82
|
-
/** General execution error occurred */
|
|
83
|
-
SandboxErrorCode["EXECUTION_ERROR"] = "EXECUTION_ERROR";
|
|
84
|
-
/** Too many file descriptors opened */
|
|
85
|
-
SandboxErrorCode["FILE_DESCRIPTOR_LIMIT"] = "FILE_DESCRIPTOR_LIMIT";
|
|
86
|
-
/** Too many processes spawned */
|
|
87
|
-
SandboxErrorCode["PROCESS_LIMIT_EXCEEDED"] = "PROCESS_LIMIT_EXCEEDED";
|
|
88
|
-
/** Network bandwidth limit exceeded */
|
|
89
|
-
SandboxErrorCode["BANDWIDTH_LIMIT_EXCEEDED"] = "BANDWIDTH_LIMIT_EXCEEDED";
|
|
90
|
-
/** Disk write limit exceeded */
|
|
91
|
-
SandboxErrorCode["DISK_LIMIT_EXCEEDED"] = "DISK_LIMIT_EXCEEDED";
|
|
92
|
-
/** Sandbox crashed unexpectedly */
|
|
93
|
-
SandboxErrorCode["SANDBOX_CRASHED"] = "SANDBOX_CRASHED";
|
|
94
|
-
/** Sandbox is paused and not accepting executions */
|
|
95
|
-
SandboxErrorCode["SANDBOX_PAUSED"] = "SANDBOX_PAUSED";
|
|
96
|
-
})(SandboxErrorCode || (SandboxErrorCode = {}));
|
|
97
|
-
/**
|
|
98
|
-
* Sandbox error class.
|
|
99
|
-
*
|
|
100
|
-
* @description
|
|
101
|
-
* Custom error class for sandbox-specific errors. Includes an error code
|
|
102
|
-
* for programmatic handling and optional additional data.
|
|
103
|
-
*
|
|
104
|
-
* @class SandboxError
|
|
105
|
-
* @extends Error
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* try {
|
|
109
|
-
* await sandbox.execute(fn)
|
|
110
|
-
* } catch (error) {
|
|
111
|
-
* if (error instanceof SandboxError) {
|
|
112
|
-
* console.log('Error code:', error.code)
|
|
113
|
-
* console.log('Error data:', error.data)
|
|
114
|
-
* }
|
|
115
|
-
* }
|
|
116
|
-
*/
|
|
117
|
-
export class SandboxError extends Error {
|
|
118
|
-
/** The error code identifying the type of error */
|
|
119
|
-
code;
|
|
120
|
-
/** Optional additional error data */
|
|
121
|
-
data;
|
|
122
|
-
/** Stack trace (inherited from Error) */
|
|
123
|
-
stack;
|
|
124
|
-
/**
|
|
125
|
-
* Create a new sandbox error.
|
|
126
|
-
* @param code - The error code
|
|
127
|
-
* @param message - Human-readable error message
|
|
128
|
-
* @param data - Optional additional error data
|
|
129
|
-
*/
|
|
130
|
-
constructor(code, message, data) {
|
|
131
|
-
super(message);
|
|
132
|
-
this.name = 'SandboxError';
|
|
133
|
-
this.code = code;
|
|
134
|
-
this.data = data;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Convert error to JSON representation.
|
|
138
|
-
* @returns JSON-serializable error object
|
|
139
|
-
*/
|
|
140
|
-
toJSON() {
|
|
141
|
-
const result = {
|
|
142
|
-
code: this.code,
|
|
143
|
-
message: this.message,
|
|
144
|
-
};
|
|
145
|
-
if (this.data !== undefined) {
|
|
146
|
-
result.data = this.data;
|
|
147
|
-
}
|
|
148
|
-
return result;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Sandbox state enum.
|
|
153
|
-
*
|
|
154
|
-
* @description
|
|
155
|
-
* Represents the lifecycle state of a sandbox instance.
|
|
156
|
-
*
|
|
157
|
-
* @enum {string}
|
|
158
|
-
*/
|
|
159
|
-
export var SandboxState;
|
|
160
|
-
(function (SandboxState) {
|
|
161
|
-
/** Sandbox is idle and ready for use */
|
|
162
|
-
SandboxState["IDLE"] = "IDLE";
|
|
163
|
-
/** Sandbox is currently executing code */
|
|
164
|
-
SandboxState["RUNNING"] = "RUNNING";
|
|
165
|
-
/** Sandbox is paused (can be resumed) */
|
|
166
|
-
SandboxState["PAUSED"] = "PAUSED";
|
|
167
|
-
/** Sandbox has been destroyed and cannot be reused */
|
|
168
|
-
SandboxState["DESTROYED"] = "DESTROYED";
|
|
169
|
-
})(SandboxState || (SandboxState = {}));
|
|
170
|
-
/**
|
|
171
|
-
* Generate unique ID.
|
|
172
|
-
* @returns Unique sandbox identifier
|
|
173
|
-
* @internal
|
|
174
|
-
*/
|
|
175
|
-
function generateId() {
|
|
176
|
-
return `sandbox-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Get permission set from preset.
|
|
180
|
-
* @param preset - The permission preset to convert
|
|
181
|
-
* @returns Corresponding PermissionSet
|
|
182
|
-
* @internal
|
|
183
|
-
*/
|
|
184
|
-
function getPermissionsFromPreset(preset) {
|
|
185
|
-
switch (preset) {
|
|
186
|
-
case 'readonly':
|
|
187
|
-
return {
|
|
188
|
-
fileRead: true,
|
|
189
|
-
fileWrite: false,
|
|
190
|
-
network: false,
|
|
191
|
-
spawn: false,
|
|
192
|
-
env: true,
|
|
193
|
-
nativeModules: true,
|
|
194
|
-
};
|
|
195
|
-
case 'full':
|
|
196
|
-
return {
|
|
197
|
-
fileRead: true,
|
|
198
|
-
fileWrite: true,
|
|
199
|
-
network: true,
|
|
200
|
-
spawn: true,
|
|
201
|
-
env: true,
|
|
202
|
-
nativeModules: true,
|
|
203
|
-
};
|
|
204
|
-
case 'network-only':
|
|
205
|
-
return {
|
|
206
|
-
fileRead: false,
|
|
207
|
-
fileWrite: false,
|
|
208
|
-
network: true,
|
|
209
|
-
spawn: false,
|
|
210
|
-
env: false,
|
|
211
|
-
nativeModules: false,
|
|
212
|
-
};
|
|
213
|
-
default:
|
|
214
|
-
return {};
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Dangerous modules that require permission checks.
|
|
219
|
-
* @internal
|
|
220
|
-
*/
|
|
221
|
-
const DANGEROUS_MODULES = new Set([
|
|
222
|
-
'fs',
|
|
223
|
-
'fs/promises',
|
|
224
|
-
'child_process',
|
|
225
|
-
'http',
|
|
226
|
-
'https',
|
|
227
|
-
'net',
|
|
228
|
-
'dgram',
|
|
229
|
-
'dns',
|
|
230
|
-
'tls',
|
|
231
|
-
'cluster',
|
|
232
|
-
'worker_threads',
|
|
233
|
-
]);
|
|
234
|
-
/**
|
|
235
|
-
* File system read methods.
|
|
236
|
-
* @internal
|
|
237
|
-
*/
|
|
238
|
-
const FS_READ_METHODS = new Set([
|
|
239
|
-
'readFile',
|
|
240
|
-
'readFileSync',
|
|
241
|
-
'readdir',
|
|
242
|
-
'readdirSync',
|
|
243
|
-
'readlink',
|
|
244
|
-
'readlinkSync',
|
|
245
|
-
'stat',
|
|
246
|
-
'statSync',
|
|
247
|
-
'lstat',
|
|
248
|
-
'lstatSync',
|
|
249
|
-
'access',
|
|
250
|
-
'accessSync',
|
|
251
|
-
'exists',
|
|
252
|
-
'existsSync',
|
|
253
|
-
'open',
|
|
254
|
-
'openSync',
|
|
255
|
-
'createReadStream',
|
|
256
|
-
]);
|
|
257
|
-
/**
|
|
258
|
-
* File system write methods.
|
|
259
|
-
* @internal
|
|
260
|
-
*/
|
|
261
|
-
const FS_WRITE_METHODS = new Set([
|
|
262
|
-
'writeFile',
|
|
263
|
-
'writeFileSync',
|
|
264
|
-
'appendFile',
|
|
265
|
-
'appendFileSync',
|
|
266
|
-
'mkdir',
|
|
267
|
-
'mkdirSync',
|
|
268
|
-
'rmdir',
|
|
269
|
-
'rmdirSync',
|
|
270
|
-
'unlink',
|
|
271
|
-
'unlinkSync',
|
|
272
|
-
'rename',
|
|
273
|
-
'renameSync',
|
|
274
|
-
'copyFile',
|
|
275
|
-
'copyFileSync',
|
|
276
|
-
'truncate',
|
|
277
|
-
'truncateSync',
|
|
278
|
-
'createWriteStream',
|
|
279
|
-
'chmod',
|
|
280
|
-
'chmodSync',
|
|
281
|
-
'chown',
|
|
282
|
-
'chownSync',
|
|
283
|
-
]);
|
|
284
|
-
/**
|
|
285
|
-
* MCP Sandbox class for isolated execution.
|
|
286
|
-
*
|
|
287
|
-
* @description
|
|
288
|
-
* Provides an isolated execution environment with resource limits and
|
|
289
|
-
* permission controls. Uses multi-layer security including static analysis,
|
|
290
|
-
* runtime permission checks, and resource limit enforcement.
|
|
291
|
-
*
|
|
292
|
-
* SECURITY: This implementation uses Node.js vm module concepts with proper
|
|
293
|
-
* context isolation and runtime permission checks instead of string analysis.
|
|
294
|
-
*
|
|
295
|
-
* Lifecycle:
|
|
296
|
-
* 1. Create sandbox with createSandbox() or new MCPSandbox()
|
|
297
|
-
* 2. Start the sandbox with start()
|
|
298
|
-
* 3. Execute code with execute()
|
|
299
|
-
* 4. Optionally pause()/resume()
|
|
300
|
-
* 5. Cleanup with cleanup() or destroy()
|
|
301
|
-
*
|
|
302
|
-
* @class MCPSandbox
|
|
303
|
-
* @extends EventEmitter
|
|
304
|
-
*
|
|
305
|
-
* @fires stateChange - When sandbox state changes
|
|
306
|
-
*
|
|
307
|
-
* @example
|
|
308
|
-
* const sandbox = new MCPSandbox({
|
|
309
|
-
* timeout: 5000,
|
|
310
|
-
* permissions: { fileRead: true, fileWrite: false }
|
|
311
|
-
* })
|
|
312
|
-
*
|
|
313
|
-
* await sandbox.start()
|
|
314
|
-
*
|
|
315
|
-
* const result = await sandbox.execute(() => {
|
|
316
|
-
* return 'Hello from sandbox!'
|
|
317
|
-
* })
|
|
318
|
-
*
|
|
319
|
-
* console.log(result.value) // 'Hello from sandbox!'
|
|
320
|
-
*
|
|
321
|
-
* await sandbox.destroy()
|
|
322
|
-
*/
|
|
323
|
-
export class MCPSandbox extends EventEmitter {
|
|
324
|
-
id;
|
|
325
|
-
config;
|
|
326
|
-
state = SandboxState.IDLE;
|
|
327
|
-
resourceStats = {
|
|
328
|
-
memoryUsed: 0,
|
|
329
|
-
cpuTimeUsed: 0,
|
|
330
|
-
executionCount: 0,
|
|
331
|
-
activeHandles: 0,
|
|
332
|
-
};
|
|
333
|
-
permissionViolations = [];
|
|
334
|
-
permissions;
|
|
335
|
-
executionQueue = [];
|
|
336
|
-
activeExecutions = 0;
|
|
337
|
-
globalContext = new Map();
|
|
338
|
-
/**
|
|
339
|
-
* Create a new sandbox instance.
|
|
340
|
-
* @param config - Configuration options
|
|
341
|
-
*/
|
|
342
|
-
constructor(config = {}) {
|
|
343
|
-
super();
|
|
344
|
-
this.id = generateId();
|
|
345
|
-
this.config = {
|
|
346
|
-
timeout: config.timeout ?? 30000,
|
|
347
|
-
memoryLimit: config.memoryLimit ?? 256 * 1024 * 1024,
|
|
348
|
-
isolationLevel: config.isolationLevel ?? 'normal',
|
|
349
|
-
...config,
|
|
350
|
-
};
|
|
351
|
-
// Apply resource limits from config
|
|
352
|
-
if (config.resourceLimits) {
|
|
353
|
-
this.config.memoryLimit = config.resourceLimits.memoryLimit ?? this.config.memoryLimit;
|
|
354
|
-
this.config.cpuTimeLimit = config.resourceLimits.cpuTimeLimit ?? this.config.cpuTimeLimit;
|
|
355
|
-
this.config.maxOpenFiles = config.resourceLimits.maxOpenFiles ?? this.config.maxOpenFiles;
|
|
356
|
-
this.config.maxProcesses = config.resourceLimits.maxProcesses ?? this.config.maxProcesses;
|
|
357
|
-
this.config.diskWriteLimit = config.resourceLimits.diskWriteLimit ?? this.config.diskWriteLimit;
|
|
358
|
-
}
|
|
359
|
-
// Set permissions from preset or config
|
|
360
|
-
if (config.permissionPreset) {
|
|
361
|
-
this.permissions = getPermissionsFromPreset(config.permissionPreset);
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
this.permissions = config.permissions ?? {};
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Get the sandbox ID.
|
|
369
|
-
* @returns Unique sandbox identifier
|
|
370
|
-
*/
|
|
371
|
-
getId() {
|
|
372
|
-
return this.id;
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Get the sandbox configuration.
|
|
376
|
-
* @returns Copy of the configuration
|
|
377
|
-
*/
|
|
378
|
-
getConfig() {
|
|
379
|
-
return { ...this.config };
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Get the current sandbox state.
|
|
383
|
-
* @returns Current SandboxState
|
|
384
|
-
*/
|
|
385
|
-
getState() {
|
|
386
|
-
return this.state;
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Get the current permission set.
|
|
390
|
-
* @returns Copy of permissions
|
|
391
|
-
*/
|
|
392
|
-
getPermissions() {
|
|
393
|
-
return { ...this.permissions };
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Get resource usage statistics.
|
|
397
|
-
* @returns Copy of resource stats
|
|
398
|
-
*/
|
|
399
|
-
getResourceStats() {
|
|
400
|
-
return { ...this.resourceStats };
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Get configured resource limits.
|
|
404
|
-
* @returns Copy of resource limits
|
|
405
|
-
*/
|
|
406
|
-
getResourceLimits() {
|
|
407
|
-
return {
|
|
408
|
-
memoryLimit: this.config.memoryLimit,
|
|
409
|
-
cpuTimeLimit: this.config.cpuTimeLimit,
|
|
410
|
-
maxOpenFiles: this.config.maxOpenFiles,
|
|
411
|
-
maxProcesses: this.config.maxProcesses,
|
|
412
|
-
diskWriteLimit: this.config.diskWriteLimit,
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Get list of permission violations.
|
|
417
|
-
* @returns Array of recorded violations
|
|
418
|
-
*/
|
|
419
|
-
getPermissionViolations() {
|
|
420
|
-
return [...this.permissionViolations];
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Start the sandbox.
|
|
424
|
-
*
|
|
425
|
-
* @description
|
|
426
|
-
* Transitions the sandbox to RUNNING state. Must be called before execute().
|
|
427
|
-
*
|
|
428
|
-
* @returns Promise that resolves when started
|
|
429
|
-
* @throws {Error} If sandbox is destroyed or already running
|
|
430
|
-
*/
|
|
431
|
-
async start() {
|
|
432
|
-
if (this.state === SandboxState.DESTROYED) {
|
|
433
|
-
throw new Error('Cannot start a destroyed sandbox');
|
|
434
|
-
}
|
|
435
|
-
if (this.state === SandboxState.RUNNING) {
|
|
436
|
-
throw new Error('Sandbox is already running');
|
|
437
|
-
}
|
|
438
|
-
this.state = SandboxState.RUNNING;
|
|
439
|
-
this.emit('stateChange', this.state);
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* Stop the sandbox.
|
|
443
|
-
*
|
|
444
|
-
* @description
|
|
445
|
-
* Transitions from RUNNING or PAUSED to IDLE state. Clears global context.
|
|
446
|
-
*
|
|
447
|
-
* @returns Promise that resolves when stopped
|
|
448
|
-
* @throws {Error} If sandbox is not running
|
|
449
|
-
*/
|
|
450
|
-
async stop() {
|
|
451
|
-
if (this.state !== SandboxState.RUNNING && this.state !== SandboxState.PAUSED) {
|
|
452
|
-
throw new Error('Sandbox is not running');
|
|
453
|
-
}
|
|
454
|
-
this.state = SandboxState.IDLE;
|
|
455
|
-
this.globalContext.clear();
|
|
456
|
-
this.emit('stateChange', this.state);
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Pause the sandbox.
|
|
460
|
-
*
|
|
461
|
-
* @description
|
|
462
|
-
* Temporarily pauses execution. New execute() calls will be queued if
|
|
463
|
-
* queueOnPause is enabled, otherwise they return immediately with an error.
|
|
464
|
-
*
|
|
465
|
-
* @returns Promise that resolves when paused
|
|
466
|
-
* @throws {Error} If sandbox is not running
|
|
467
|
-
*/
|
|
468
|
-
async pause() {
|
|
469
|
-
if (this.state !== SandboxState.RUNNING) {
|
|
470
|
-
throw new Error('Sandbox is not running');
|
|
471
|
-
}
|
|
472
|
-
this.state = SandboxState.PAUSED;
|
|
473
|
-
this.emit('stateChange', this.state);
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Resume the sandbox.
|
|
477
|
-
*
|
|
478
|
-
* @description
|
|
479
|
-
* Resumes execution after pause. Processes any queued executions.
|
|
480
|
-
*
|
|
481
|
-
* @returns Promise that resolves when resumed
|
|
482
|
-
* @throws {Error} If sandbox is not paused
|
|
483
|
-
*/
|
|
484
|
-
async resume() {
|
|
485
|
-
if (this.state !== SandboxState.PAUSED) {
|
|
486
|
-
throw new Error('Sandbox is not paused');
|
|
487
|
-
}
|
|
488
|
-
this.state = SandboxState.RUNNING;
|
|
489
|
-
this.emit('stateChange', this.state);
|
|
490
|
-
// Process queued executions
|
|
491
|
-
while (this.executionQueue.length > 0) {
|
|
492
|
-
const item = this.executionQueue.shift();
|
|
493
|
-
if (item) {
|
|
494
|
-
item.resolve();
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Cleanup sandbox resources.
|
|
500
|
-
*
|
|
501
|
-
* @description
|
|
502
|
-
* Resets resource statistics and clears global context. Sandbox remains
|
|
503
|
-
* usable after cleanup.
|
|
504
|
-
*
|
|
505
|
-
* @returns Promise that resolves when cleanup is complete
|
|
506
|
-
*/
|
|
507
|
-
async cleanup() {
|
|
508
|
-
this.resourceStats = {
|
|
509
|
-
memoryUsed: 0,
|
|
510
|
-
cpuTimeUsed: 0,
|
|
511
|
-
executionCount: 0,
|
|
512
|
-
activeHandles: 0,
|
|
513
|
-
};
|
|
514
|
-
this.globalContext.clear();
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Destroy the sandbox.
|
|
518
|
-
*
|
|
519
|
-
* @description
|
|
520
|
-
* Permanently destroys the sandbox. It cannot be reused after destruction.
|
|
521
|
-
*
|
|
522
|
-
* @returns Promise that resolves when destroyed
|
|
523
|
-
*/
|
|
524
|
-
async destroy() {
|
|
525
|
-
if (this.state === SandboxState.RUNNING) {
|
|
526
|
-
await this.stop();
|
|
527
|
-
}
|
|
528
|
-
this.state = SandboxState.DESTROYED;
|
|
529
|
-
this.emit('stateChange', this.state);
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Execute a function in the sandbox.
|
|
533
|
-
*
|
|
534
|
-
* @description
|
|
535
|
-
* Executes the provided function within the sandbox's isolated environment.
|
|
536
|
-
* The function is subject to configured timeout, resource limits, and
|
|
537
|
-
* permission restrictions.
|
|
538
|
-
*
|
|
539
|
-
* @template T - Return type of the function
|
|
540
|
-
* @param fn - Function to execute (sync or async)
|
|
541
|
-
* @param options - Execution options (timeout, context)
|
|
542
|
-
* @returns Promise resolving to SandboxResult with value or error
|
|
543
|
-
*
|
|
544
|
-
* @example
|
|
545
|
-
* const result = await sandbox.execute<number>(() => {
|
|
546
|
-
* return 42
|
|
547
|
-
* })
|
|
548
|
-
*
|
|
549
|
-
* if (result.error) {
|
|
550
|
-
* console.error('Failed:', result.error.code)
|
|
551
|
-
* } else {
|
|
552
|
-
* console.log('Result:', result.value) // 42
|
|
553
|
-
* }
|
|
554
|
-
*/
|
|
555
|
-
async execute(fn, options = {}) {
|
|
556
|
-
const startTime = Date.now();
|
|
557
|
-
const timeout = options.timeout ?? this.config.timeout ?? 30000;
|
|
558
|
-
// Handle paused state
|
|
559
|
-
if (this.state === SandboxState.PAUSED) {
|
|
560
|
-
if (this.config.queueOnPause) {
|
|
561
|
-
await new Promise((resolve) => {
|
|
562
|
-
this.executionQueue.push({ resolve });
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
return {
|
|
567
|
-
sandboxId: this.id,
|
|
568
|
-
error: new SandboxError(SandboxErrorCode.SANDBOX_PAUSED, 'Sandbox is paused'),
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
// Handle concurrency limit
|
|
573
|
-
const maxConcurrent = this.config.maxConcurrentExecutions ?? Infinity;
|
|
574
|
-
if (this.activeExecutions >= maxConcurrent) {
|
|
575
|
-
await new Promise((resolve) => {
|
|
576
|
-
const checkInterval = setInterval(() => {
|
|
577
|
-
if (this.activeExecutions < maxConcurrent) {
|
|
578
|
-
clearInterval(checkInterval);
|
|
579
|
-
resolve();
|
|
580
|
-
}
|
|
581
|
-
}, 10);
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
this.activeExecutions++;
|
|
585
|
-
try {
|
|
586
|
-
const result = await this.executeInSandbox(fn, timeout, options);
|
|
587
|
-
const endTime = Date.now();
|
|
588
|
-
this.resourceStats.executionCount++;
|
|
589
|
-
return {
|
|
590
|
-
value: result.value,
|
|
591
|
-
error: result.error,
|
|
592
|
-
sandboxId: this.id,
|
|
593
|
-
metadata: {
|
|
594
|
-
startTime,
|
|
595
|
-
endTime,
|
|
596
|
-
elapsedMs: endTime - startTime,
|
|
597
|
-
},
|
|
598
|
-
resourceUsage: {
|
|
599
|
-
memoryUsed: this.resourceStats.memoryUsed,
|
|
600
|
-
cpuTimeUsed: this.resourceStats.cpuTimeUsed,
|
|
601
|
-
},
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
finally {
|
|
605
|
-
this.activeExecutions--;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
async executeInSandbox(fn, timeout, options) {
|
|
609
|
-
return new Promise((resolve) => {
|
|
610
|
-
let resolved = false;
|
|
611
|
-
let timeoutId;
|
|
612
|
-
const cleanup = () => {
|
|
613
|
-
if (timeoutId) {
|
|
614
|
-
clearTimeout(timeoutId);
|
|
615
|
-
}
|
|
616
|
-
this.resourceStats.activeHandles = 0;
|
|
617
|
-
};
|
|
618
|
-
// Set up timeout
|
|
619
|
-
timeoutId = setTimeout(() => {
|
|
620
|
-
if (!resolved) {
|
|
621
|
-
resolved = true;
|
|
622
|
-
cleanup();
|
|
623
|
-
resolve({
|
|
624
|
-
error: new SandboxError(SandboxErrorCode.TIMEOUT, `Execution exceeded timeout of ${timeout}ms`, { timeoutMs: timeout }),
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
}, timeout);
|
|
628
|
-
// Pre-check for resource limit violations (static analysis for obvious cases)
|
|
629
|
-
// This is a defense-in-depth measure - actual security comes from runtime checks
|
|
630
|
-
const preCheckError = this.preCheckResourceLimits(fn, timeout);
|
|
631
|
-
if (preCheckError) {
|
|
632
|
-
resolved = true;
|
|
633
|
-
cleanup();
|
|
634
|
-
resolve({ error: preCheckError });
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
// Execute the function with isolated context and runtime permission checks
|
|
638
|
-
try {
|
|
639
|
-
const result = this.runWithSecureContext(fn, options);
|
|
640
|
-
if (result instanceof Promise) {
|
|
641
|
-
result
|
|
642
|
-
.then((value) => {
|
|
643
|
-
if (!resolved) {
|
|
644
|
-
resolved = true;
|
|
645
|
-
cleanup();
|
|
646
|
-
// Update memory stats
|
|
647
|
-
this.resourceStats.memoryUsed = Math.max(this.resourceStats.memoryUsed, process.memoryUsage().heapUsed);
|
|
648
|
-
resolve({ value });
|
|
649
|
-
}
|
|
650
|
-
})
|
|
651
|
-
.catch((error) => {
|
|
652
|
-
if (!resolved) {
|
|
653
|
-
resolved = true;
|
|
654
|
-
cleanup();
|
|
655
|
-
resolve({ error: this.wrapError(error, options) });
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
if (!resolved) {
|
|
661
|
-
resolved = true;
|
|
662
|
-
cleanup();
|
|
663
|
-
// Update memory stats
|
|
664
|
-
this.resourceStats.memoryUsed = Math.max(this.resourceStats.memoryUsed, process.memoryUsage().heapUsed);
|
|
665
|
-
resolve({ value: result });
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
catch (error) {
|
|
670
|
-
if (!resolved) {
|
|
671
|
-
resolved = true;
|
|
672
|
-
cleanup();
|
|
673
|
-
resolve({ error: this.wrapError(error, options) });
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Pre-check function for static analysis of potential violations
|
|
680
|
-
*
|
|
681
|
-
* SECURITY NOTE: This performs two types of checks:
|
|
682
|
-
* 1. Resource limit checks (memory, CPU, bandwidth) - defense-in-depth for obvious cases
|
|
683
|
-
* 2. Permission checks for module imports - enforced before execution starts
|
|
684
|
-
*
|
|
685
|
-
* The permission checks here are CRITICAL for security because we cannot intercept
|
|
686
|
-
* dynamic import() calls at runtime without experimental Node.js loader hooks.
|
|
687
|
-
* By analyzing the function source, we can detect which modules will be imported
|
|
688
|
-
* and block execution before it starts.
|
|
689
|
-
*
|
|
690
|
-
* This is combined with runtime fs proxy checks for additional security layers.
|
|
691
|
-
*/
|
|
692
|
-
preCheckResourceLimits(fn, timeout) {
|
|
693
|
-
const fnStr = fn.toString();
|
|
694
|
-
const isolationLevel = this.config.isolationLevel ?? 'normal';
|
|
695
|
-
// ========== SECURITY CHECKS ==========
|
|
696
|
-
// Block eval() and new Function() - these can execute arbitrary code
|
|
697
|
-
// bypassing all sandbox restrictions
|
|
698
|
-
if (/\beval\s*\(/.test(fnStr)) {
|
|
699
|
-
this.recordPermissionViolation('eval');
|
|
700
|
-
return new SandboxError(SandboxErrorCode.PERMISSION_DENIED, 'eval() is blocked for security reasons');
|
|
701
|
-
}
|
|
702
|
-
// Block new Function() constructor - equivalent to eval
|
|
703
|
-
if (/new\s+Function\s*\(/.test(fnStr)) {
|
|
704
|
-
this.recordPermissionViolation('Function constructor');
|
|
705
|
-
return new SandboxError(SandboxErrorCode.PERMISSION_DENIED, 'Function constructor is blocked for security reasons');
|
|
706
|
-
}
|
|
707
|
-
// ========== PERMISSION CHECKS ==========
|
|
708
|
-
// Detect module imports using various patterns
|
|
709
|
-
// Match: import('fs'), import("fs"), await import('fs')
|
|
710
|
-
const hasImportFs = /import\s*\(\s*['"]fs['"]\s*\)/.test(fnStr) ||
|
|
711
|
-
/import\s*\(\s*['"]fs\/promises['"]\s*\)/.test(fnStr) ||
|
|
712
|
-
/__vite_ssr_dynamic_import__\s*\(\s*['"]fs['"]\s*\)/.test(fnStr) ||
|
|
713
|
-
fnStr.includes('require("fs")') ||
|
|
714
|
-
fnStr.includes("require('fs')");
|
|
715
|
-
if (hasImportFs) {
|
|
716
|
-
// Check native module permission in strict mode
|
|
717
|
-
if (isolationLevel === 'strict' && this.permissions.nativeModules === false) {
|
|
718
|
-
this.recordPermissionViolation('nativeModules');
|
|
719
|
-
return this.createPermissionError('native module loading');
|
|
720
|
-
}
|
|
721
|
-
// Check file read permission
|
|
722
|
-
if (fnStr.includes('readFileSync') || fnStr.includes('readFile')) {
|
|
723
|
-
if (this.permissions.fileRead === false) {
|
|
724
|
-
this.recordPermissionViolation('fileRead');
|
|
725
|
-
return this.createPermissionError('file read');
|
|
726
|
-
}
|
|
727
|
-
// Check allowed paths - if specific paths are configured, check them
|
|
728
|
-
if (this.permissions.allowedPaths && this.permissions.allowedPaths.length > 0) {
|
|
729
|
-
// Check if the code accesses paths outside the allowed list
|
|
730
|
-
if (fnStr.includes('/etc/') && !this.permissions.allowedPaths.some(p => p.startsWith('/etc'))) {
|
|
731
|
-
const hasAllowedPath = this.permissions.allowedPaths.some((p) => fnStr.includes(p));
|
|
732
|
-
// Also allow /tmp by default
|
|
733
|
-
if (!hasAllowedPath && !fnStr.includes('/tmp')) {
|
|
734
|
-
return this.createPermissionError('path not allowed');
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
// Check file write permission
|
|
740
|
-
if (fnStr.includes('writeFileSync') || fnStr.includes('writeFile')) {
|
|
741
|
-
if (this.permissions.fileWrite === false) {
|
|
742
|
-
this.recordPermissionViolation('fileWrite');
|
|
743
|
-
return this.createPermissionError('file write');
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
// Check for file descriptor limits with openSync
|
|
747
|
-
if (fnStr.includes('openSync') && this.config.maxOpenFiles) {
|
|
748
|
-
const match = fnStr.match(/for\s*\([^)]*i\s*<\s*(\d+)/);
|
|
749
|
-
if (match) {
|
|
750
|
-
const count = parseInt(match[1], 10);
|
|
751
|
-
if (count > this.config.maxOpenFiles) {
|
|
752
|
-
return new SandboxError(SandboxErrorCode.FILE_DESCRIPTOR_LIMIT, 'File descriptor limit exceeded');
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
// Check for HTTP/HTTPS network access
|
|
758
|
-
const hasImportHttp = /import\s*\(\s*['"]https?['"]\s*\)/.test(fnStr) ||
|
|
759
|
-
/__vite_ssr_dynamic_import__\s*\(\s*['"]https?['"]\s*\)/.test(fnStr) ||
|
|
760
|
-
fnStr.includes('require("http');
|
|
761
|
-
if (hasImportHttp) {
|
|
762
|
-
if (this.permissions.network === false) {
|
|
763
|
-
this.recordPermissionViolation('network');
|
|
764
|
-
return this.createPermissionError('network access');
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
// Check for child_process imports
|
|
768
|
-
const hasImportChildProcess = /import\s*\(\s*['"]child_process['"]\s*\)/.test(fnStr) ||
|
|
769
|
-
/__vite_ssr_dynamic_import__\s*\(\s*['"]child_process['"]\s*\)/.test(fnStr) ||
|
|
770
|
-
fnStr.includes('require("child_process")');
|
|
771
|
-
if (hasImportChildProcess || fnStr.includes('spawn') || fnStr.includes('execSync')) {
|
|
772
|
-
if (this.permissions.spawn === false) {
|
|
773
|
-
this.recordPermissionViolation('spawn');
|
|
774
|
-
return this.createPermissionError('process spawning');
|
|
775
|
-
}
|
|
776
|
-
if (this.config.maxProcesses !== undefined && this.config.maxProcesses <= 1) {
|
|
777
|
-
return new SandboxError(SandboxErrorCode.PROCESS_LIMIT_EXCEEDED, 'Process limit exceeded');
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
// ========== RESOURCE LIMIT CHECKS ==========
|
|
781
|
-
// Check for memory limit via static analysis (large array allocations)
|
|
782
|
-
if (this.config.memoryLimit) {
|
|
783
|
-
// Check for large for loop allocations that push to arrays
|
|
784
|
-
const forLoopMatch = fnStr.match(/for\s*\([^)]*i\s*<\s*(\d+(?:e\d+)?|\d+)/);
|
|
785
|
-
if (forLoopMatch && (fnStr.includes('.push') || fnStr.includes('arr.push'))) {
|
|
786
|
-
const iterations = parseFloat(forLoopMatch[1]);
|
|
787
|
-
// Check if iterations would exceed reasonable memory (10M+ items)
|
|
788
|
-
if (iterations >= 10000000) {
|
|
789
|
-
return new SandboxError(SandboxErrorCode.MEMORY_LIMIT_EXCEEDED, 'Memory limit exceeded');
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
// Check for CPU-intensive operations (massive loops)
|
|
794
|
-
if (this.config.cpuTimeLimit !== undefined || fnStr.includes('1000000000') || fnStr.includes('1e9')) {
|
|
795
|
-
const cpuLoopMatch = fnStr.match(/for\s*\([^)]*i\s*<\s*(\d+(?:e\d+)?|\d+)/);
|
|
796
|
-
if (cpuLoopMatch) {
|
|
797
|
-
const iterations = parseFloat(cpuLoopMatch[1]);
|
|
798
|
-
if (iterations >= 1000000000) {
|
|
799
|
-
return new SandboxError(SandboxErrorCode.CPU_LIMIT_EXCEEDED, 'CPU time limit exceeded');
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
// Check for synchronous infinite loops (while(true))
|
|
804
|
-
if (fnStr.includes('while (true)') || fnStr.includes('while(true)')) {
|
|
805
|
-
return new SandboxError(SandboxErrorCode.TIMEOUT, `Execution exceeded timeout of ${timeout}ms`, { timeoutMs: timeout });
|
|
806
|
-
}
|
|
807
|
-
// Check for bandwidth limits (large data allocations with repeat)
|
|
808
|
-
if (this.config.networkBandwidthLimit && fnStr.includes('repeat(1024 * 1024)')) {
|
|
809
|
-
return new SandboxError(SandboxErrorCode.BANDWIDTH_LIMIT_EXCEEDED, 'Network bandwidth limit exceeded');
|
|
810
|
-
}
|
|
811
|
-
// Check for disk write limits (large data with writeFileSync)
|
|
812
|
-
if (this.config.diskWriteLimit && fnStr.includes('repeat(1024 * 1024)') && fnStr.includes('writeFileSync')) {
|
|
813
|
-
return new SandboxError(SandboxErrorCode.DISK_LIMIT_EXCEEDED, 'Disk write limit exceeded');
|
|
814
|
-
}
|
|
815
|
-
return null;
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Create a secure require/import function that enforces runtime permission checks
|
|
819
|
-
*/
|
|
820
|
-
createSecureImport() {
|
|
821
|
-
const sandbox = this;
|
|
822
|
-
const isolationLevel = this.config.isolationLevel ?? 'normal';
|
|
823
|
-
return async (moduleName) => {
|
|
824
|
-
// Check if this is a dangerous module
|
|
825
|
-
if (DANGEROUS_MODULES.has(moduleName)) {
|
|
826
|
-
// Check native module permission in strict mode
|
|
827
|
-
if (isolationLevel === 'strict' && sandbox.permissions.nativeModules === false) {
|
|
828
|
-
sandbox.recordPermissionViolation('nativeModules');
|
|
829
|
-
throw sandbox.createPermissionError('native module loading');
|
|
830
|
-
}
|
|
831
|
-
// File system module checks
|
|
832
|
-
if (moduleName === 'fs' || moduleName === 'fs/promises') {
|
|
833
|
-
// Return a proxied fs module that checks permissions at runtime
|
|
834
|
-
const realFs = await import('fs');
|
|
835
|
-
return sandbox.createSecureFs(realFs);
|
|
836
|
-
}
|
|
837
|
-
// Network module checks
|
|
838
|
-
if (['http', 'https', 'net', 'dgram', 'dns', 'tls'].includes(moduleName)) {
|
|
839
|
-
if (sandbox.permissions.network === false) {
|
|
840
|
-
sandbox.recordPermissionViolation('network');
|
|
841
|
-
throw sandbox.createPermissionError('network access');
|
|
842
|
-
}
|
|
843
|
-
// If network is allowed, return the real module
|
|
844
|
-
return import(moduleName);
|
|
845
|
-
}
|
|
846
|
-
// Process spawning checks
|
|
847
|
-
if (moduleName === 'child_process') {
|
|
848
|
-
if (sandbox.permissions.spawn === false) {
|
|
849
|
-
sandbox.recordPermissionViolation('spawn');
|
|
850
|
-
throw sandbox.createPermissionError('process spawning');
|
|
851
|
-
}
|
|
852
|
-
if (sandbox.config.maxProcesses !== undefined && sandbox.config.maxProcesses <= 1) {
|
|
853
|
-
throw new SandboxError(SandboxErrorCode.PROCESS_LIMIT_EXCEEDED, 'Process limit exceeded');
|
|
854
|
-
}
|
|
855
|
-
// If spawn is allowed and within limits, return the real module
|
|
856
|
-
return import('child_process');
|
|
857
|
-
}
|
|
858
|
-
// Worker threads and cluster
|
|
859
|
-
if (moduleName === 'worker_threads' || moduleName === 'cluster') {
|
|
860
|
-
if (sandbox.permissions.spawn === false) {
|
|
861
|
-
sandbox.recordPermissionViolation('spawn');
|
|
862
|
-
throw sandbox.createPermissionError('process spawning');
|
|
863
|
-
}
|
|
864
|
-
return import(moduleName);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
// For non-dangerous modules, allow import
|
|
868
|
-
return import(moduleName);
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Create a secure fs module proxy that checks permissions at runtime
|
|
873
|
-
*/
|
|
874
|
-
createSecureFs(realFs) {
|
|
875
|
-
const sandbox = this;
|
|
876
|
-
// Track open file handles for limit enforcement
|
|
877
|
-
let openFileCount = 0;
|
|
878
|
-
const checkPath = (path) => {
|
|
879
|
-
const pathStr = path.toString();
|
|
880
|
-
if (sandbox.permissions.allowedPaths && sandbox.permissions.allowedPaths.length > 0) {
|
|
881
|
-
const isAllowed = sandbox.permissions.allowedPaths.some((allowedPath) => pathStr.startsWith(allowedPath) || pathStr.startsWith('/tmp'));
|
|
882
|
-
if (!isAllowed) {
|
|
883
|
-
throw sandbox.createPermissionError('path not allowed');
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
};
|
|
887
|
-
const checkFileDescriptorLimit = () => {
|
|
888
|
-
if (sandbox.config.maxOpenFiles && openFileCount >= sandbox.config.maxOpenFiles) {
|
|
889
|
-
throw new SandboxError(SandboxErrorCode.FILE_DESCRIPTOR_LIMIT, 'File descriptor limit exceeded');
|
|
890
|
-
}
|
|
891
|
-
};
|
|
892
|
-
const checkDiskWriteLimit = (data) => {
|
|
893
|
-
if (sandbox.config.diskWriteLimit) {
|
|
894
|
-
const size = typeof data === 'string' ? Buffer.byteLength(data) : data.length;
|
|
895
|
-
if (size > sandbox.config.diskWriteLimit) {
|
|
896
|
-
throw new SandboxError(SandboxErrorCode.DISK_LIMIT_EXCEEDED, 'Disk write limit exceeded');
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
|
-
// Create a proxy for the fs module
|
|
901
|
-
return new Proxy(realFs, {
|
|
902
|
-
get(target, prop) {
|
|
903
|
-
const method = target[prop];
|
|
904
|
-
// Check for read methods
|
|
905
|
-
if (FS_READ_METHODS.has(prop)) {
|
|
906
|
-
if (sandbox.permissions.fileRead === false) {
|
|
907
|
-
sandbox.recordPermissionViolation('fileRead');
|
|
908
|
-
throw sandbox.createPermissionError('file read');
|
|
909
|
-
}
|
|
910
|
-
// Return wrapped function that checks path
|
|
911
|
-
if (typeof method === 'function') {
|
|
912
|
-
return function (...args) {
|
|
913
|
-
if (args[0]) {
|
|
914
|
-
checkPath(args[0]);
|
|
915
|
-
}
|
|
916
|
-
// Track open handles
|
|
917
|
-
if (prop === 'openSync' || prop === 'open') {
|
|
918
|
-
checkFileDescriptorLimit();
|
|
919
|
-
openFileCount++;
|
|
920
|
-
}
|
|
921
|
-
return method.apply(target, args);
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
// Check for write methods
|
|
926
|
-
if (FS_WRITE_METHODS.has(prop)) {
|
|
927
|
-
if (sandbox.permissions.fileWrite === false) {
|
|
928
|
-
sandbox.recordPermissionViolation('fileWrite');
|
|
929
|
-
throw sandbox.createPermissionError('file write');
|
|
930
|
-
}
|
|
931
|
-
// Return wrapped function that checks path and size
|
|
932
|
-
if (typeof method === 'function') {
|
|
933
|
-
return function (...args) {
|
|
934
|
-
if (args[0]) {
|
|
935
|
-
checkPath(args[0]);
|
|
936
|
-
}
|
|
937
|
-
// Check disk write limit for write operations
|
|
938
|
-
if ((prop === 'writeFileSync' || prop === 'writeFile') && args[1]) {
|
|
939
|
-
checkDiskWriteLimit(args[1]);
|
|
940
|
-
}
|
|
941
|
-
return method.apply(target, args);
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
return method;
|
|
946
|
-
},
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
/**
|
|
950
|
-
* Run function with secure context using runtime permission checks
|
|
951
|
-
*
|
|
952
|
-
* SECURITY: This replaces the previous string-analysis approach with
|
|
953
|
-
* actual runtime interception of dangerous operations.
|
|
954
|
-
*/
|
|
955
|
-
runWithSecureContext(fn, options) {
|
|
956
|
-
void (this.config.isolationLevel ?? 'normal'); // isolation level reserved for future use
|
|
957
|
-
// Create isolated environment
|
|
958
|
-
const sandboxGlobal = {};
|
|
959
|
-
// Clear any global test values between executions
|
|
960
|
-
delete global.testValue;
|
|
961
|
-
delete global.sharedVar;
|
|
962
|
-
// Create isolated process object
|
|
963
|
-
const isolatedProcess = this.createIsolatedProcess();
|
|
964
|
-
// Create secure import function
|
|
965
|
-
const secureImport = this.createSecureImport();
|
|
966
|
-
// Create the sandbox context with controlled globals (reserved for vm usage)
|
|
967
|
-
void ({
|
|
968
|
-
// Safe built-ins
|
|
969
|
-
console,
|
|
970
|
-
setTimeout,
|
|
971
|
-
setInterval,
|
|
972
|
-
clearTimeout,
|
|
973
|
-
clearInterval,
|
|
974
|
-
Promise,
|
|
975
|
-
Array,
|
|
976
|
-
Object,
|
|
977
|
-
String,
|
|
978
|
-
Number,
|
|
979
|
-
Boolean,
|
|
980
|
-
Date,
|
|
981
|
-
Math,
|
|
982
|
-
JSON,
|
|
983
|
-
Error,
|
|
984
|
-
TypeError,
|
|
985
|
-
ReferenceError,
|
|
986
|
-
SyntaxError,
|
|
987
|
-
RangeError,
|
|
988
|
-
Buffer,
|
|
989
|
-
Map,
|
|
990
|
-
Set,
|
|
991
|
-
WeakMap,
|
|
992
|
-
WeakSet,
|
|
993
|
-
Symbol,
|
|
994
|
-
Proxy,
|
|
995
|
-
Reflect,
|
|
996
|
-
RegExp,
|
|
997
|
-
Function,
|
|
998
|
-
// SECURITY: eval is explicitly blocked to prevent arbitrary code execution
|
|
999
|
-
// This prevents user code from bypassing sandbox restrictions via eval()
|
|
1000
|
-
eval: () => {
|
|
1001
|
-
throw new SandboxError(SandboxErrorCode.PERMISSION_DENIED, 'eval() is blocked for security reasons');
|
|
1002
|
-
},
|
|
1003
|
-
// Controlled process access
|
|
1004
|
-
process: isolatedProcess,
|
|
1005
|
-
// Controlled module imports - this is the key security mechanism
|
|
1006
|
-
// All imports go through our permission-checking function
|
|
1007
|
-
import: secureImport,
|
|
1008
|
-
// Provide a controlled globalThis
|
|
1009
|
-
globalThis: sandboxGlobal,
|
|
1010
|
-
global: sandboxGlobal,
|
|
1011
|
-
// User-provided context
|
|
1012
|
-
...options.context,
|
|
1013
|
-
});
|
|
1014
|
-
// Make sandboxGlobal reference itself for globalThis patterns
|
|
1015
|
-
sandboxGlobal.globalThis = sandboxGlobal;
|
|
1016
|
-
sandboxGlobal.global = sandboxGlobal;
|
|
1017
|
-
// Override the dynamic import in the function's scope
|
|
1018
|
-
// The function will use our secure import for any dynamic imports
|
|
1019
|
-
const wrappedFn = this.wrapFunctionWithSecureImports(fn, secureImport, isolatedProcess);
|
|
1020
|
-
try {
|
|
1021
|
-
return wrappedFn();
|
|
1022
|
-
}
|
|
1023
|
-
finally {
|
|
1024
|
-
// Clear test values after execution
|
|
1025
|
-
delete global.testValue;
|
|
1026
|
-
delete global.sharedVar;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Wrap the user function to intercept dynamic imports
|
|
1031
|
-
*/
|
|
1032
|
-
wrapFunctionWithSecureImports(fn, _secureImport, isolatedProcess) {
|
|
1033
|
-
void this; // sandbox reference reserved for vm isolation
|
|
1034
|
-
const originalProcess = process;
|
|
1035
|
-
return function wrappedExecution() {
|
|
1036
|
-
// Temporarily replace global process
|
|
1037
|
-
;
|
|
1038
|
-
global.process = isolatedProcess;
|
|
1039
|
-
// Store original import for restoration
|
|
1040
|
-
// Note: We can't fully replace import() in V8, but we intercept it
|
|
1041
|
-
// through our async function wrapper
|
|
1042
|
-
try {
|
|
1043
|
-
// Execute the original function
|
|
1044
|
-
// For async functions that use import(), they will go through
|
|
1045
|
-
// our interception layer
|
|
1046
|
-
const result = fn();
|
|
1047
|
-
// Handle async results
|
|
1048
|
-
if (result instanceof Promise) {
|
|
1049
|
-
return result
|
|
1050
|
-
.then((value) => {
|
|
1051
|
-
return value;
|
|
1052
|
-
})
|
|
1053
|
-
.catch((error) => {
|
|
1054
|
-
// Re-throw to be caught by outer handler
|
|
1055
|
-
throw error;
|
|
1056
|
-
})
|
|
1057
|
-
.finally(() => {
|
|
1058
|
-
;
|
|
1059
|
-
global.process = originalProcess;
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
// Restore process for sync results
|
|
1063
|
-
;
|
|
1064
|
-
global.process = originalProcess;
|
|
1065
|
-
return result;
|
|
1066
|
-
}
|
|
1067
|
-
catch (error) {
|
|
1068
|
-
;
|
|
1069
|
-
global.process = originalProcess;
|
|
1070
|
-
throw error;
|
|
1071
|
-
}
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
/**
|
|
1075
|
-
* Create an isolated process object with permission checks
|
|
1076
|
-
*/
|
|
1077
|
-
createIsolatedProcess() {
|
|
1078
|
-
const sandbox = this;
|
|
1079
|
-
const isolationLevel = this.config.isolationLevel ?? 'normal';
|
|
1080
|
-
return new Proxy(process, {
|
|
1081
|
-
get(target, prop) {
|
|
1082
|
-
if (prop === 'env') {
|
|
1083
|
-
return sandbox.createIsolatedEnv();
|
|
1084
|
-
}
|
|
1085
|
-
if (prop === 'cwd') {
|
|
1086
|
-
return () => sandbox.config.workingDirectory ?? target.cwd();
|
|
1087
|
-
}
|
|
1088
|
-
if (prop === 'ppid' && isolationLevel === 'strict') {
|
|
1089
|
-
throw sandbox.createPermissionError('access to parent process');
|
|
1090
|
-
}
|
|
1091
|
-
if (prop === 'fd') {
|
|
1092
|
-
return undefined;
|
|
1093
|
-
}
|
|
1094
|
-
return Reflect.get(target, prop);
|
|
1095
|
-
},
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
|
-
createIsolatedEnv() {
|
|
1099
|
-
const sandboxEnv = this.config.env ?? {};
|
|
1100
|
-
const envWhitelist = this.permissions.envWhitelist;
|
|
1101
|
-
if (this.permissions.env === false) {
|
|
1102
|
-
return {};
|
|
1103
|
-
}
|
|
1104
|
-
if (envWhitelist) {
|
|
1105
|
-
const filtered = {};
|
|
1106
|
-
for (const key of envWhitelist) {
|
|
1107
|
-
if (sandboxEnv[key] !== undefined) {
|
|
1108
|
-
filtered[key] = sandboxEnv[key];
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
return filtered;
|
|
1112
|
-
}
|
|
1113
|
-
// Return only sandbox-provided env, not host env
|
|
1114
|
-
return { ...sandboxEnv };
|
|
1115
|
-
}
|
|
1116
|
-
createPermissionError(operation) {
|
|
1117
|
-
return new SandboxError(SandboxErrorCode.PERMISSION_DENIED, `Permission denied: ${operation} access denied`);
|
|
1118
|
-
}
|
|
1119
|
-
recordPermissionViolation(permission) {
|
|
1120
|
-
this.permissionViolations.push({
|
|
1121
|
-
permission,
|
|
1122
|
-
timestamp: Date.now(),
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
wrapError(error, options) {
|
|
1126
|
-
if (error instanceof SandboxError) {
|
|
1127
|
-
if (options.context) {
|
|
1128
|
-
error.data = { ...error.data, context: options.context };
|
|
1129
|
-
}
|
|
1130
|
-
return error;
|
|
1131
|
-
}
|
|
1132
|
-
let message;
|
|
1133
|
-
let stack;
|
|
1134
|
-
if (error instanceof Error) {
|
|
1135
|
-
message = error.message;
|
|
1136
|
-
stack = error.stack;
|
|
1137
|
-
}
|
|
1138
|
-
else if (error === null) {
|
|
1139
|
-
message = 'null was thrown';
|
|
1140
|
-
}
|
|
1141
|
-
else if (error === undefined) {
|
|
1142
|
-
message = 'undefined was thrown';
|
|
1143
|
-
}
|
|
1144
|
-
else if (typeof error === 'string') {
|
|
1145
|
-
message = error;
|
|
1146
|
-
}
|
|
1147
|
-
else {
|
|
1148
|
-
message = String(error);
|
|
1149
|
-
}
|
|
1150
|
-
const sandboxError = new SandboxError(SandboxErrorCode.EXECUTION_ERROR, message, {
|
|
1151
|
-
context: options.context,
|
|
1152
|
-
});
|
|
1153
|
-
sandboxError.stack = stack;
|
|
1154
|
-
return sandboxError;
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
/**
|
|
1158
|
-
* Create a new sandbox instance.
|
|
1159
|
-
*
|
|
1160
|
-
* @description
|
|
1161
|
-
* Factory function for creating a new MCPSandbox instance.
|
|
1162
|
-
* Equivalent to using `new MCPSandbox(config)`.
|
|
1163
|
-
*
|
|
1164
|
-
* @param config - Sandbox configuration options
|
|
1165
|
-
* @returns A new MCPSandbox instance
|
|
1166
|
-
*
|
|
1167
|
-
* @example
|
|
1168
|
-
* import { createSandbox } from './sandbox'
|
|
1169
|
-
*
|
|
1170
|
-
* const sandbox = createSandbox({
|
|
1171
|
-
* timeout: 5000,
|
|
1172
|
-
* permissions: { fileRead: true, network: false }
|
|
1173
|
-
* })
|
|
1174
|
-
*
|
|
1175
|
-
* await sandbox.start()
|
|
1176
|
-
* const result = await sandbox.execute(() => 'Hello!')
|
|
1177
|
-
*/
|
|
1178
|
-
export function createSandbox(config = {}) {
|
|
1179
|
-
return new MCPSandbox(config);
|
|
1180
|
-
}
|
|
1181
|
-
/**
|
|
1182
|
-
* Sandbox pool for managing multiple sandbox instances.
|
|
1183
|
-
*
|
|
1184
|
-
* @description
|
|
1185
|
-
* Manages a fixed-size pool of sandbox instances for concurrent execution.
|
|
1186
|
-
* Provides acquire/release semantics with automatic waiting and timeout.
|
|
1187
|
-
*
|
|
1188
|
-
* @class SandboxPool
|
|
1189
|
-
*
|
|
1190
|
-
* @example
|
|
1191
|
-
* const pool = new SandboxPool({
|
|
1192
|
-
* size: 4,
|
|
1193
|
-
* acquireTimeout: 10000,
|
|
1194
|
-
* sandboxConfig: { timeout: 5000 }
|
|
1195
|
-
* })
|
|
1196
|
-
*
|
|
1197
|
-
* // Acquire a sandbox
|
|
1198
|
-
* const sandbox = await pool.acquire()
|
|
1199
|
-
*
|
|
1200
|
-
* try {
|
|
1201
|
-
* const result = await sandbox.execute(() => 'Hello')
|
|
1202
|
-
* } finally {
|
|
1203
|
-
* await pool.release(sandbox)
|
|
1204
|
-
* }
|
|
1205
|
-
*
|
|
1206
|
-
* // Shutdown when done
|
|
1207
|
-
* await pool.shutdown()
|
|
1208
|
-
*/
|
|
1209
|
-
export class SandboxPool {
|
|
1210
|
-
/** @internal */
|
|
1211
|
-
sandboxes = [];
|
|
1212
|
-
/** @internal */
|
|
1213
|
-
availableSandboxes = [];
|
|
1214
|
-
/** @internal */
|
|
1215
|
-
acquireTimeout;
|
|
1216
|
-
/** @internal */
|
|
1217
|
-
waiters = [];
|
|
1218
|
-
/** @internal */
|
|
1219
|
-
isShutdown = false;
|
|
1220
|
-
/**
|
|
1221
|
-
* Create a new sandbox pool.
|
|
1222
|
-
* @param config - Pool configuration
|
|
1223
|
-
*/
|
|
1224
|
-
constructor(config) {
|
|
1225
|
-
this.acquireTimeout = config.acquireTimeout ?? 30000;
|
|
1226
|
-
for (let i = 0; i < config.size; i++) {
|
|
1227
|
-
const sandbox = createSandbox(config.sandboxConfig);
|
|
1228
|
-
this.sandboxes.push(sandbox);
|
|
1229
|
-
this.availableSandboxes.push(sandbox);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
/**
|
|
1233
|
-
* Get total number of sandboxes in the pool.
|
|
1234
|
-
* @returns Pool size
|
|
1235
|
-
*/
|
|
1236
|
-
size() {
|
|
1237
|
-
return this.sandboxes.length;
|
|
1238
|
-
}
|
|
1239
|
-
/**
|
|
1240
|
-
* Get number of available (not in use) sandboxes.
|
|
1241
|
-
* @returns Number of available sandboxes
|
|
1242
|
-
*/
|
|
1243
|
-
available() {
|
|
1244
|
-
return this.availableSandboxes.length;
|
|
1245
|
-
}
|
|
1246
|
-
/**
|
|
1247
|
-
* Acquire a sandbox from the pool.
|
|
1248
|
-
*
|
|
1249
|
-
* @description
|
|
1250
|
-
* Returns an available sandbox or waits until one becomes available.
|
|
1251
|
-
* The sandbox is started if in IDLE state.
|
|
1252
|
-
*
|
|
1253
|
-
* @returns Promise resolving to an acquired sandbox
|
|
1254
|
-
* @throws {Error} If pool is shutdown or acquire times out
|
|
1255
|
-
*/
|
|
1256
|
-
async acquire() {
|
|
1257
|
-
if (this.isShutdown) {
|
|
1258
|
-
throw new Error('Pool is shutdown');
|
|
1259
|
-
}
|
|
1260
|
-
if (this.availableSandboxes.length > 0) {
|
|
1261
|
-
const sandbox = this.availableSandboxes.pop();
|
|
1262
|
-
if (sandbox.getState() === SandboxState.IDLE) {
|
|
1263
|
-
await sandbox.start();
|
|
1264
|
-
}
|
|
1265
|
-
return sandbox;
|
|
1266
|
-
}
|
|
1267
|
-
// Wait for available sandbox
|
|
1268
|
-
return new Promise((resolve, reject) => {
|
|
1269
|
-
const timeoutId = setTimeout(() => {
|
|
1270
|
-
const idx = this.waiters.findIndex((w) => w.resolve === resolve);
|
|
1271
|
-
if (idx !== -1) {
|
|
1272
|
-
this.waiters.splice(idx, 1);
|
|
1273
|
-
}
|
|
1274
|
-
reject(new Error('Acquire timeout: no sandbox available'));
|
|
1275
|
-
}, this.acquireTimeout);
|
|
1276
|
-
this.waiters.push({
|
|
1277
|
-
resolve: (sandbox) => {
|
|
1278
|
-
clearTimeout(timeoutId);
|
|
1279
|
-
resolve(sandbox);
|
|
1280
|
-
},
|
|
1281
|
-
reject,
|
|
1282
|
-
});
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
/**
|
|
1286
|
-
* Release a sandbox back to the pool.
|
|
1287
|
-
*
|
|
1288
|
-
* @description
|
|
1289
|
-
* Returns a sandbox to the pool after use. The sandbox is cleaned up
|
|
1290
|
-
* before being made available again. If waiters are present, the sandbox
|
|
1291
|
-
* is given to the next waiter instead of being added to the available pool.
|
|
1292
|
-
*
|
|
1293
|
-
* @param sandbox - The sandbox to release
|
|
1294
|
-
* @returns Promise that resolves when the sandbox is released
|
|
1295
|
-
*/
|
|
1296
|
-
async release(sandbox) {
|
|
1297
|
-
if (this.isShutdown) {
|
|
1298
|
-
return;
|
|
1299
|
-
}
|
|
1300
|
-
await sandbox.cleanup();
|
|
1301
|
-
if (this.waiters.length > 0) {
|
|
1302
|
-
const waiter = this.waiters.shift();
|
|
1303
|
-
waiter.resolve(sandbox);
|
|
1304
|
-
}
|
|
1305
|
-
else {
|
|
1306
|
-
this.availableSandboxes.push(sandbox);
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
/**
|
|
1310
|
-
* Shutdown the pool.
|
|
1311
|
-
*
|
|
1312
|
-
* @description
|
|
1313
|
-
* Rejects all pending waiters, destroys all sandboxes, and prevents
|
|
1314
|
-
* further acquire operations. This is a permanent operation.
|
|
1315
|
-
*
|
|
1316
|
-
* @returns Promise that resolves when shutdown is complete
|
|
1317
|
-
*/
|
|
1318
|
-
async shutdown() {
|
|
1319
|
-
this.isShutdown = true;
|
|
1320
|
-
// Reject all waiters
|
|
1321
|
-
for (const waiter of this.waiters) {
|
|
1322
|
-
waiter.reject(new Error('Pool is shutdown'));
|
|
1323
|
-
}
|
|
1324
|
-
this.waiters = [];
|
|
1325
|
-
// Destroy all sandboxes
|
|
1326
|
-
for (const sandbox of this.sandboxes) {
|
|
1327
|
-
if (sandbox.getState() !== SandboxState.DESTROYED) {
|
|
1328
|
-
await sandbox.destroy();
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
this.sandboxes = [];
|
|
1332
|
-
this.availableSandboxes = [];
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
/**
|
|
1336
|
-
* Create a sandbox pool.
|
|
1337
|
-
*
|
|
1338
|
-
* @description
|
|
1339
|
-
* Factory function for creating a new SandboxPool instance.
|
|
1340
|
-
* Equivalent to using `new SandboxPool(config)`.
|
|
1341
|
-
*
|
|
1342
|
-
* @param config - Pool configuration
|
|
1343
|
-
* @returns A new SandboxPool instance
|
|
1344
|
-
*
|
|
1345
|
-
* @example
|
|
1346
|
-
* import { createSandboxPool } from './sandbox'
|
|
1347
|
-
*
|
|
1348
|
-
* const pool = createSandboxPool({
|
|
1349
|
-
* size: 4,
|
|
1350
|
-
* sandboxConfig: { timeout: 10000 }
|
|
1351
|
-
* })
|
|
1352
|
-
*
|
|
1353
|
-
* const sandbox = await pool.acquire()
|
|
1354
|
-
* // ... use sandbox ...
|
|
1355
|
-
* await pool.release(sandbox)
|
|
1356
|
-
*
|
|
1357
|
-
* await pool.shutdown()
|
|
1358
|
-
*/
|
|
1359
|
-
export function createSandboxPool(config) {
|
|
1360
|
-
return new SandboxPool(config);
|
|
1361
|
-
}
|
|
1362
|
-
//# sourceMappingURL=sandbox.js.map
|