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.
Files changed (376) hide show
  1. package/README.md +40 -353
  2. package/dist/do/logger.d.ts +50 -0
  3. package/dist/do/logger.d.ts.map +1 -0
  4. package/dist/do/logger.js +122 -0
  5. package/dist/do/logger.js.map +1 -0
  6. package/dist/{durable-object → do}/schema.d.ts +3 -3
  7. package/dist/do/schema.d.ts.map +1 -0
  8. package/dist/{durable-object → do}/schema.js +4 -3
  9. package/dist/do/schema.js.map +1 -0
  10. package/dist/do/types.d.ts +267 -0
  11. package/dist/do/types.d.ts.map +1 -0
  12. package/dist/do/types.js +62 -0
  13. package/dist/do/types.js.map +1 -0
  14. package/dist/index.d.ts +15 -469
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +39 -481
  17. package/dist/index.js.map +1 -1
  18. package/dist/mcp/auth.d.ts +77 -0
  19. package/dist/mcp/auth.d.ts.map +1 -0
  20. package/dist/mcp/auth.js +278 -0
  21. package/dist/mcp/auth.js.map +1 -0
  22. package/dist/mcp/index.d.ts +13 -0
  23. package/dist/mcp/index.d.ts.map +1 -0
  24. package/dist/mcp/index.js +19 -0
  25. package/dist/mcp/index.js.map +1 -0
  26. package/dist/mcp/server.d.ts +200 -0
  27. package/dist/mcp/server.d.ts.map +1 -0
  28. package/dist/mcp/server.js +275 -0
  29. package/dist/mcp/server.js.map +1 -0
  30. package/dist/mcp/tool-registry.d.ts +47 -0
  31. package/dist/mcp/tool-registry.d.ts.map +1 -0
  32. package/dist/mcp/tool-registry.js +284 -0
  33. package/dist/mcp/tool-registry.js.map +1 -0
  34. package/dist/mcp/tools.d.ts +103 -515
  35. package/dist/mcp/tools.d.ts.map +1 -1
  36. package/dist/mcp/tools.js +676 -3087
  37. package/dist/mcp/tools.js.map +1 -1
  38. package/dist/mcp/types.d.ts +124 -0
  39. package/dist/mcp/types.d.ts.map +1 -0
  40. package/dist/mcp/types.js +9 -0
  41. package/dist/mcp/types.js.map +1 -0
  42. package/package.json +19 -21
  43. package/dist/cli/commands/add.d.ts +0 -176
  44. package/dist/cli/commands/add.d.ts.map +0 -1
  45. package/dist/cli/commands/add.js +0 -979
  46. package/dist/cli/commands/add.js.map +0 -1
  47. package/dist/cli/commands/blame.d.ts +0 -259
  48. package/dist/cli/commands/blame.d.ts.map +0 -1
  49. package/dist/cli/commands/blame.js +0 -609
  50. package/dist/cli/commands/blame.js.map +0 -1
  51. package/dist/cli/commands/branch.d.ts +0 -249
  52. package/dist/cli/commands/branch.d.ts.map +0 -1
  53. package/dist/cli/commands/branch.js +0 -693
  54. package/dist/cli/commands/branch.js.map +0 -1
  55. package/dist/cli/commands/checkout.d.ts +0 -73
  56. package/dist/cli/commands/checkout.d.ts.map +0 -1
  57. package/dist/cli/commands/checkout.js +0 -725
  58. package/dist/cli/commands/checkout.js.map +0 -1
  59. package/dist/cli/commands/commit.d.ts +0 -182
  60. package/dist/cli/commands/commit.d.ts.map +0 -1
  61. package/dist/cli/commands/commit.js +0 -457
  62. package/dist/cli/commands/commit.js.map +0 -1
  63. package/dist/cli/commands/diff.d.ts +0 -464
  64. package/dist/cli/commands/diff.d.ts.map +0 -1
  65. package/dist/cli/commands/diff.js +0 -959
  66. package/dist/cli/commands/diff.js.map +0 -1
  67. package/dist/cli/commands/log.d.ts +0 -239
  68. package/dist/cli/commands/log.d.ts.map +0 -1
  69. package/dist/cli/commands/log.js +0 -535
  70. package/dist/cli/commands/log.js.map +0 -1
  71. package/dist/cli/commands/merge.d.ts +0 -106
  72. package/dist/cli/commands/merge.d.ts.map +0 -1
  73. package/dist/cli/commands/merge.js +0 -852
  74. package/dist/cli/commands/merge.js.map +0 -1
  75. package/dist/cli/commands/review.d.ts +0 -457
  76. package/dist/cli/commands/review.d.ts.map +0 -1
  77. package/dist/cli/commands/review.js +0 -558
  78. package/dist/cli/commands/review.js.map +0 -1
  79. package/dist/cli/commands/stash.d.ts +0 -157
  80. package/dist/cli/commands/stash.d.ts.map +0 -1
  81. package/dist/cli/commands/stash.js +0 -655
  82. package/dist/cli/commands/stash.js.map +0 -1
  83. package/dist/cli/commands/status.d.ts +0 -269
  84. package/dist/cli/commands/status.d.ts.map +0 -1
  85. package/dist/cli/commands/status.js +0 -492
  86. package/dist/cli/commands/status.js.map +0 -1
  87. package/dist/cli/commands/web.d.ts +0 -199
  88. package/dist/cli/commands/web.d.ts.map +0 -1
  89. package/dist/cli/commands/web.js +0 -697
  90. package/dist/cli/commands/web.js.map +0 -1
  91. package/dist/cli/fs-adapter.d.ts +0 -656
  92. package/dist/cli/fs-adapter.d.ts.map +0 -1
  93. package/dist/cli/fs-adapter.js +0 -1177
  94. package/dist/cli/fs-adapter.js.map +0 -1
  95. package/dist/cli/fsx-cli-adapter.d.ts +0 -359
  96. package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
  97. package/dist/cli/fsx-cli-adapter.js +0 -619
  98. package/dist/cli/fsx-cli-adapter.js.map +0 -1
  99. package/dist/cli/index.d.ts +0 -387
  100. package/dist/cli/index.d.ts.map +0 -1
  101. package/dist/cli/index.js +0 -579
  102. package/dist/cli/index.js.map +0 -1
  103. package/dist/cli/ui/components/DiffView.d.ts +0 -12
  104. package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
  105. package/dist/cli/ui/components/DiffView.js +0 -11
  106. package/dist/cli/ui/components/DiffView.js.map +0 -1
  107. package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -10
  108. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
  109. package/dist/cli/ui/components/ErrorDisplay.js +0 -11
  110. package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
  111. package/dist/cli/ui/components/FuzzySearch.d.ts +0 -15
  112. package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
  113. package/dist/cli/ui/components/FuzzySearch.js +0 -12
  114. package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
  115. package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -10
  116. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
  117. package/dist/cli/ui/components/LoadingSpinner.js +0 -10
  118. package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
  119. package/dist/cli/ui/components/NavigationList.d.ts +0 -14
  120. package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
  121. package/dist/cli/ui/components/NavigationList.js +0 -11
  122. package/dist/cli/ui/components/NavigationList.js.map +0 -1
  123. package/dist/cli/ui/components/ScrollableContent.d.ts +0 -13
  124. package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
  125. package/dist/cli/ui/components/ScrollableContent.js +0 -11
  126. package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
  127. package/dist/cli/ui/components/index.d.ts +0 -7
  128. package/dist/cli/ui/components/index.d.ts.map +0 -1
  129. package/dist/cli/ui/components/index.js +0 -9
  130. package/dist/cli/ui/components/index.js.map +0 -1
  131. package/dist/cli/ui/terminal-ui.d.ts +0 -85
  132. package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
  133. package/dist/cli/ui/terminal-ui.js +0 -121
  134. package/dist/cli/ui/terminal-ui.js.map +0 -1
  135. package/dist/do/BashModule.d.ts +0 -871
  136. package/dist/do/BashModule.d.ts.map +0 -1
  137. package/dist/do/BashModule.js +0 -1143
  138. package/dist/do/BashModule.js.map +0 -1
  139. package/dist/do/FsModule.d.ts +0 -612
  140. package/dist/do/FsModule.d.ts.map +0 -1
  141. package/dist/do/FsModule.js +0 -1120
  142. package/dist/do/FsModule.js.map +0 -1
  143. package/dist/do/GitModule.d.ts +0 -635
  144. package/dist/do/GitModule.d.ts.map +0 -1
  145. package/dist/do/GitModule.js +0 -784
  146. package/dist/do/GitModule.js.map +0 -1
  147. package/dist/do/GitRepoDO.d.ts +0 -281
  148. package/dist/do/GitRepoDO.d.ts.map +0 -1
  149. package/dist/do/GitRepoDO.js +0 -479
  150. package/dist/do/GitRepoDO.js.map +0 -1
  151. package/dist/do/bash-ast.d.ts +0 -246
  152. package/dist/do/bash-ast.d.ts.map +0 -1
  153. package/dist/do/bash-ast.js +0 -888
  154. package/dist/do/bash-ast.js.map +0 -1
  155. package/dist/do/container-executor.d.ts +0 -491
  156. package/dist/do/container-executor.d.ts.map +0 -1
  157. package/dist/do/container-executor.js +0 -731
  158. package/dist/do/container-executor.js.map +0 -1
  159. package/dist/do/index.d.ts +0 -53
  160. package/dist/do/index.d.ts.map +0 -1
  161. package/dist/do/index.js +0 -91
  162. package/dist/do/index.js.map +0 -1
  163. package/dist/do/tiered-storage.d.ts +0 -403
  164. package/dist/do/tiered-storage.d.ts.map +0 -1
  165. package/dist/do/tiered-storage.js +0 -689
  166. package/dist/do/tiered-storage.js.map +0 -1
  167. package/dist/do/withBash.d.ts +0 -231
  168. package/dist/do/withBash.d.ts.map +0 -1
  169. package/dist/do/withBash.js +0 -244
  170. package/dist/do/withBash.js.map +0 -1
  171. package/dist/do/withFs.d.ts +0 -237
  172. package/dist/do/withFs.d.ts.map +0 -1
  173. package/dist/do/withFs.js +0 -387
  174. package/dist/do/withFs.js.map +0 -1
  175. package/dist/do/withGit.d.ts +0 -180
  176. package/dist/do/withGit.d.ts.map +0 -1
  177. package/dist/do/withGit.js +0 -271
  178. package/dist/do/withGit.js.map +0 -1
  179. package/dist/durable-object/object-store.d.ts +0 -633
  180. package/dist/durable-object/object-store.d.ts.map +0 -1
  181. package/dist/durable-object/object-store.js +0 -1164
  182. package/dist/durable-object/object-store.js.map +0 -1
  183. package/dist/durable-object/schema.d.ts.map +0 -1
  184. package/dist/durable-object/schema.js.map +0 -1
  185. package/dist/durable-object/wal.d.ts +0 -416
  186. package/dist/durable-object/wal.d.ts.map +0 -1
  187. package/dist/durable-object/wal.js +0 -445
  188. package/dist/durable-object/wal.js.map +0 -1
  189. package/dist/mcp/adapter.d.ts +0 -772
  190. package/dist/mcp/adapter.d.ts.map +0 -1
  191. package/dist/mcp/adapter.js +0 -895
  192. package/dist/mcp/adapter.js.map +0 -1
  193. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
  194. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
  195. package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
  196. package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
  197. package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
  198. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
  199. package/dist/mcp/sandbox/object-store-proxy.js +0 -30
  200. package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
  201. package/dist/mcp/sandbox/template.d.ts +0 -17
  202. package/dist/mcp/sandbox/template.d.ts.map +0 -1
  203. package/dist/mcp/sandbox/template.js +0 -71
  204. package/dist/mcp/sandbox/template.js.map +0 -1
  205. package/dist/mcp/sandbox.d.ts +0 -764
  206. package/dist/mcp/sandbox.d.ts.map +0 -1
  207. package/dist/mcp/sandbox.js +0 -1362
  208. package/dist/mcp/sandbox.js.map +0 -1
  209. package/dist/mcp/sdk-adapter.d.ts +0 -835
  210. package/dist/mcp/sdk-adapter.d.ts.map +0 -1
  211. package/dist/mcp/sdk-adapter.js +0 -974
  212. package/dist/mcp/sdk-adapter.js.map +0 -1
  213. package/dist/mcp/tools/do.d.ts +0 -32
  214. package/dist/mcp/tools/do.d.ts.map +0 -1
  215. package/dist/mcp/tools/do.js +0 -117
  216. package/dist/mcp/tools/do.js.map +0 -1
  217. package/dist/ops/blame.d.ts +0 -551
  218. package/dist/ops/blame.d.ts.map +0 -1
  219. package/dist/ops/blame.js +0 -1037
  220. package/dist/ops/blame.js.map +0 -1
  221. package/dist/ops/branch.d.ts +0 -766
  222. package/dist/ops/branch.d.ts.map +0 -1
  223. package/dist/ops/branch.js +0 -950
  224. package/dist/ops/branch.js.map +0 -1
  225. package/dist/ops/commit-traversal.d.ts +0 -349
  226. package/dist/ops/commit-traversal.d.ts.map +0 -1
  227. package/dist/ops/commit-traversal.js +0 -821
  228. package/dist/ops/commit-traversal.js.map +0 -1
  229. package/dist/ops/commit.d.ts +0 -555
  230. package/dist/ops/commit.d.ts.map +0 -1
  231. package/dist/ops/commit.js +0 -826
  232. package/dist/ops/commit.js.map +0 -1
  233. package/dist/ops/merge-base.d.ts +0 -397
  234. package/dist/ops/merge-base.d.ts.map +0 -1
  235. package/dist/ops/merge-base.js +0 -691
  236. package/dist/ops/merge-base.js.map +0 -1
  237. package/dist/ops/merge.d.ts +0 -855
  238. package/dist/ops/merge.d.ts.map +0 -1
  239. package/dist/ops/merge.js +0 -1551
  240. package/dist/ops/merge.js.map +0 -1
  241. package/dist/ops/tag.d.ts +0 -247
  242. package/dist/ops/tag.d.ts.map +0 -1
  243. package/dist/ops/tag.js +0 -649
  244. package/dist/ops/tag.js.map +0 -1
  245. package/dist/ops/tree-builder.d.ts +0 -178
  246. package/dist/ops/tree-builder.d.ts.map +0 -1
  247. package/dist/ops/tree-builder.js +0 -271
  248. package/dist/ops/tree-builder.js.map +0 -1
  249. package/dist/ops/tree-diff.d.ts +0 -291
  250. package/dist/ops/tree-diff.d.ts.map +0 -1
  251. package/dist/ops/tree-diff.js +0 -705
  252. package/dist/ops/tree-diff.js.map +0 -1
  253. package/dist/pack/delta.d.ts +0 -248
  254. package/dist/pack/delta.d.ts.map +0 -1
  255. package/dist/pack/delta.js +0 -740
  256. package/dist/pack/delta.js.map +0 -1
  257. package/dist/pack/format.d.ts +0 -446
  258. package/dist/pack/format.d.ts.map +0 -1
  259. package/dist/pack/format.js +0 -572
  260. package/dist/pack/format.js.map +0 -1
  261. package/dist/pack/full-generation.d.ts +0 -612
  262. package/dist/pack/full-generation.d.ts.map +0 -1
  263. package/dist/pack/full-generation.js +0 -1378
  264. package/dist/pack/full-generation.js.map +0 -1
  265. package/dist/pack/generation.d.ts +0 -441
  266. package/dist/pack/generation.d.ts.map +0 -1
  267. package/dist/pack/generation.js +0 -707
  268. package/dist/pack/generation.js.map +0 -1
  269. package/dist/pack/index.d.ts +0 -502
  270. package/dist/pack/index.d.ts.map +0 -1
  271. package/dist/pack/index.js +0 -833
  272. package/dist/pack/index.js.map +0 -1
  273. package/dist/refs/branch.d.ts +0 -683
  274. package/dist/refs/branch.d.ts.map +0 -1
  275. package/dist/refs/branch.js +0 -881
  276. package/dist/refs/branch.js.map +0 -1
  277. package/dist/refs/storage.d.ts +0 -833
  278. package/dist/refs/storage.d.ts.map +0 -1
  279. package/dist/refs/storage.js +0 -1023
  280. package/dist/refs/storage.js.map +0 -1
  281. package/dist/refs/tag.d.ts +0 -860
  282. package/dist/refs/tag.d.ts.map +0 -1
  283. package/dist/refs/tag.js +0 -996
  284. package/dist/refs/tag.js.map +0 -1
  285. package/dist/storage/backend.d.ts +0 -425
  286. package/dist/storage/backend.d.ts.map +0 -1
  287. package/dist/storage/backend.js +0 -41
  288. package/dist/storage/backend.js.map +0 -1
  289. package/dist/storage/fsx-adapter.d.ts +0 -204
  290. package/dist/storage/fsx-adapter.d.ts.map +0 -1
  291. package/dist/storage/fsx-adapter.js +0 -518
  292. package/dist/storage/fsx-adapter.js.map +0 -1
  293. package/dist/storage/lru-cache.d.ts +0 -691
  294. package/dist/storage/lru-cache.d.ts.map +0 -1
  295. package/dist/storage/lru-cache.js +0 -813
  296. package/dist/storage/lru-cache.js.map +0 -1
  297. package/dist/storage/object-index.d.ts +0 -585
  298. package/dist/storage/object-index.d.ts.map +0 -1
  299. package/dist/storage/object-index.js +0 -532
  300. package/dist/storage/object-index.js.map +0 -1
  301. package/dist/storage/r2-pack.d.ts +0 -1257
  302. package/dist/storage/r2-pack.d.ts.map +0 -1
  303. package/dist/storage/r2-pack.js +0 -1773
  304. package/dist/storage/r2-pack.js.map +0 -1
  305. package/dist/tiered/cdc-pipeline.d.ts +0 -1888
  306. package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
  307. package/dist/tiered/cdc-pipeline.js +0 -1880
  308. package/dist/tiered/cdc-pipeline.js.map +0 -1
  309. package/dist/tiered/migration.d.ts +0 -1104
  310. package/dist/tiered/migration.d.ts.map +0 -1
  311. package/dist/tiered/migration.js +0 -1217
  312. package/dist/tiered/migration.js.map +0 -1
  313. package/dist/tiered/parquet-writer.d.ts +0 -1145
  314. package/dist/tiered/parquet-writer.d.ts.map +0 -1
  315. package/dist/tiered/parquet-writer.js +0 -1183
  316. package/dist/tiered/parquet-writer.js.map +0 -1
  317. package/dist/tiered/read-path.d.ts +0 -835
  318. package/dist/tiered/read-path.d.ts.map +0 -1
  319. package/dist/tiered/read-path.js +0 -487
  320. package/dist/tiered/read-path.js.map +0 -1
  321. package/dist/types/capability.d.ts +0 -1385
  322. package/dist/types/capability.d.ts.map +0 -1
  323. package/dist/types/capability.js +0 -36
  324. package/dist/types/capability.js.map +0 -1
  325. package/dist/types/index.d.ts +0 -13
  326. package/dist/types/index.d.ts.map +0 -1
  327. package/dist/types/index.js +0 -18
  328. package/dist/types/index.js.map +0 -1
  329. package/dist/types/interfaces.d.ts +0 -673
  330. package/dist/types/interfaces.d.ts.map +0 -1
  331. package/dist/types/interfaces.js +0 -26
  332. package/dist/types/interfaces.js.map +0 -1
  333. package/dist/types/objects.d.ts +0 -692
  334. package/dist/types/objects.d.ts.map +0 -1
  335. package/dist/types/objects.js +0 -837
  336. package/dist/types/objects.js.map +0 -1
  337. package/dist/types/storage.d.ts +0 -603
  338. package/dist/types/storage.d.ts.map +0 -1
  339. package/dist/types/storage.js +0 -191
  340. package/dist/types/storage.js.map +0 -1
  341. package/dist/types/worker-loader.d.ts +0 -60
  342. package/dist/types/worker-loader.d.ts.map +0 -1
  343. package/dist/types/worker-loader.js +0 -62
  344. package/dist/types/worker-loader.js.map +0 -1
  345. package/dist/utils/hash.d.ts +0 -198
  346. package/dist/utils/hash.d.ts.map +0 -1
  347. package/dist/utils/hash.js +0 -272
  348. package/dist/utils/hash.js.map +0 -1
  349. package/dist/utils/sha1.d.ts +0 -325
  350. package/dist/utils/sha1.d.ts.map +0 -1
  351. package/dist/utils/sha1.js +0 -635
  352. package/dist/utils/sha1.js.map +0 -1
  353. package/dist/wire/capabilities.d.ts +0 -1044
  354. package/dist/wire/capabilities.d.ts.map +0 -1
  355. package/dist/wire/capabilities.js +0 -941
  356. package/dist/wire/capabilities.js.map +0 -1
  357. package/dist/wire/path-security.d.ts +0 -157
  358. package/dist/wire/path-security.d.ts.map +0 -1
  359. package/dist/wire/path-security.js +0 -307
  360. package/dist/wire/path-security.js.map +0 -1
  361. package/dist/wire/pkt-line.d.ts +0 -345
  362. package/dist/wire/pkt-line.d.ts.map +0 -1
  363. package/dist/wire/pkt-line.js +0 -381
  364. package/dist/wire/pkt-line.js.map +0 -1
  365. package/dist/wire/receive-pack.d.ts +0 -1059
  366. package/dist/wire/receive-pack.d.ts.map +0 -1
  367. package/dist/wire/receive-pack.js +0 -1414
  368. package/dist/wire/receive-pack.js.map +0 -1
  369. package/dist/wire/smart-http.d.ts +0 -799
  370. package/dist/wire/smart-http.d.ts.map +0 -1
  371. package/dist/wire/smart-http.js +0 -945
  372. package/dist/wire/smart-http.js.map +0 -1
  373. package/dist/wire/upload-pack.d.ts +0 -727
  374. package/dist/wire/upload-pack.d.ts.map +0 -1
  375. package/dist/wire/upload-pack.js +0 -1141
  376. package/dist/wire/upload-pack.js.map +0 -1
@@ -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