@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.
Files changed (216) hide show
  1. package/.github/workflows/codeql.yml +0 -8
  2. package/.github/workflows/docker-publish.yml +11 -10
  3. package/CHANGELOG.md +96 -0
  4. package/CODE_MODE.md +245 -0
  5. package/DOCKER_README.md +71 -254
  6. package/Dockerfile +5 -0
  7. package/README.md +102 -55
  8. package/VERSION +1 -1
  9. package/dist/adapters/mysql/MySQLAdapter.d.ts +4 -0
  10. package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
  11. package/dist/adapters/mysql/MySQLAdapter.js +9 -0
  12. package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
  13. package/dist/adapters/mysql/prompts/index.d.ts +8 -1
  14. package/dist/adapters/mysql/prompts/index.d.ts.map +1 -1
  15. package/dist/adapters/mysql/prompts/index.js +8 -1
  16. package/dist/adapters/mysql/prompts/index.js.map +1 -1
  17. package/dist/adapters/mysql/prompts/routerSetup.d.ts.map +1 -1
  18. package/dist/adapters/mysql/prompts/routerSetup.js +5 -0
  19. package/dist/adapters/mysql/prompts/routerSetup.js.map +1 -1
  20. package/dist/adapters/mysql/resources/capabilities.d.ts.map +1 -1
  21. package/dist/adapters/mysql/resources/capabilities.js +6 -5
  22. package/dist/adapters/mysql/resources/capabilities.js.map +1 -1
  23. package/dist/adapters/mysql/resources/index.d.ts +9 -1
  24. package/dist/adapters/mysql/resources/index.d.ts.map +1 -1
  25. package/dist/adapters/mysql/resources/index.js +9 -1
  26. package/dist/adapters/mysql/resources/index.js.map +1 -1
  27. package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
  28. package/dist/adapters/mysql/tools/admin/backup.js +3 -3
  29. package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
  30. package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
  31. package/dist/adapters/mysql/tools/admin/maintenance.js +5 -5
  32. package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
  33. package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
  34. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +26 -5
  35. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
  36. package/dist/adapters/mysql/tools/codemode/index.d.ts +38 -0
  37. package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -0
  38. package/dist/adapters/mysql/tools/codemode/index.js +203 -0
  39. package/dist/adapters/mysql/tools/codemode/index.js.map +1 -0
  40. package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
  41. package/dist/adapters/mysql/tools/core.js +32 -20
  42. package/dist/adapters/mysql/tools/core.js.map +1 -1
  43. package/dist/adapters/mysql/tools/events.js +18 -6
  44. package/dist/adapters/mysql/tools/events.js.map +1 -1
  45. package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
  46. package/dist/adapters/mysql/tools/json/core.js +5 -5
  47. package/dist/adapters/mysql/tools/json/core.js.map +1 -1
  48. package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
  49. package/dist/adapters/mysql/tools/json/helpers.js +9 -3
  50. package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
  51. package/dist/adapters/mysql/tools/partitioning.d.ts.map +1 -1
  52. package/dist/adapters/mysql/tools/partitioning.js +38 -6
  53. package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
  54. package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
  55. package/dist/adapters/mysql/tools/performance/analysis.js +67 -20
  56. package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
  57. package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
  58. package/dist/adapters/mysql/tools/performance/optimization.js +36 -6
  59. package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
  60. package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
  61. package/dist/adapters/mysql/tools/security/data-protection.js +9 -4
  62. package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
  63. package/dist/adapters/mysql/tools/shell/common.d.ts.map +1 -1
  64. package/dist/adapters/mysql/tools/shell/common.js +28 -2
  65. package/dist/adapters/mysql/tools/shell/common.js.map +1 -1
  66. package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
  67. package/dist/adapters/mysql/tools/shell/restore.js +54 -4
  68. package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
  69. package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
  70. package/dist/adapters/mysql/tools/spatial/operations.js +10 -2
  71. package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
  72. package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
  73. package/dist/adapters/mysql/tools/spatial/setup.js +18 -0
  74. package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
  75. package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
  76. package/dist/adapters/mysql/tools/sysschema/resources.js +5 -0
  77. package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
  78. package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
  79. package/dist/adapters/mysql/tools/text/fulltext.js +6 -4
  80. package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
  81. package/dist/adapters/mysql/tools/text/processing.d.ts.map +1 -1
  82. package/dist/adapters/mysql/tools/text/processing.js +10 -45
  83. package/dist/adapters/mysql/tools/text/processing.js.map +1 -1
  84. package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
  85. package/dist/adapters/mysql/tools/transactions.js +8 -8
  86. package/dist/adapters/mysql/tools/transactions.js.map +1 -1
  87. package/dist/adapters/mysql/types.d.ts +968 -78
  88. package/dist/adapters/mysql/types.d.ts.map +1 -1
  89. package/dist/adapters/mysql/types.js +1084 -78
  90. package/dist/adapters/mysql/types.js.map +1 -1
  91. package/dist/auth/scopes.d.ts.map +1 -1
  92. package/dist/auth/scopes.js +1 -0
  93. package/dist/auth/scopes.js.map +1 -1
  94. package/dist/cli/args.d.ts.map +1 -1
  95. package/dist/cli/args.js +12 -0
  96. package/dist/cli/args.js.map +1 -1
  97. package/dist/codemode/api.d.ts +69 -0
  98. package/dist/codemode/api.d.ts.map +1 -0
  99. package/dist/codemode/api.js +1035 -0
  100. package/dist/codemode/api.js.map +1 -0
  101. package/dist/codemode/index.d.ts +13 -0
  102. package/dist/codemode/index.d.ts.map +1 -0
  103. package/dist/codemode/index.js +17 -0
  104. package/dist/codemode/index.js.map +1 -0
  105. package/dist/codemode/sandbox-factory.d.ts +72 -0
  106. package/dist/codemode/sandbox-factory.d.ts.map +1 -0
  107. package/dist/codemode/sandbox-factory.js +88 -0
  108. package/dist/codemode/sandbox-factory.js.map +1 -0
  109. package/dist/codemode/sandbox.d.ts +96 -0
  110. package/dist/codemode/sandbox.d.ts.map +1 -0
  111. package/dist/codemode/sandbox.js +345 -0
  112. package/dist/codemode/sandbox.js.map +1 -0
  113. package/dist/codemode/security.d.ts +44 -0
  114. package/dist/codemode/security.d.ts.map +1 -0
  115. package/dist/codemode/security.js +149 -0
  116. package/dist/codemode/security.js.map +1 -0
  117. package/dist/codemode/types.d.ts +137 -0
  118. package/dist/codemode/types.d.ts.map +1 -0
  119. package/dist/codemode/types.js +46 -0
  120. package/dist/codemode/types.js.map +1 -0
  121. package/dist/codemode/worker-sandbox.d.ts +82 -0
  122. package/dist/codemode/worker-sandbox.d.ts.map +1 -0
  123. package/dist/codemode/worker-sandbox.js +244 -0
  124. package/dist/codemode/worker-sandbox.js.map +1 -0
  125. package/dist/codemode/worker-script.d.ts +8 -0
  126. package/dist/codemode/worker-script.d.ts.map +1 -0
  127. package/dist/codemode/worker-script.js +113 -0
  128. package/dist/codemode/worker-script.js.map +1 -0
  129. package/dist/constants/ServerInstructions.d.ts +1 -1
  130. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  131. package/dist/constants/ServerInstructions.js +33 -9
  132. package/dist/constants/ServerInstructions.js.map +1 -1
  133. package/dist/filtering/ToolConstants.d.ts +11 -11
  134. package/dist/filtering/ToolConstants.d.ts.map +1 -1
  135. package/dist/filtering/ToolConstants.js +37 -19
  136. package/dist/filtering/ToolConstants.js.map +1 -1
  137. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  138. package/dist/filtering/ToolFilter.js +12 -0
  139. package/dist/filtering/ToolFilter.js.map +1 -1
  140. package/dist/server/McpServer.js +1 -1
  141. package/dist/server/McpServer.js.map +1 -1
  142. package/dist/types/modules/server.d.ts +2 -0
  143. package/dist/types/modules/server.d.ts.map +1 -1
  144. package/dist/types/modules/tools.d.ts +1 -1
  145. package/dist/types/modules/tools.d.ts.map +1 -1
  146. package/dist/utils/logger.d.ts +1 -1
  147. package/dist/utils/logger.d.ts.map +1 -1
  148. package/dist/utils/logger.js.map +1 -1
  149. package/package.json +12 -7
  150. package/releases/v2.2.0-release-notes.md +18 -18
  151. package/releases/v2.3.0-release-notes.md +191 -0
  152. package/releases/v2.3.1-release-notes.md +34 -0
  153. package/src/__tests__/perf.test.ts +12 -12
  154. package/src/adapters/mysql/MySQLAdapter.ts +10 -0
  155. package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
  156. package/src/adapters/mysql/prompts/index.ts +8 -1
  157. package/src/adapters/mysql/prompts/routerSetup.ts +5 -0
  158. package/src/adapters/mysql/resources/__tests__/capabilities.test.ts +50 -1
  159. package/src/adapters/mysql/resources/capabilities.ts +6 -4
  160. package/src/adapters/mysql/resources/index.ts +9 -1
  161. package/src/adapters/mysql/tools/__tests__/core.test.ts +68 -0
  162. package/src/adapters/mysql/tools/__tests__/events.test.ts +56 -2
  163. package/src/adapters/mysql/tools/__tests__/json_core.test.ts +1 -1
  164. package/src/adapters/mysql/tools/__tests__/json_helpers.test.ts +46 -4
  165. package/src/adapters/mysql/tools/__tests__/replication.test.ts +144 -42
  166. package/src/adapters/mysql/tools/__tests__/security.test.ts +39 -0
  167. package/src/adapters/mysql/tools/__tests__/spatial.test.ts +39 -7
  168. package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +35 -3
  169. package/src/adapters/mysql/tools/__tests__/transactions.test.ts +3 -5
  170. package/src/adapters/mysql/tools/admin/backup.ts +8 -3
  171. package/src/adapters/mysql/tools/admin/maintenance.ts +8 -4
  172. package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +35 -0
  173. package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +26 -5
  174. package/src/adapters/mysql/tools/codemode/index.ts +249 -0
  175. package/src/adapters/mysql/tools/core.ts +44 -27
  176. package/src/adapters/mysql/tools/events.ts +23 -7
  177. package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +59 -14
  178. package/src/adapters/mysql/tools/json/core.ts +8 -4
  179. package/src/adapters/mysql/tools/json/helpers.ts +13 -3
  180. package/src/adapters/mysql/tools/partitioning.ts +53 -6
  181. package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +227 -4
  182. package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +35 -0
  183. package/src/adapters/mysql/tools/performance/analysis.ts +75 -21
  184. package/src/adapters/mysql/tools/performance/optimization.ts +44 -6
  185. package/src/adapters/mysql/tools/security/data-protection.ts +10 -4
  186. package/src/adapters/mysql/tools/shell/__tests__/common.test.ts +46 -0
  187. package/src/adapters/mysql/tools/shell/__tests__/restore.test.ts +28 -1
  188. package/src/adapters/mysql/tools/shell/common.ts +34 -2
  189. package/src/adapters/mysql/tools/shell/restore.ts +70 -7
  190. package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +29 -0
  191. package/src/adapters/mysql/tools/spatial/operations.ts +13 -2
  192. package/src/adapters/mysql/tools/spatial/setup.ts +23 -0
  193. package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +21 -0
  194. package/src/adapters/mysql/tools/sysschema/resources.ts +5 -0
  195. package/src/adapters/mysql/tools/text/fulltext.ts +13 -5
  196. package/src/adapters/mysql/tools/text/processing.ts +20 -49
  197. package/src/adapters/mysql/tools/transactions.ts +11 -7
  198. package/src/adapters/mysql/types.ts +1241 -87
  199. package/src/auth/scopes.ts +1 -0
  200. package/src/cli/args.ts +14 -0
  201. package/src/codemode/api.ts +1224 -0
  202. package/src/codemode/index.ts +51 -0
  203. package/src/codemode/sandbox-factory.ts +146 -0
  204. package/src/codemode/sandbox.ts +450 -0
  205. package/src/codemode/security.ts +188 -0
  206. package/src/codemode/types.ts +194 -0
  207. package/src/codemode/worker-sandbox.ts +326 -0
  208. package/src/codemode/worker-script.ts +144 -0
  209. package/src/constants/ServerInstructions.ts +33 -9
  210. package/src/filtering/ToolConstants.ts +37 -19
  211. package/src/filtering/ToolFilter.ts +15 -0
  212. package/src/filtering/__tests__/ToolFilter.test.ts +65 -38
  213. package/src/server/McpServer.ts +1 -1
  214. package/src/types/modules/server.ts +3 -0
  215. package/src/types/modules/tools.ts +2 -1
  216. package/src/utils/logger.ts +2 -1
@@ -0,0 +1,188 @@
1
+ /**
2
+ * mysql-mcp - Code Mode Security
3
+ *
4
+ * Input validation, rate limiting, and audit logging for code execution.
5
+ */
6
+
7
+ import { logger } from "../utils/logger.js";
8
+ import {
9
+ DEFAULT_SECURITY_CONFIG,
10
+ type SecurityConfig,
11
+ type ValidationResult,
12
+ type ExecutionRecord,
13
+ type SandboxResult,
14
+ } from "./types.js";
15
+
16
+ /**
17
+ * Security manager for Code Mode executions
18
+ */
19
+ export class CodeModeSecurityManager {
20
+ private readonly config: SecurityConfig;
21
+ private readonly rateLimitMap = new Map<
22
+ string,
23
+ { count: number; resetTime: number }
24
+ >();
25
+
26
+ constructor(config?: Partial<SecurityConfig>) {
27
+ this.config = { ...DEFAULT_SECURITY_CONFIG, ...config };
28
+ }
29
+
30
+ /**
31
+ * Validate code before execution
32
+ */
33
+ validateCode(code: string): ValidationResult {
34
+ const errors: string[] = [];
35
+
36
+ // Check code length
37
+ if (!code || typeof code !== "string") {
38
+ errors.push("Code must be a non-empty string");
39
+ return { valid: false, errors };
40
+ }
41
+
42
+ if (code.length > this.config.maxCodeLength) {
43
+ errors.push(
44
+ `Code exceeds maximum length of ${String(this.config.maxCodeLength)} bytes`,
45
+ );
46
+ return { valid: false, errors };
47
+ }
48
+
49
+ // Check for blocked patterns
50
+ for (const pattern of this.config.blockedPatterns) {
51
+ if (pattern.test(code)) {
52
+ errors.push(`Blocked pattern detected: ${pattern.source}`);
53
+ }
54
+ }
55
+
56
+ return {
57
+ valid: errors.length === 0,
58
+ errors,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Check rate limit for a client
64
+ * @returns true if within limits, false if rate limited
65
+ */
66
+ checkRateLimit(clientId: string): boolean {
67
+ const now = Date.now();
68
+ const windowMs = 60000; // 1 minute window
69
+
70
+ const existing = this.rateLimitMap.get(clientId);
71
+
72
+ if (!existing || now >= existing.resetTime) {
73
+ // Start new window
74
+ this.rateLimitMap.set(clientId, {
75
+ count: 1,
76
+ resetTime: now + windowMs,
77
+ });
78
+ return true;
79
+ }
80
+
81
+ if (existing.count >= this.config.maxExecutionsPerMinute) {
82
+ return false;
83
+ }
84
+
85
+ existing.count++;
86
+ return true;
87
+ }
88
+
89
+ /**
90
+ * Get remaining rate limit for a client
91
+ */
92
+ getRateLimitRemaining(clientId: string): number {
93
+ const existing = this.rateLimitMap.get(clientId);
94
+ if (!existing || Date.now() >= existing.resetTime) {
95
+ return this.config.maxExecutionsPerMinute;
96
+ }
97
+ return Math.max(0, this.config.maxExecutionsPerMinute - existing.count);
98
+ }
99
+
100
+ /**
101
+ * Sanitize and truncate result if too large
102
+ */
103
+ sanitizeResult(result: unknown): unknown {
104
+ try {
105
+ const serialized = JSON.stringify(result);
106
+ if (serialized.length > this.config.maxResultSize) {
107
+ return {
108
+ _truncated: true,
109
+ _originalSize: serialized.length,
110
+ _maxSize: this.config.maxResultSize,
111
+ preview: serialized.substring(0, 1000) + "...",
112
+ };
113
+ }
114
+ return result;
115
+ } catch {
116
+ return {
117
+ _error: "Result could not be serialized",
118
+ _type: typeof result,
119
+ };
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Log execution for audit purposes
125
+ */
126
+ auditLog(execution: ExecutionRecord): void {
127
+ const { id, clientId, codePreview, result, readonly } = execution;
128
+
129
+ const logContext = {
130
+ module: "CODEMODE" as const,
131
+ operation: "execute",
132
+ entityId: id,
133
+ clientId: clientId ?? "anonymous",
134
+ readonly,
135
+ success: result.success,
136
+ wallTimeMs: result.metrics.wallTimeMs,
137
+ memoryUsedMb: result.metrics.memoryUsedMb,
138
+ };
139
+
140
+ if (result.success) {
141
+ logger.info(
142
+ `Code execution completed: ${codePreview.substring(0, 50)}...`,
143
+ logContext,
144
+ );
145
+ } else {
146
+ const errorContext = {
147
+ ...logContext,
148
+ ...(result.error !== undefined ? { error: result.error } : {}),
149
+ ...(result.stack !== undefined ? { stack: result.stack } : {}),
150
+ };
151
+ logger.warning(
152
+ `Code execution failed: ${result.error ?? "unknown error"}`,
153
+ errorContext,
154
+ );
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Create execution record for audit
160
+ */
161
+ createExecutionRecord(
162
+ code: string,
163
+ result: SandboxResult,
164
+ readonly: boolean,
165
+ clientId?: string,
166
+ ): ExecutionRecord {
167
+ return {
168
+ id: crypto.randomUUID(),
169
+ clientId,
170
+ timestamp: new Date(),
171
+ codePreview: code.length > 200 ? code.substring(0, 200) + "..." : code,
172
+ result,
173
+ readonly,
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Clean up old rate limit entries
179
+ */
180
+ cleanupRateLimits(): void {
181
+ const now = Date.now();
182
+ for (const [clientId, entry] of this.rateLimitMap) {
183
+ if (now >= entry.resetTime) {
184
+ this.rateLimitMap.delete(clientId);
185
+ }
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * mysql-mcp - Code Mode Types
3
+ *
4
+ * Type definitions for the sandboxed code execution environment.
5
+ */
6
+
7
+ import type { ToolGroup } from "../types/index.js";
8
+
9
+ // =============================================================================
10
+ // Sandbox Configuration
11
+ // =============================================================================
12
+
13
+ /**
14
+ * Options for sandbox execution
15
+ */
16
+ export interface SandboxOptions {
17
+ /** Memory limit in MB (default: 128) */
18
+ memoryLimitMb?: number;
19
+ /** Execution timeout in milliseconds (default: 30000) */
20
+ timeoutMs?: number;
21
+ /** CPU time limit in milliseconds (default: 10000) */
22
+ cpuLimitMs?: number;
23
+ }
24
+
25
+ /**
26
+ * Options for the sandbox pool
27
+ */
28
+ export interface PoolOptions {
29
+ /** Minimum instances to keep warm (default: 2) */
30
+ minInstances?: number;
31
+ /** Maximum instances in pool (default: 10) */
32
+ maxInstances?: number;
33
+ /** Idle timeout before disposing instance (default: 60000ms) */
34
+ idleTimeoutMs?: number;
35
+ }
36
+
37
+ /**
38
+ * Default sandbox configuration
39
+ */
40
+ export const DEFAULT_SANDBOX_OPTIONS: Required<SandboxOptions> = {
41
+ memoryLimitMb: 128,
42
+ timeoutMs: 30000,
43
+ cpuLimitMs: 10000,
44
+ };
45
+
46
+ /**
47
+ * Default pool configuration
48
+ */
49
+ export const DEFAULT_POOL_OPTIONS: Required<PoolOptions> = {
50
+ minInstances: 2,
51
+ maxInstances: 10,
52
+ idleTimeoutMs: 60000,
53
+ };
54
+
55
+ // =============================================================================
56
+ // Execution Results
57
+ // =============================================================================
58
+
59
+ /**
60
+ * Metrics collected during sandbox execution
61
+ */
62
+ export interface ExecutionMetrics {
63
+ /** Wall clock time in milliseconds */
64
+ wallTimeMs: number;
65
+ /** CPU time consumed in milliseconds */
66
+ cpuTimeMs: number;
67
+ /** Peak memory usage in MB */
68
+ memoryUsedMb: number;
69
+ }
70
+
71
+ /**
72
+ * Result of sandbox code execution
73
+ */
74
+ export interface SandboxResult {
75
+ /** Whether execution completed successfully */
76
+ success: boolean;
77
+ /** Return value from the code (if successful) */
78
+ result?: unknown;
79
+ /** Error message (if failed) */
80
+ error?: string | undefined;
81
+ /** Stack trace (if failed) */
82
+ stack?: string | undefined;
83
+ /** Execution metrics */
84
+ metrics: ExecutionMetrics;
85
+ }
86
+
87
+ // =============================================================================
88
+ // Security Configuration
89
+ // =============================================================================
90
+
91
+ /**
92
+ * Security configuration for code validation
93
+ */
94
+ export interface SecurityConfig {
95
+ /** Maximum code length in bytes (default: 50KB) */
96
+ maxCodeLength: number;
97
+ /** Maximum executions per minute per client (default: 60) */
98
+ maxExecutionsPerMinute: number;
99
+ /** Maximum result size in bytes (default: 10MB) */
100
+ maxResultSize: number;
101
+ /** Patterns to block in code */
102
+ blockedPatterns: RegExp[];
103
+ }
104
+
105
+ /**
106
+ * Default security configuration
107
+ */
108
+ export const DEFAULT_SECURITY_CONFIG: SecurityConfig = {
109
+ maxCodeLength: 50 * 1024, // 50KB
110
+ maxExecutionsPerMinute: 60,
111
+ maxResultSize: 10 * 1024 * 1024, // 10MB
112
+ blockedPatterns: [
113
+ /\brequire\s*\(/, // No require()
114
+ /\bimport\s*\(/, // No dynamic import()
115
+ /\bprocess\./, // No process access
116
+ /\bglobal\./, // No global access
117
+ /\bglobalThis\./, // No globalThis access
118
+ /\beval\s*\(/, // No eval()
119
+ /\bFunction\s*\(/, // No Function constructor
120
+ /\b__proto__\b/, // No prototype pollution
121
+ /\bconstructor\.constructor/, // No constructor chaining
122
+ /\bchild_process/, // No child processes
123
+ /\bfs\./, // No filesystem
124
+ /\bnet\./, // No networking
125
+ /\bhttp\./, // No HTTP
126
+ /\bhttps\./, // No HTTPS
127
+ ],
128
+ };
129
+
130
+ /**
131
+ * Validation result from security checks
132
+ */
133
+ export interface ValidationResult {
134
+ /** Whether the code passed validation */
135
+ valid: boolean;
136
+ /** Validation errors (if any) */
137
+ errors: string[];
138
+ }
139
+
140
+ /**
141
+ * Execution record for audit logging
142
+ */
143
+ export interface ExecutionRecord {
144
+ /** Unique execution ID */
145
+ id: string;
146
+ /** Client identifier (for rate limiting) */
147
+ clientId?: string | undefined;
148
+ /** Timestamp of execution start */
149
+ timestamp: Date;
150
+ /** Code that was executed (truncated for logging) */
151
+ codePreview: string;
152
+ /** Execution result */
153
+ result: SandboxResult;
154
+ /** Whether code was in readonly mode */
155
+ readonly: boolean;
156
+ }
157
+
158
+ // =============================================================================
159
+ // API Types
160
+ // =============================================================================
161
+
162
+ /**
163
+ * Tool group API interface - each group exposes its tools as methods
164
+ */
165
+ export interface GroupApi {
166
+ /** Tool group name */
167
+ readonly groupName: ToolGroup;
168
+ }
169
+
170
+ /**
171
+ * Options passed to mysql_execute_code tool
172
+ */
173
+ export interface ExecuteCodeOptions {
174
+ /** TypeScript code to execute */
175
+ code: string;
176
+ /** Timeout in milliseconds (max 30000) */
177
+ timeout?: number;
178
+ /** Restrict to read-only operations */
179
+ readonly?: boolean;
180
+ }
181
+
182
+ /**
183
+ * Result returned by mysql_execute_code tool
184
+ */
185
+ export interface ExecuteCodeResult {
186
+ /** Whether execution succeeded */
187
+ success: boolean;
188
+ /** Return value from the code */
189
+ result?: unknown;
190
+ /** Error message (if failed) */
191
+ error?: string;
192
+ /** Execution metrics */
193
+ metrics: ExecutionMetrics;
194
+ }
@@ -0,0 +1,326 @@
1
+ /**
2
+ * mysql-mcp - Code Mode Worker Sandbox
3
+ *
4
+ * Enhanced sandboxed execution using worker_threads for process-level isolation.
5
+ * Provides stronger isolation than vm module by running code in a separate thread
6
+ * with isolated memory space.
7
+ *
8
+ * Features:
9
+ * - Separate V8 instance per worker thread
10
+ * - Hard timeout enforcement (worker termination)
11
+ * - Isolated memory space
12
+ * - Clean process state on each execution
13
+ */
14
+
15
+ import { Worker } from "node:worker_threads";
16
+ import { fileURLToPath } from "node:url";
17
+ import { dirname, join } from "node:path";
18
+ import { logger } from "../utils/logger.js";
19
+ import {
20
+ DEFAULT_SANDBOX_OPTIONS,
21
+ DEFAULT_POOL_OPTIONS,
22
+ type SandboxOptions,
23
+ type PoolOptions,
24
+ type SandboxResult,
25
+ type ExecutionMetrics,
26
+ } from "./types.js";
27
+
28
+ // Get directory for worker script
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+ const WORKER_SCRIPT_PATH = join(__dirname, "worker-script.js");
31
+
32
+ /**
33
+ * A sandboxed execution context using worker_threads
34
+ * Provides stronger isolation than vm module with separate V8 instance
35
+ */
36
+ export class WorkerSandbox {
37
+ private readonly options: Required<SandboxOptions>;
38
+ private disposed = false;
39
+
40
+ private constructor(options: Required<SandboxOptions>) {
41
+ this.options = options;
42
+ }
43
+
44
+ /**
45
+ * Create a new worker sandbox instance
46
+ */
47
+ static create(options?: SandboxOptions): WorkerSandbox {
48
+ const opts = { ...DEFAULT_SANDBOX_OPTIONS, ...options };
49
+ return new WorkerSandbox(opts);
50
+ }
51
+
52
+ /**
53
+ * Execute code in a worker thread
54
+ * Each execution spawns a fresh worker for maximum isolation
55
+ */
56
+ async execute(
57
+ code: string,
58
+ apiBindings: Record<string, unknown>,
59
+ ): Promise<SandboxResult> {
60
+ if (this.disposed) {
61
+ return {
62
+ success: false,
63
+ error: "Sandbox has been disposed",
64
+ metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 },
65
+ };
66
+ }
67
+
68
+ const startTime = performance.now();
69
+ const startMemory = process.memoryUsage().heapUsed;
70
+
71
+ return new Promise((resolve) => {
72
+ let worker: Worker | null = null;
73
+ let timeoutId: NodeJS.Timeout | null = null;
74
+ let resolved = false;
75
+
76
+ const cleanup = (): void => {
77
+ if (timeoutId) {
78
+ clearTimeout(timeoutId);
79
+ timeoutId = null;
80
+ }
81
+ if (worker) {
82
+ worker.terminate().catch((): void => {
83
+ /* intentionally empty */
84
+ });
85
+ worker = null;
86
+ }
87
+ };
88
+
89
+ const respond = (result: SandboxResult): void => {
90
+ if (resolved) return;
91
+ resolved = true;
92
+ cleanup();
93
+ resolve(result);
94
+ };
95
+
96
+ try {
97
+ worker = new Worker(WORKER_SCRIPT_PATH, {
98
+ workerData: {
99
+ code,
100
+ apiBindings: this.serializeBindings(apiBindings),
101
+ timeout: this.options.timeoutMs,
102
+ },
103
+ });
104
+
105
+ // Set hard timeout (will kill worker)
106
+ timeoutId = setTimeout(() => {
107
+ const endTime = performance.now();
108
+ const endMemory = process.memoryUsage().heapUsed;
109
+ respond({
110
+ success: false,
111
+ error: `Execution timeout: exceeded ${String(this.options.timeoutMs)}ms limit`,
112
+ metrics: this.calculateMetrics(
113
+ startTime,
114
+ endTime,
115
+ startMemory,
116
+ endMemory,
117
+ ),
118
+ });
119
+ }, this.options.timeoutMs + 1000); // Extra buffer for cleanup
120
+
121
+ worker.on(
122
+ "message",
123
+ (result: {
124
+ success: boolean;
125
+ result?: unknown;
126
+ error?: string;
127
+ stack?: string;
128
+ }) => {
129
+ const endTime = performance.now();
130
+ const endMemory = process.memoryUsage().heapUsed;
131
+ respond({
132
+ success: result.success,
133
+ result: result.result,
134
+ error: result.error,
135
+ stack: result.stack,
136
+ metrics: this.calculateMetrics(
137
+ startTime,
138
+ endTime,
139
+ startMemory,
140
+ endMemory,
141
+ ),
142
+ });
143
+ },
144
+ );
145
+
146
+ worker.on("error", (error: Error) => {
147
+ const endTime = performance.now();
148
+ const endMemory = process.memoryUsage().heapUsed;
149
+ respond({
150
+ success: false,
151
+ error: error.message,
152
+ stack: error.stack,
153
+ metrics: this.calculateMetrics(
154
+ startTime,
155
+ endTime,
156
+ startMemory,
157
+ endMemory,
158
+ ),
159
+ });
160
+ });
161
+
162
+ worker.on("exit", (exitCode: number) => {
163
+ if (!resolved && exitCode !== 0) {
164
+ const endTime = performance.now();
165
+ const endMemory = process.memoryUsage().heapUsed;
166
+ respond({
167
+ success: false,
168
+ error: `Worker exited with code ${String(exitCode)}`,
169
+ metrics: this.calculateMetrics(
170
+ startTime,
171
+ endTime,
172
+ startMemory,
173
+ endMemory,
174
+ ),
175
+ });
176
+ }
177
+ });
178
+ } catch (error) {
179
+ const endTime = performance.now();
180
+ const endMemory = process.memoryUsage().heapUsed;
181
+ respond({
182
+ success: false,
183
+ error: error instanceof Error ? error.message : String(error),
184
+ stack: error instanceof Error ? error.stack : undefined,
185
+ metrics: this.calculateMetrics(
186
+ startTime,
187
+ endTime,
188
+ startMemory,
189
+ endMemory,
190
+ ),
191
+ });
192
+ }
193
+ });
194
+ }
195
+
196
+ /**
197
+ * Serialize API bindings for worker transfer
198
+ * We can't transfer functions directly, so we send method names
199
+ */
200
+ private serializeBindings(
201
+ bindings: Record<string, unknown>,
202
+ ): Record<string, string[]> {
203
+ const serialized: Record<string, string[]> = {};
204
+ for (const [group, methods] of Object.entries(bindings)) {
205
+ if (typeof methods === "object" && methods !== null) {
206
+ serialized[group] = Object.keys(methods);
207
+ }
208
+ }
209
+ return serialized;
210
+ }
211
+
212
+ /**
213
+ * Calculate execution metrics
214
+ */
215
+ private calculateMetrics(
216
+ startTime: number,
217
+ endTime: number,
218
+ startMemory: number,
219
+ endMemory: number,
220
+ ): ExecutionMetrics {
221
+ return {
222
+ wallTimeMs: Math.round(endTime - startTime),
223
+ cpuTimeMs: Math.round(endTime - startTime), // Approximation
224
+ memoryUsedMb: Math.max(
225
+ 0,
226
+ Math.round(((endMemory - startMemory) / (1024 * 1024)) * 100) / 100,
227
+ ),
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Check if sandbox is healthy
233
+ */
234
+ isHealthy(): boolean {
235
+ return !this.disposed;
236
+ }
237
+
238
+ /**
239
+ * Dispose of the sandbox
240
+ */
241
+ dispose(): void {
242
+ this.disposed = true;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Pool of worker sandboxes
248
+ * Unlike VM pool, worker sandboxes are created fresh for each execution
249
+ * so this pool is simpler (mainly for statistics and control)
250
+ */
251
+ export class WorkerSandboxPool {
252
+ private readonly options: Required<PoolOptions>;
253
+ private readonly sandboxOptions: Required<SandboxOptions>;
254
+ private activeCount = 0;
255
+ private disposed = false;
256
+
257
+ constructor(poolOptions?: PoolOptions, sandboxOptions?: SandboxOptions) {
258
+ this.options = { ...DEFAULT_POOL_OPTIONS, ...poolOptions };
259
+ this.sandboxOptions = { ...DEFAULT_SANDBOX_OPTIONS, ...sandboxOptions };
260
+ }
261
+
262
+ /**
263
+ * Initialize the pool
264
+ */
265
+ initialize(): void {
266
+ logger.info(
267
+ `Worker sandbox pool initialized (max: ${String(this.options.maxInstances)} concurrent)`,
268
+ {
269
+ module: "CODEMODE" as const,
270
+ },
271
+ );
272
+ }
273
+
274
+ /**
275
+ * Execute code using a worker sandbox
276
+ */
277
+ async execute(
278
+ code: string,
279
+ apiBindings: Record<string, unknown>,
280
+ ): Promise<SandboxResult> {
281
+ if (this.disposed) {
282
+ return {
283
+ success: false,
284
+ error: "Pool has been disposed",
285
+ metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 },
286
+ };
287
+ }
288
+
289
+ if (this.activeCount >= this.options.maxInstances) {
290
+ return {
291
+ success: false,
292
+ error: `Worker pool exhausted (max: ${String(this.options.maxInstances)} concurrent)`,
293
+ metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 },
294
+ };
295
+ }
296
+
297
+ this.activeCount++;
298
+ try {
299
+ const sandbox = WorkerSandbox.create(this.sandboxOptions);
300
+ return await sandbox.execute(code, apiBindings);
301
+ } finally {
302
+ this.activeCount--;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Get pool statistics
308
+ */
309
+ getStats(): { available: number; inUse: number; max: number } {
310
+ return {
311
+ available: this.options.maxInstances - this.activeCount,
312
+ inUse: this.activeCount,
313
+ max: this.options.maxInstances,
314
+ };
315
+ }
316
+
317
+ /**
318
+ * Dispose of the pool
319
+ */
320
+ dispose(): void {
321
+ this.disposed = true;
322
+ logger.info("Worker sandbox pool disposed", {
323
+ module: "CODEMODE" as const,
324
+ });
325
+ }
326
+ }