@neverinfamous/mysql-mcp 2.2.0 → 2.3.1
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/.github/workflows/codeql.yml +0 -8
- package/.github/workflows/docker-publish.yml +11 -10
- package/CHANGELOG.md +96 -0
- package/CODE_MODE.md +245 -0
- package/DOCKER_README.md +71 -254
- package/Dockerfile +5 -0
- package/README.md +102 -55
- package/VERSION +1 -1
- package/dist/adapters/mysql/MySQLAdapter.d.ts +4 -0
- package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
- package/dist/adapters/mysql/MySQLAdapter.js +9 -0
- package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
- package/dist/adapters/mysql/prompts/index.d.ts +8 -1
- package/dist/adapters/mysql/prompts/index.d.ts.map +1 -1
- package/dist/adapters/mysql/prompts/index.js +8 -1
- package/dist/adapters/mysql/prompts/index.js.map +1 -1
- package/dist/adapters/mysql/prompts/routerSetup.d.ts.map +1 -1
- package/dist/adapters/mysql/prompts/routerSetup.js +5 -0
- package/dist/adapters/mysql/prompts/routerSetup.js.map +1 -1
- package/dist/adapters/mysql/resources/capabilities.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/capabilities.js +6 -5
- package/dist/adapters/mysql/resources/capabilities.js.map +1 -1
- package/dist/adapters/mysql/resources/index.d.ts +9 -1
- package/dist/adapters/mysql/resources/index.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/index.js +9 -1
- package/dist/adapters/mysql/resources/index.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.js +3 -3
- package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/maintenance.js +5 -5
- package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +26 -5
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
- package/dist/adapters/mysql/tools/codemode/index.d.ts +38 -0
- package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -0
- package/dist/adapters/mysql/tools/codemode/index.js +203 -0
- package/dist/adapters/mysql/tools/codemode/index.js.map +1 -0
- package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/core.js +32 -20
- package/dist/adapters/mysql/tools/core.js.map +1 -1
- package/dist/adapters/mysql/tools/events.js +18 -6
- package/dist/adapters/mysql/tools/events.js.map +1 -1
- package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/core.js +5 -5
- package/dist/adapters/mysql/tools/json/core.js.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.js +9 -3
- package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
- package/dist/adapters/mysql/tools/partitioning.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/partitioning.js +38 -6
- package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
- package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/performance/analysis.js +67 -20
- package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
- package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/performance/optimization.js +36 -6
- package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
- package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/security/data-protection.js +9 -4
- package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/common.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/common.js +28 -2
- package/dist/adapters/mysql/tools/shell/common.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.js +54 -4
- package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.js +10 -2
- package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.js +18 -0
- package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.js +5 -0
- package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
- package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/text/fulltext.js +6 -4
- package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
- package/dist/adapters/mysql/tools/text/processing.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/text/processing.js +10 -45
- package/dist/adapters/mysql/tools/text/processing.js.map +1 -1
- package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/transactions.js +8 -8
- package/dist/adapters/mysql/tools/transactions.js.map +1 -1
- package/dist/adapters/mysql/types.d.ts +968 -78
- package/dist/adapters/mysql/types.d.ts.map +1 -1
- package/dist/adapters/mysql/types.js +1084 -78
- package/dist/adapters/mysql/types.js.map +1 -1
- package/dist/auth/scopes.d.ts.map +1 -1
- package/dist/auth/scopes.js +1 -0
- package/dist/auth/scopes.js.map +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +12 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/codemode/api.d.ts +69 -0
- package/dist/codemode/api.d.ts.map +1 -0
- package/dist/codemode/api.js +1035 -0
- package/dist/codemode/api.js.map +1 -0
- package/dist/codemode/index.d.ts +13 -0
- package/dist/codemode/index.d.ts.map +1 -0
- package/dist/codemode/index.js +17 -0
- package/dist/codemode/index.js.map +1 -0
- package/dist/codemode/sandbox-factory.d.ts +72 -0
- package/dist/codemode/sandbox-factory.d.ts.map +1 -0
- package/dist/codemode/sandbox-factory.js +88 -0
- package/dist/codemode/sandbox-factory.js.map +1 -0
- package/dist/codemode/sandbox.d.ts +96 -0
- package/dist/codemode/sandbox.d.ts.map +1 -0
- package/dist/codemode/sandbox.js +345 -0
- package/dist/codemode/sandbox.js.map +1 -0
- package/dist/codemode/security.d.ts +44 -0
- package/dist/codemode/security.d.ts.map +1 -0
- package/dist/codemode/security.js +149 -0
- package/dist/codemode/security.js.map +1 -0
- package/dist/codemode/types.d.ts +137 -0
- package/dist/codemode/types.d.ts.map +1 -0
- package/dist/codemode/types.js +46 -0
- package/dist/codemode/types.js.map +1 -0
- package/dist/codemode/worker-sandbox.d.ts +82 -0
- package/dist/codemode/worker-sandbox.d.ts.map +1 -0
- package/dist/codemode/worker-sandbox.js +244 -0
- package/dist/codemode/worker-sandbox.js.map +1 -0
- package/dist/codemode/worker-script.d.ts +8 -0
- package/dist/codemode/worker-script.d.ts.map +1 -0
- package/dist/codemode/worker-script.js +113 -0
- package/dist/codemode/worker-script.js.map +1 -0
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +33 -9
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/filtering/ToolConstants.d.ts +11 -11
- package/dist/filtering/ToolConstants.d.ts.map +1 -1
- package/dist/filtering/ToolConstants.js +37 -19
- package/dist/filtering/ToolConstants.js.map +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +12 -0
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/server/McpServer.js +1 -1
- package/dist/server/McpServer.js.map +1 -1
- package/dist/types/modules/server.d.ts +2 -0
- package/dist/types/modules/server.d.ts.map +1 -1
- package/dist/types/modules/tools.d.ts +1 -1
- package/dist/types/modules/tools.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +12 -7
- package/releases/v2.2.0-release-notes.md +18 -18
- package/releases/v2.3.0-release-notes.md +191 -0
- package/releases/v2.3.1-release-notes.md +34 -0
- package/src/__tests__/perf.test.ts +12 -12
- package/src/adapters/mysql/MySQLAdapter.ts +10 -0
- package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
- package/src/adapters/mysql/prompts/index.ts +8 -1
- package/src/adapters/mysql/prompts/routerSetup.ts +5 -0
- package/src/adapters/mysql/resources/__tests__/capabilities.test.ts +50 -1
- package/src/adapters/mysql/resources/capabilities.ts +6 -4
- package/src/adapters/mysql/resources/index.ts +9 -1
- package/src/adapters/mysql/tools/__tests__/core.test.ts +68 -0
- package/src/adapters/mysql/tools/__tests__/events.test.ts +56 -2
- package/src/adapters/mysql/tools/__tests__/json_core.test.ts +1 -1
- package/src/adapters/mysql/tools/__tests__/json_helpers.test.ts +46 -4
- package/src/adapters/mysql/tools/__tests__/replication.test.ts +144 -42
- package/src/adapters/mysql/tools/__tests__/security.test.ts +39 -0
- package/src/adapters/mysql/tools/__tests__/spatial.test.ts +39 -7
- package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +35 -3
- package/src/adapters/mysql/tools/__tests__/transactions.test.ts +3 -5
- package/src/adapters/mysql/tools/admin/backup.ts +8 -3
- package/src/adapters/mysql/tools/admin/maintenance.ts +8 -4
- package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +35 -0
- package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +26 -5
- package/src/adapters/mysql/tools/codemode/index.ts +249 -0
- package/src/adapters/mysql/tools/core.ts +44 -27
- package/src/adapters/mysql/tools/events.ts +23 -7
- package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +59 -14
- package/src/adapters/mysql/tools/json/core.ts +8 -4
- package/src/adapters/mysql/tools/json/helpers.ts +13 -3
- package/src/adapters/mysql/tools/partitioning.ts +53 -6
- package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +227 -4
- package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +35 -0
- package/src/adapters/mysql/tools/performance/analysis.ts +75 -21
- package/src/adapters/mysql/tools/performance/optimization.ts +44 -6
- package/src/adapters/mysql/tools/security/data-protection.ts +10 -4
- package/src/adapters/mysql/tools/shell/__tests__/common.test.ts +46 -0
- package/src/adapters/mysql/tools/shell/__tests__/restore.test.ts +28 -1
- package/src/adapters/mysql/tools/shell/common.ts +34 -2
- package/src/adapters/mysql/tools/shell/restore.ts +70 -7
- package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +29 -0
- package/src/adapters/mysql/tools/spatial/operations.ts +13 -2
- package/src/adapters/mysql/tools/spatial/setup.ts +23 -0
- package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +21 -0
- package/src/adapters/mysql/tools/sysschema/resources.ts +5 -0
- package/src/adapters/mysql/tools/text/fulltext.ts +13 -5
- package/src/adapters/mysql/tools/text/processing.ts +20 -49
- package/src/adapters/mysql/tools/transactions.ts +11 -7
- package/src/adapters/mysql/types.ts +1241 -87
- package/src/auth/scopes.ts +1 -0
- package/src/cli/args.ts +14 -0
- package/src/codemode/api.ts +1224 -0
- package/src/codemode/index.ts +51 -0
- package/src/codemode/sandbox-factory.ts +146 -0
- package/src/codemode/sandbox.ts +450 -0
- package/src/codemode/security.ts +188 -0
- package/src/codemode/types.ts +194 -0
- package/src/codemode/worker-sandbox.ts +326 -0
- package/src/codemode/worker-script.ts +144 -0
- package/src/constants/ServerInstructions.ts +33 -9
- package/src/filtering/ToolConstants.ts +37 -19
- package/src/filtering/ToolFilter.ts +15 -0
- package/src/filtering/__tests__/ToolFilter.test.ts +65 -38
- package/src/server/McpServer.ts +1 -1
- package/src/types/modules/server.ts +3 -0
- package/src/types/modules/tools.ts +2 -1
- package/src/utils/logger.ts +2 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mysql-mcp - Code Mode Module
|
|
3
|
+
*
|
|
4
|
+
* Exports for the sandboxed code execution environment.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
SandboxOptions,
|
|
10
|
+
PoolOptions,
|
|
11
|
+
SandboxResult,
|
|
12
|
+
ExecutionMetrics,
|
|
13
|
+
SecurityConfig,
|
|
14
|
+
ValidationResult,
|
|
15
|
+
ExecutionRecord,
|
|
16
|
+
ExecuteCodeOptions,
|
|
17
|
+
ExecuteCodeResult,
|
|
18
|
+
GroupApi,
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
DEFAULT_SANDBOX_OPTIONS,
|
|
23
|
+
DEFAULT_POOL_OPTIONS,
|
|
24
|
+
DEFAULT_SECURITY_CONFIG,
|
|
25
|
+
} from "./types.js";
|
|
26
|
+
|
|
27
|
+
// Sandbox (VM-based)
|
|
28
|
+
export { CodeModeSandbox, SandboxPool } from "./sandbox.js";
|
|
29
|
+
|
|
30
|
+
// Worker Sandbox (worker_threads-based)
|
|
31
|
+
export { WorkerSandbox, WorkerSandboxPool } from "./worker-sandbox.js";
|
|
32
|
+
|
|
33
|
+
// Sandbox Factory (mode selection)
|
|
34
|
+
export {
|
|
35
|
+
setDefaultSandboxMode,
|
|
36
|
+
getDefaultSandboxMode,
|
|
37
|
+
getAvailableSandboxModes,
|
|
38
|
+
createSandbox,
|
|
39
|
+
createSandboxPool,
|
|
40
|
+
getSandboxModeInfo,
|
|
41
|
+
type SandboxMode,
|
|
42
|
+
type ISandbox,
|
|
43
|
+
type ISandboxPool,
|
|
44
|
+
type SandboxModeInfo,
|
|
45
|
+
} from "./sandbox-factory.js";
|
|
46
|
+
|
|
47
|
+
// Security
|
|
48
|
+
export { CodeModeSecurityManager } from "./security.js";
|
|
49
|
+
|
|
50
|
+
// API
|
|
51
|
+
export { MysqlApi, createMysqlApi } from "./api.js";
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mysql-mcp - Sandbox Factory
|
|
3
|
+
*
|
|
4
|
+
* Factory functions for creating sandbox instances with configurable isolation modes.
|
|
5
|
+
* Allows runtime selection between vm-based and worker-based sandboxes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CodeModeSandbox, SandboxPool } from "./sandbox.js";
|
|
9
|
+
import { WorkerSandbox, WorkerSandboxPool } from "./worker-sandbox.js";
|
|
10
|
+
import { logger } from "../utils/logger.js";
|
|
11
|
+
import type { SandboxOptions, PoolOptions, SandboxResult } from "./types.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sandbox isolation mode
|
|
15
|
+
*/
|
|
16
|
+
export type SandboxMode = "vm" | "worker";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Unified sandbox interface
|
|
20
|
+
*/
|
|
21
|
+
export interface ISandbox {
|
|
22
|
+
execute(
|
|
23
|
+
code: string,
|
|
24
|
+
apiBindings: Record<string, unknown>,
|
|
25
|
+
): Promise<SandboxResult>;
|
|
26
|
+
isHealthy(): boolean;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Unified sandbox pool interface
|
|
32
|
+
*/
|
|
33
|
+
export interface ISandboxPool {
|
|
34
|
+
initialize(): void;
|
|
35
|
+
execute(
|
|
36
|
+
code: string,
|
|
37
|
+
apiBindings: Record<string, unknown>,
|
|
38
|
+
): Promise<SandboxResult>;
|
|
39
|
+
getStats(): { available: number; inUse: number; max: number };
|
|
40
|
+
dispose(): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Mode info for documentation/selection
|
|
45
|
+
*/
|
|
46
|
+
export interface SandboxModeInfo {
|
|
47
|
+
name: string;
|
|
48
|
+
isolation: string;
|
|
49
|
+
performance: string;
|
|
50
|
+
security: string;
|
|
51
|
+
requirements: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Default mode (module-level state)
|
|
55
|
+
let defaultMode: SandboxMode = "vm";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set the default sandbox mode
|
|
59
|
+
*/
|
|
60
|
+
export function setDefaultSandboxMode(mode: SandboxMode): void {
|
|
61
|
+
defaultMode = mode;
|
|
62
|
+
logger.info(`Sandbox default mode set to: ${mode}`, {
|
|
63
|
+
module: "CODEMODE" as const,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the current default mode
|
|
69
|
+
*/
|
|
70
|
+
export function getDefaultSandboxMode(): SandboxMode {
|
|
71
|
+
return defaultMode;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get available sandbox modes
|
|
76
|
+
*/
|
|
77
|
+
export function getAvailableSandboxModes(): SandboxMode[] {
|
|
78
|
+
return ["vm", "worker"];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a sandbox instance
|
|
83
|
+
* @param mode - Isolation mode ('vm' or 'worker')
|
|
84
|
+
* @param options - Sandbox options
|
|
85
|
+
*/
|
|
86
|
+
export function createSandbox(
|
|
87
|
+
mode?: SandboxMode,
|
|
88
|
+
options?: SandboxOptions,
|
|
89
|
+
): ISandbox {
|
|
90
|
+
const selectedMode = mode ?? defaultMode;
|
|
91
|
+
|
|
92
|
+
switch (selectedMode) {
|
|
93
|
+
case "worker":
|
|
94
|
+
return WorkerSandbox.create(options);
|
|
95
|
+
case "vm":
|
|
96
|
+
default:
|
|
97
|
+
return CodeModeSandbox.create(options);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create a sandbox pool
|
|
103
|
+
* @param mode - Isolation mode ('vm' or 'worker')
|
|
104
|
+
* @param poolOptions - Pool configuration
|
|
105
|
+
* @param sandboxOptions - Sandbox configuration
|
|
106
|
+
*/
|
|
107
|
+
export function createSandboxPool(
|
|
108
|
+
mode?: SandboxMode,
|
|
109
|
+
poolOptions?: PoolOptions,
|
|
110
|
+
sandboxOptions?: SandboxOptions,
|
|
111
|
+
): ISandboxPool {
|
|
112
|
+
const selectedMode = mode ?? defaultMode;
|
|
113
|
+
|
|
114
|
+
switch (selectedMode) {
|
|
115
|
+
case "worker":
|
|
116
|
+
return new WorkerSandboxPool(poolOptions, sandboxOptions);
|
|
117
|
+
case "vm":
|
|
118
|
+
default:
|
|
119
|
+
return new SandboxPool(poolOptions, sandboxOptions);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get mode characteristics for documentation/selection
|
|
125
|
+
*/
|
|
126
|
+
export function getSandboxModeInfo(mode: SandboxMode): SandboxModeInfo {
|
|
127
|
+
switch (mode) {
|
|
128
|
+
case "worker":
|
|
129
|
+
return {
|
|
130
|
+
name: "Worker Thread",
|
|
131
|
+
isolation: "Separate V8 instance per worker",
|
|
132
|
+
performance: "Higher overhead (thread spawn per execution)",
|
|
133
|
+
security: "Enhanced - isolated memory, hard timeouts",
|
|
134
|
+
requirements: "Node.js worker_threads (built-in)",
|
|
135
|
+
};
|
|
136
|
+
case "vm":
|
|
137
|
+
default:
|
|
138
|
+
return {
|
|
139
|
+
name: "VM Context",
|
|
140
|
+
isolation: "Script isolation within same process",
|
|
141
|
+
performance: "Low overhead (reusable contexts)",
|
|
142
|
+
security: "Standard - script isolation, blocked globals",
|
|
143
|
+
requirements: "Node.js vm module (built-in)",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mysql-mcp - Code Mode Sandbox
|
|
3
|
+
*
|
|
4
|
+
* Sandboxed execution environment using Node.js vm module.
|
|
5
|
+
* Provides code isolation with memory/time limits for LLM-generated code.
|
|
6
|
+
*
|
|
7
|
+
* Note: This uses Node.js vm module which provides script isolation but not
|
|
8
|
+
* true V8 isolate separation. For production environments with untrusted code,
|
|
9
|
+
* consider using isolated-vm or running in a separate process/container.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import vm from "node:vm";
|
|
13
|
+
import { logger } from "../utils/logger.js";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_SANDBOX_OPTIONS,
|
|
16
|
+
DEFAULT_POOL_OPTIONS,
|
|
17
|
+
type SandboxOptions,
|
|
18
|
+
type PoolOptions,
|
|
19
|
+
type SandboxResult,
|
|
20
|
+
type ExecutionMetrics,
|
|
21
|
+
} from "./types.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A sandboxed execution context using Node.js vm module
|
|
25
|
+
*/
|
|
26
|
+
export class CodeModeSandbox {
|
|
27
|
+
private context: vm.Context;
|
|
28
|
+
private readonly options: Required<SandboxOptions>;
|
|
29
|
+
private disposed = false;
|
|
30
|
+
private readonly logBuffer: string[] = [];
|
|
31
|
+
|
|
32
|
+
private constructor(context: vm.Context, options: Required<SandboxOptions>) {
|
|
33
|
+
this.context = context;
|
|
34
|
+
this.options = options;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a new sandbox instance
|
|
39
|
+
*/
|
|
40
|
+
static create(options?: SandboxOptions): CodeModeSandbox {
|
|
41
|
+
const opts = { ...DEFAULT_SANDBOX_OPTIONS, ...options };
|
|
42
|
+
|
|
43
|
+
// Create a shared log buffer that will be used by both sandbox console and instance
|
|
44
|
+
const sharedLogBuffer: string[] = [];
|
|
45
|
+
|
|
46
|
+
// Create a minimal sandbox context
|
|
47
|
+
const sandbox = {
|
|
48
|
+
console: {
|
|
49
|
+
log: (...args: unknown[]) => {
|
|
50
|
+
sharedLogBuffer.push(
|
|
51
|
+
args
|
|
52
|
+
.map((a) =>
|
|
53
|
+
typeof a === "object" && a !== null
|
|
54
|
+
? JSON.stringify(a)
|
|
55
|
+
: String(a),
|
|
56
|
+
)
|
|
57
|
+
.join(" "),
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
warn: (...args: unknown[]) =>
|
|
61
|
+
sharedLogBuffer.push(
|
|
62
|
+
"[WARN] " +
|
|
63
|
+
args
|
|
64
|
+
.map((a) =>
|
|
65
|
+
typeof a === "object" && a !== null
|
|
66
|
+
? JSON.stringify(a)
|
|
67
|
+
: String(a),
|
|
68
|
+
)
|
|
69
|
+
.join(" "),
|
|
70
|
+
),
|
|
71
|
+
error: (...args: unknown[]) =>
|
|
72
|
+
sharedLogBuffer.push(
|
|
73
|
+
"[ERROR] " +
|
|
74
|
+
args
|
|
75
|
+
.map((a) =>
|
|
76
|
+
typeof a === "object" && a !== null
|
|
77
|
+
? JSON.stringify(a)
|
|
78
|
+
: String(a),
|
|
79
|
+
)
|
|
80
|
+
.join(" "),
|
|
81
|
+
),
|
|
82
|
+
info: (...args: unknown[]) =>
|
|
83
|
+
sharedLogBuffer.push(
|
|
84
|
+
"[INFO] " +
|
|
85
|
+
args
|
|
86
|
+
.map((a) =>
|
|
87
|
+
typeof a === "object" && a !== null
|
|
88
|
+
? JSON.stringify(a)
|
|
89
|
+
: String(a),
|
|
90
|
+
)
|
|
91
|
+
.join(" "),
|
|
92
|
+
),
|
|
93
|
+
},
|
|
94
|
+
// No access to Node.js globals
|
|
95
|
+
require: undefined,
|
|
96
|
+
process: undefined,
|
|
97
|
+
global: undefined,
|
|
98
|
+
globalThis: undefined,
|
|
99
|
+
__dirname: undefined,
|
|
100
|
+
__filename: undefined,
|
|
101
|
+
module: undefined,
|
|
102
|
+
exports: undefined,
|
|
103
|
+
// Safe built-ins only
|
|
104
|
+
JSON,
|
|
105
|
+
Math,
|
|
106
|
+
Date,
|
|
107
|
+
Array,
|
|
108
|
+
Object,
|
|
109
|
+
String,
|
|
110
|
+
Number,
|
|
111
|
+
Boolean,
|
|
112
|
+
Map,
|
|
113
|
+
Set,
|
|
114
|
+
Promise,
|
|
115
|
+
Error,
|
|
116
|
+
TypeError,
|
|
117
|
+
RangeError,
|
|
118
|
+
SyntaxError,
|
|
119
|
+
// Async support
|
|
120
|
+
setTimeout: undefined, // Disabled for security
|
|
121
|
+
setInterval: undefined, // Disabled for security
|
|
122
|
+
setImmediate: undefined, // Disabled for security
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const context = vm.createContext(sandbox);
|
|
126
|
+
const instance = new CodeModeSandbox(context, opts);
|
|
127
|
+
|
|
128
|
+
// Use the shared buffer directly - replace instance's buffer with the shared one
|
|
129
|
+
(instance as unknown as { logBuffer: string[] }).logBuffer =
|
|
130
|
+
sharedLogBuffer;
|
|
131
|
+
|
|
132
|
+
return instance;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Execute code in the sandbox
|
|
137
|
+
* @param code - TypeScript/JavaScript code to execute
|
|
138
|
+
* @param apiBindings - Object with mysql.* API methods to expose
|
|
139
|
+
*/
|
|
140
|
+
async execute(
|
|
141
|
+
code: string,
|
|
142
|
+
apiBindings: Record<string, unknown>,
|
|
143
|
+
): Promise<SandboxResult> {
|
|
144
|
+
if (this.disposed) {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
error: "Sandbox has been disposed",
|
|
148
|
+
metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const startTime = performance.now();
|
|
153
|
+
const startMemory = process.memoryUsage().heapUsed;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
// Inject mysql API bindings into the context
|
|
157
|
+
this.context["mysql"] = apiBindings;
|
|
158
|
+
|
|
159
|
+
// Wrap code in async IIFE to support await
|
|
160
|
+
const wrappedCode = `
|
|
161
|
+
(async () => {
|
|
162
|
+
${code}
|
|
163
|
+
})();
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
// Compile and run with timeout
|
|
167
|
+
const script = new vm.Script(wrappedCode, {
|
|
168
|
+
filename: "codemode-script.js",
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const result = await (script.runInContext(this.context, {
|
|
172
|
+
timeout: this.options.timeoutMs,
|
|
173
|
+
breakOnSigint: true,
|
|
174
|
+
}) as Promise<unknown>);
|
|
175
|
+
|
|
176
|
+
const endTime = performance.now();
|
|
177
|
+
const endMemory = process.memoryUsage().heapUsed;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
result,
|
|
182
|
+
metrics: this.calculateMetrics(
|
|
183
|
+
startTime,
|
|
184
|
+
endTime,
|
|
185
|
+
startMemory,
|
|
186
|
+
endMemory,
|
|
187
|
+
),
|
|
188
|
+
};
|
|
189
|
+
} catch (error) {
|
|
190
|
+
const endTime = performance.now();
|
|
191
|
+
const endMemory = process.memoryUsage().heapUsed;
|
|
192
|
+
|
|
193
|
+
const errorMessage =
|
|
194
|
+
error instanceof Error ? error.message : String(error);
|
|
195
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
196
|
+
|
|
197
|
+
// Check for specific error types
|
|
198
|
+
if (errorMessage.includes("Script execution timed out")) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
error: `Execution timeout: exceeded ${String(this.options.timeoutMs)}ms limit`,
|
|
202
|
+
stack,
|
|
203
|
+
metrics: this.calculateMetrics(
|
|
204
|
+
startTime,
|
|
205
|
+
endTime,
|
|
206
|
+
startMemory,
|
|
207
|
+
endMemory,
|
|
208
|
+
),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
error: errorMessage,
|
|
215
|
+
stack,
|
|
216
|
+
metrics: this.calculateMetrics(
|
|
217
|
+
startTime,
|
|
218
|
+
endTime,
|
|
219
|
+
startMemory,
|
|
220
|
+
endMemory,
|
|
221
|
+
),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Calculate execution metrics
|
|
228
|
+
*/
|
|
229
|
+
private calculateMetrics(
|
|
230
|
+
startTime: number,
|
|
231
|
+
endTime: number,
|
|
232
|
+
startMemory: number,
|
|
233
|
+
endMemory: number,
|
|
234
|
+
): ExecutionMetrics {
|
|
235
|
+
return {
|
|
236
|
+
wallTimeMs: Math.round(endTime - startTime),
|
|
237
|
+
cpuTimeMs: Math.round(endTime - startTime), // Approximation
|
|
238
|
+
memoryUsedMb: Math.max(
|
|
239
|
+
0,
|
|
240
|
+
Math.round(((endMemory - startMemory) / (1024 * 1024)) * 100) / 100,
|
|
241
|
+
),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get console output from the sandbox
|
|
247
|
+
*/
|
|
248
|
+
getConsoleOutput(): string[] {
|
|
249
|
+
return [...this.logBuffer];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Clear console output buffer
|
|
254
|
+
*/
|
|
255
|
+
clearConsoleOutput(): void {
|
|
256
|
+
this.logBuffer.length = 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Check if sandbox is healthy
|
|
261
|
+
*/
|
|
262
|
+
isHealthy(): boolean {
|
|
263
|
+
return !this.disposed;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Dispose of the sandbox and release resources
|
|
268
|
+
*/
|
|
269
|
+
dispose(): void {
|
|
270
|
+
if (this.disposed) return;
|
|
271
|
+
|
|
272
|
+
this.disposed = true;
|
|
273
|
+
// vm.Context doesn't need explicit cleanup, but we mark as disposed
|
|
274
|
+
this.logBuffer.length = 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Pool of sandbox instances for reuse
|
|
280
|
+
*/
|
|
281
|
+
export class SandboxPool {
|
|
282
|
+
private readonly options: Required<PoolOptions>;
|
|
283
|
+
private readonly sandboxOptions: Required<SandboxOptions>;
|
|
284
|
+
private readonly available: CodeModeSandbox[] = [];
|
|
285
|
+
private readonly inUse = new Set<CodeModeSandbox>();
|
|
286
|
+
private disposed = false;
|
|
287
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
288
|
+
|
|
289
|
+
constructor(poolOptions?: PoolOptions, sandboxOptions?: SandboxOptions) {
|
|
290
|
+
this.options = { ...DEFAULT_POOL_OPTIONS, ...poolOptions };
|
|
291
|
+
this.sandboxOptions = { ...DEFAULT_SANDBOX_OPTIONS, ...sandboxOptions };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Initialize the pool with minimum instances
|
|
296
|
+
*/
|
|
297
|
+
initialize(): void {
|
|
298
|
+
logger.info(
|
|
299
|
+
`Initializing sandbox pool with ${String(this.options.minInstances)} instances`,
|
|
300
|
+
{
|
|
301
|
+
module: "CODEMODE" as const,
|
|
302
|
+
},
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
for (let i = 0; i < this.options.minInstances; i++) {
|
|
306
|
+
const sandbox = CodeModeSandbox.create(this.sandboxOptions);
|
|
307
|
+
this.available.push(sandbox);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Start cleanup interval
|
|
311
|
+
this.cleanupInterval = setInterval(() => {
|
|
312
|
+
this.cleanup();
|
|
313
|
+
}, this.options.idleTimeoutMs);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Acquire a sandbox from the pool
|
|
318
|
+
*/
|
|
319
|
+
acquire(): CodeModeSandbox {
|
|
320
|
+
if (this.disposed) {
|
|
321
|
+
throw new Error("Pool has been disposed");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Try to get an available sandbox
|
|
325
|
+
while (this.available.length > 0) {
|
|
326
|
+
const sandbox = this.available.pop();
|
|
327
|
+
if (sandbox?.isHealthy()) {
|
|
328
|
+
this.inUse.add(sandbox);
|
|
329
|
+
return sandbox;
|
|
330
|
+
}
|
|
331
|
+
// Sandbox is unhealthy, dispose it
|
|
332
|
+
sandbox?.dispose();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Create a new sandbox if under limit
|
|
336
|
+
const totalCount = this.inUse.size;
|
|
337
|
+
if (totalCount < this.options.maxInstances) {
|
|
338
|
+
const sandbox = CodeModeSandbox.create(this.sandboxOptions);
|
|
339
|
+
this.inUse.add(sandbox);
|
|
340
|
+
return sandbox;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Pool exhausted
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Sandbox pool exhausted (max: ${String(this.options.maxInstances)})`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Release a sandbox back to the pool
|
|
351
|
+
*/
|
|
352
|
+
release(sandbox: CodeModeSandbox): void {
|
|
353
|
+
if (!this.inUse.has(sandbox)) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
this.inUse.delete(sandbox);
|
|
358
|
+
|
|
359
|
+
if (this.disposed) {
|
|
360
|
+
sandbox.dispose();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Return to pool if healthy and under limit
|
|
365
|
+
if (
|
|
366
|
+
sandbox.isHealthy() &&
|
|
367
|
+
this.available.length < this.options.maxInstances
|
|
368
|
+
) {
|
|
369
|
+
sandbox.clearConsoleOutput();
|
|
370
|
+
this.available.push(sandbox);
|
|
371
|
+
} else {
|
|
372
|
+
sandbox.dispose();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Execute code using a pooled sandbox
|
|
378
|
+
*/
|
|
379
|
+
async execute(
|
|
380
|
+
code: string,
|
|
381
|
+
apiBindings: Record<string, unknown>,
|
|
382
|
+
): Promise<SandboxResult> {
|
|
383
|
+
const sandbox = this.acquire();
|
|
384
|
+
try {
|
|
385
|
+
return await sandbox.execute(code, apiBindings);
|
|
386
|
+
} finally {
|
|
387
|
+
this.release(sandbox);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Clean up excess idle sandboxes
|
|
393
|
+
*/
|
|
394
|
+
private cleanup(): void {
|
|
395
|
+
// Remove unhealthy sandboxes
|
|
396
|
+
const healthy: CodeModeSandbox[] = [];
|
|
397
|
+
for (const sandbox of this.available) {
|
|
398
|
+
if (sandbox.isHealthy()) {
|
|
399
|
+
healthy.push(sandbox);
|
|
400
|
+
} else {
|
|
401
|
+
sandbox.dispose();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
this.available.length = 0;
|
|
405
|
+
this.available.push(...healthy);
|
|
406
|
+
|
|
407
|
+
// Trim to minimum
|
|
408
|
+
while (this.available.length > this.options.minInstances) {
|
|
409
|
+
const sandbox = this.available.pop();
|
|
410
|
+
sandbox?.dispose();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Get pool statistics
|
|
416
|
+
*/
|
|
417
|
+
getStats(): { available: number; inUse: number; max: number } {
|
|
418
|
+
return {
|
|
419
|
+
available: this.available.length,
|
|
420
|
+
inUse: this.inUse.size,
|
|
421
|
+
max: this.options.maxInstances,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Dispose of all sandboxes in the pool
|
|
427
|
+
*/
|
|
428
|
+
dispose(): void {
|
|
429
|
+
if (this.disposed) return;
|
|
430
|
+
|
|
431
|
+
this.disposed = true;
|
|
432
|
+
|
|
433
|
+
if (this.cleanupInterval) {
|
|
434
|
+
clearInterval(this.cleanupInterval);
|
|
435
|
+
this.cleanupInterval = null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
for (const sandbox of this.available) {
|
|
439
|
+
sandbox.dispose();
|
|
440
|
+
}
|
|
441
|
+
this.available.length = 0;
|
|
442
|
+
|
|
443
|
+
for (const sandbox of this.inUse) {
|
|
444
|
+
sandbox.dispose();
|
|
445
|
+
}
|
|
446
|
+
this.inUse.clear();
|
|
447
|
+
|
|
448
|
+
logger.info("Sandbox pool disposed", { module: "CODEMODE" as const });
|
|
449
|
+
}
|
|
450
|
+
}
|