@jshookmcp/jshook 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -5
- package/README.zh.md +36 -5
- package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-BNk-EoBt.mjs} +3 -3
- package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-Cq8q01kp.mjs} +5 -5
- package/dist/ConsoleMonitor-CPVQW1Y-.mjs +2201 -0
- package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-BNPxu0RH.mjs} +1 -1
- package/dist/DetailedDataManager-BQQcxh64.mjs +217 -0
- package/dist/EventBus-DgPmwpeu.mjs +141 -0
- package/dist/EvidenceGraphBridge-SFesNera.mjs +153 -0
- package/dist/{ExtensionManager-D5-bO9D8.mjs → ExtensionManager-CWYgw0YW.mjs} +13 -6
- package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-gzWtkKuf.mjs} +1 -1
- package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-B9gZCdFP.mjs} +3 -3
- package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-BLDH0dCv.mjs} +4 -4
- package/dist/HookGeneratorBuilders.core.generators.storage-CtcdK78Q.mjs +639 -0
- package/dist/InstrumentationSession-CvPC7Jwy.mjs +244 -0
- package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CbVdCIJF.mjs} +3 -3
- package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-BsDZbLYm.mjs} +81 -78
- package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-Bcpml6II.mjs} +44 -18
- package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-dZtA1ZGn.mjs} +14 -53
- package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-B-FjA2mJ.mjs} +1 -1
- package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-D1lzJ_VG.mjs} +2 -2
- package/dist/PageController-Bqm2kZ_X.mjs +417 -0
- package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-BOhyVsjx.mjs} +4 -4
- package/dist/PrerequisiteError-Dl33Svkz.mjs +20 -0
- package/dist/ResponseBuilder-D3iFYx2N.mjs +143 -0
- package/dist/ReverseEvidenceGraph-Dlsk94LC.mjs +269 -0
- package/dist/ScriptManager-aHHq0X7U.mjs +3000 -0
- package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-CqdIFlQl.mjs} +2 -2
- package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-DhFaPvRO.mjs} +3 -3
- package/dist/ToolCatalog-C0JGZoOm.mjs +582 -0
- package/dist/ToolError-jh9whhMd.mjs +15 -0
- package/dist/ToolProbe-oC7aPrkv.mjs +45 -0
- package/dist/ToolRegistry-BjaF4oNz.mjs +131 -0
- package/dist/ToolRouter.policy-BWV67ZK-.mjs +304 -0
- package/dist/TraceRecorder-DgxyVbdQ.mjs +519 -0
- package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-CePkipZY.mjs} +1 -1
- package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-BvKs-gxc.mjs} +2 -2
- package/dist/WorkflowEngine-CuvkZtWu.mjs +598 -0
- package/dist/analysis-CL9uACt9.mjs +463 -0
- package/dist/antidebug-CqDTB_uk.mjs +1081 -0
- package/dist/artifactRetention-CFEprwPw.mjs +591 -0
- package/dist/artifacts-Bk2-_uPq.mjs +59 -0
- package/dist/betterSqlite3-0pqusHHH.mjs +74 -0
- package/dist/binary-instrument-CXfpx6fT.mjs +979 -0
- package/dist/bind-helpers-xFfRF-qm.mjs +22 -0
- package/dist/boringssl-inspector-BH2D3VKc.mjs +180 -0
- package/dist/browser-BpOr5PEx.mjs +4082 -0
- package/dist/concurrency-Bt0yv1kJ.mjs +41 -0
- package/dist/{constants-CCvsN80K.mjs → constants-B0OANIBL.mjs} +88 -46
- package/dist/coordination-qUbyF8KU.mjs +259 -0
- package/dist/debugger-gnKxRSN0.mjs +1271 -0
- package/dist/definitions-6M-eejaT.mjs +53 -0
- package/dist/definitions-B18eyf0B.mjs +18 -0
- package/dist/definitions-B3QdlrHv.mjs +34 -0
- package/dist/definitions-B4rAvHNZ.mjs +63 -0
- package/dist/definitions-BB_4jnmy.mjs +37 -0
- package/dist/definitions-BMfYXoNC.mjs +43 -0
- package/dist/definitions-Beid2EB3.mjs +27 -0
- package/dist/definitions-C1UvM5Iy.mjs +126 -0
- package/dist/definitions-CXEI7QC72.mjs +216 -0
- package/dist/definitions-C_4r7Fo-2.mjs +14 -0
- package/dist/definitions-CkFDALoa.mjs +26 -0
- package/dist/definitions-Cke7zEb8.mjs +94 -0
- package/dist/definitions-ClJLzsJQ.mjs +25 -0
- package/dist/definitions-Cq-zroAU.mjs +28 -0
- package/dist/definitions-Cy3Sl6gV.mjs +34 -0
- package/dist/definitions-D3VsGcvz.mjs +47 -0
- package/dist/definitions-DVGfrn7y.mjs +96 -0
- package/dist/definitions-LKpC3-nL.mjs +9 -0
- package/dist/definitions-bAhHQJq9.mjs +359 -0
- package/dist/encoding-Bvz5jLRv.mjs +1065 -0
- package/dist/evidence-graph-bridge-C_fv9PuC.mjs +135 -0
- package/dist/{factory-CibqTNC8.mjs → factory-DxlGh9Xf.mjs} +37 -52
- package/dist/graphql-DYWzJ29s.mjs +1026 -0
- package/dist/handlers-9sAbfIg-.mjs +2552 -0
- package/dist/handlers-Bl8zkwz1.mjs +2716 -0
- package/dist/handlers-C67ktuRN.mjs +710 -0
- package/dist/handlers-C87g8oCe.mjs +276 -0
- package/dist/handlers-CTsDAO6p.mjs +681 -0
- package/dist/handlers-Cgyg6c0U.mjs +645 -0
- package/dist/handlers-D6j6yka7.mjs +2124 -0
- package/dist/handlers-DdFzXLvF.mjs +446 -0
- package/dist/handlers-DeLOCd5m.mjs +799 -0
- package/dist/handlers-DlCJN4Td.mjs +757 -0
- package/dist/handlers-DxGIq15_2.mjs +917 -0
- package/dist/handlers-U6L4xhuF.mjs +585 -0
- package/dist/handlers-tB9Mp9ZK.mjs +84 -0
- package/dist/handlers-tiy7EIBp.mjs +572 -0
- package/dist/handlers.impl-DS0d9fUw.mjs +761 -0
- package/dist/hooks-CzCWByww.mjs +898 -0
- package/dist/index.mjs +377 -155
- package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
- package/dist/maintenance-P7ePRXQC.mjs +830 -0
- package/dist/manifest-2ToTpjv8.mjs +106 -0
- package/dist/manifest-3g71z6Bg.mjs +79 -0
- package/dist/manifest-82baTv4U.mjs +45 -0
- package/dist/manifest-B3QVVeBS.mjs +82 -0
- package/dist/manifest-BB2J8IMJ.mjs +149 -0
- package/dist/manifest-BKbgbSiY.mjs +60 -0
- package/dist/manifest-Bcf-TJzH.mjs +848 -0
- package/dist/manifest-BmtZzQiQ2.mjs +45 -0
- package/dist/manifest-Bnd7kqEY.mjs +55 -0
- package/dist/manifest-BqQX6OQC2.mjs +65 -0
- package/dist/manifest-BqrQ4Tpj.mjs +81 -0
- package/dist/manifest-Br4RPFt5.mjs +370 -0
- package/dist/manifest-C5qDjysN.mjs +107 -0
- package/dist/manifest-C9RT5nk32.mjs +34 -0
- package/dist/manifest-CAhOuvSl.mjs +204 -0
- package/dist/manifest-CBYWCUBJ.mjs +51 -0
- package/dist/manifest-CFADCRa1.mjs +37 -0
- package/dist/manifest-CQVhavRF.mjs +114 -0
- package/dist/manifest-CT7zZBV1.mjs +48 -0
- package/dist/manifest-CV12bcrF.mjs +121 -0
- package/dist/manifest-CXsRWjjI.mjs +224 -0
- package/dist/manifest-CZLUCfG02.mjs +95 -0
- package/dist/manifest-D6phHKFd.mjs +131 -0
- package/dist/manifest-DCyjf4n2.mjs +294 -0
- package/dist/manifest-DHsnKgP6.mjs +60 -0
- package/dist/manifest-Df_dliIe.mjs +55 -0
- package/dist/manifest-Dh8WBmEW.mjs +129 -0
- package/dist/manifest-DhKRAT8_.mjs +92 -0
- package/dist/manifest-DlpTj4ic2.mjs +193 -0
- package/dist/manifest-DrbmZcFl2.mjs +253 -0
- package/dist/manifest-DuwHjUa5.mjs +70 -0
- package/dist/manifest-DzwvxPJX.mjs +38 -0
- package/dist/manifest-NXctwWQq.mjs +68 -0
- package/dist/manifest-Sc_0JQ13.mjs +418 -0
- package/dist/manifest-gZ4s_UtG.mjs +96 -0
- package/dist/manifest-qSleDqdO.mjs +1023 -0
- package/dist/modules-C184v-S9.mjs +11365 -0
- package/dist/mojo-ipc-B_H61Afw.mjs +525 -0
- package/dist/network-671Cw6hV.mjs +3346 -0
- package/dist/{artifacts-BbdOMET5.mjs → outputPaths-B1uGmrWZ.mjs} +219 -212
- package/dist/parse-args-BlRjqlkL.mjs +39 -0
- package/dist/platform-WmNn8Sxb.mjs +2070 -0
- package/dist/process-QcbIy5Zq.mjs +1401 -0
- package/dist/proxy-DqNs0bAd.mjs +170 -0
- package/dist/registry-D-6e18lB.mjs +34 -0
- package/dist/response-BQVP-xUn.mjs +28 -0
- package/dist/server/plugin-api.mjs +2 -2
- package/dist/shared-state-board-DV-dpHFJ.mjs +586 -0
- package/dist/sourcemap-Dq8ez8vS.mjs +650 -0
- package/dist/ssrf-policy-ZaUfvhq7.mjs +166 -0
- package/dist/streaming-BUQ0VJsg.mjs +725 -0
- package/dist/tool-builder-DCbIC5Eo.mjs +186 -0
- package/dist/transform-CiYJfNX0.mjs +1007 -0
- package/dist/types-Bx92KJfT.mjs +4 -0
- package/dist/wasm-DQTnHDs4.mjs +531 -0
- package/dist/workflow-f3xJOcjx.mjs +725 -0
- package/package.json +16 -16
- package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
- package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
- package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-CDe5WPSV.mjs} +0 -0
- package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-Bo4T3bz8.mjs} +0 -0
- package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-CwVLVdDM.mjs} +0 -0
- package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-DVkj9kpI.mjs} +0 -0
- package/dist/{types-BBjOqye-.mjs → types-CPhOReNX.mjs} +1 -1
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
import { cn as SANDBOX_MEMORY_LIMIT_MB, on as SANDBOX_EXEC_TIMEOUT_MS, sn as SANDBOX_MAX_TIMEOUT_MS } from "./constants-B0OANIBL.mjs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { getQuickJS } from "quickjs-emscripten";
|
|
4
|
+
//#region src/server/sandbox/SandboxHelpers.ts
|
|
5
|
+
/**
|
|
6
|
+
* SandboxHelpers — Pre-built pure-JS utility libraries for the sandbox.
|
|
7
|
+
*
|
|
8
|
+
* These helpers are evaluated inside QuickJS before user code runs,
|
|
9
|
+
* providing common utilities (base64, hex, hashing, JSON, array, string)
|
|
10
|
+
* without requiring Node.js APIs.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Pure-JS source string that is eval'd inside the sandbox environment.
|
|
14
|
+
* All implementations are self-contained with no external dependencies.
|
|
15
|
+
*/
|
|
16
|
+
const SANDBOX_HELPER_SOURCE = `
|
|
17
|
+
(function() {
|
|
18
|
+
var helpers = {};
|
|
19
|
+
|
|
20
|
+
// ── base64 ──
|
|
21
|
+
helpers.base64 = {
|
|
22
|
+
_chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
|
|
23
|
+
encode: function(str) {
|
|
24
|
+
var output = '';
|
|
25
|
+
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
|
26
|
+
var i = 0;
|
|
27
|
+
while (i < str.length) {
|
|
28
|
+
chr1 = str.charCodeAt(i++);
|
|
29
|
+
chr2 = str.charCodeAt(i++);
|
|
30
|
+
chr3 = str.charCodeAt(i++);
|
|
31
|
+
enc1 = chr1 >> 2;
|
|
32
|
+
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
|
33
|
+
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
|
34
|
+
enc4 = chr3 & 63;
|
|
35
|
+
if (isNaN(chr2)) { enc3 = enc4 = 64; }
|
|
36
|
+
else if (isNaN(chr3)) { enc4 = 64; }
|
|
37
|
+
output += this._chars.charAt(enc1) + this._chars.charAt(enc2) +
|
|
38
|
+
this._chars.charAt(enc3) + this._chars.charAt(enc4);
|
|
39
|
+
}
|
|
40
|
+
return output;
|
|
41
|
+
},
|
|
42
|
+
decode: function(str) {
|
|
43
|
+
var output = '';
|
|
44
|
+
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
|
45
|
+
var i = 0;
|
|
46
|
+
str = str.replace(/[^A-Za-z0-9+/=]/g, '');
|
|
47
|
+
while (i < str.length) {
|
|
48
|
+
enc1 = this._chars.indexOf(str.charAt(i++));
|
|
49
|
+
enc2 = this._chars.indexOf(str.charAt(i++));
|
|
50
|
+
enc3 = this._chars.indexOf(str.charAt(i++));
|
|
51
|
+
enc4 = this._chars.indexOf(str.charAt(i++));
|
|
52
|
+
chr1 = (enc1 << 2) | (enc2 >> 4);
|
|
53
|
+
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
|
54
|
+
chr3 = ((enc3 & 3) << 6) | enc4;
|
|
55
|
+
output += String.fromCharCode(chr1);
|
|
56
|
+
if (enc3 !== 64) output += String.fromCharCode(chr2);
|
|
57
|
+
if (enc4 !== 64) output += String.fromCharCode(chr3);
|
|
58
|
+
}
|
|
59
|
+
return output;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ── hex ──
|
|
64
|
+
helpers.hex = {
|
|
65
|
+
encode: function(str) {
|
|
66
|
+
var hex = '';
|
|
67
|
+
for (var i = 0; i < str.length; i++) {
|
|
68
|
+
hex += ('0' + str.charCodeAt(i).toString(16)).slice(-2);
|
|
69
|
+
}
|
|
70
|
+
return hex;
|
|
71
|
+
},
|
|
72
|
+
decode: function(hex) {
|
|
73
|
+
var str = '';
|
|
74
|
+
for (var i = 0; i < hex.length; i += 2) {
|
|
75
|
+
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
|
76
|
+
}
|
|
77
|
+
return str;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ── hash (simple djb2/fnv for in-sandbox use; NOT cryptographic!) ──
|
|
82
|
+
helpers.hash = {
|
|
83
|
+
djb2: function(str) {
|
|
84
|
+
var hash = 5381;
|
|
85
|
+
for (var i = 0; i < str.length; i++) {
|
|
86
|
+
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
87
|
+
hash = hash & hash; // Convert to 32-bit
|
|
88
|
+
}
|
|
89
|
+
return (hash >>> 0).toString(16);
|
|
90
|
+
},
|
|
91
|
+
fnv1a: function(str) {
|
|
92
|
+
var hash = 0x811c9dc5;
|
|
93
|
+
for (var i = 0; i < str.length; i++) {
|
|
94
|
+
hash ^= str.charCodeAt(i);
|
|
95
|
+
hash = Math.imul(hash, 0x01000193);
|
|
96
|
+
}
|
|
97
|
+
return (hash >>> 0).toString(16);
|
|
98
|
+
},
|
|
99
|
+
/** Simple MD5 — pure JS implementation */
|
|
100
|
+
md5: function(str) {
|
|
101
|
+
// Lightweight MD5 for sandbox use
|
|
102
|
+
function md5cycle(x, k) {
|
|
103
|
+
var a = x[0], b = x[1], c = x[2], d = x[3];
|
|
104
|
+
a = ff(a,b,c,d,k[0],7,-680876936);d=ff(d,a,b,c,k[1],12,-389564586);c=ff(c,d,a,b,k[2],17,606105819);b=ff(b,c,d,a,k[3],22,-1044525330);
|
|
105
|
+
a=ff(a,b,c,d,k[4],7,-176418897);d=ff(d,a,b,c,k[5],12,1200080426);c=ff(c,d,a,b,k[6],17,-1473231341);b=ff(b,c,d,a,k[7],22,-45705983);
|
|
106
|
+
a=ff(a,b,c,d,k[8],7,1770035416);d=ff(d,a,b,c,k[9],12,-1958414417);c=ff(c,d,a,b,k[10],17,-42063);b=ff(b,c,d,a,k[11],22,-1990404162);
|
|
107
|
+
a=ff(a,b,c,d,k[12],7,1804603682);d=ff(d,a,b,c,k[13],12,-40341101);c=ff(c,d,a,b,k[14],17,-1502002290);b=ff(b,c,d,a,k[15],22,1236535329);
|
|
108
|
+
a=gg(a,b,c,d,k[1],5,-165796510);d=gg(d,a,b,c,k[6],9,-1069501632);c=gg(c,d,a,b,k[11],14,643717713);b=gg(b,c,d,a,k[0],20,-373897302);
|
|
109
|
+
a=gg(a,b,c,d,k[5],5,-701558691);d=gg(d,a,b,c,k[10],9,38016083);c=gg(c,d,a,b,k[15],14,-660478335);b=gg(b,c,d,a,k[4],20,-405537848);
|
|
110
|
+
a=gg(a,b,c,d,k[9],5,568446438);d=gg(d,a,b,c,k[14],9,-1019803690);c=gg(c,d,a,b,k[3],14,-187363961);b=gg(b,c,d,a,k[8],20,1163531501);
|
|
111
|
+
a=gg(a,b,c,d,k[13],5,-1444681467);d=gg(d,a,b,c,k[2],9,-51403784);c=gg(c,d,a,b,k[7],14,1735328473);b=gg(b,c,d,a,k[12],20,-1926607734);
|
|
112
|
+
a=hh(a,b,c,d,k[5],4,-378558);d=hh(d,a,b,c,k[8],11,-2022574463);c=hh(c,d,a,b,k[11],16,1839030562);b=hh(b,c,d,a,k[14],23,-35309556);
|
|
113
|
+
a=hh(a,b,c,d,k[1],4,-1530992060);d=hh(d,a,b,c,k[4],11,1272893353);c=hh(c,d,a,b,k[7],16,-155497632);b=hh(b,c,d,a,k[10],23,-1094730640);
|
|
114
|
+
a=hh(a,b,c,d,k[13],4,681279174);d=hh(d,a,b,c,k[0],11,-358537222);c=hh(c,d,a,b,k[3],16,-722521979);b=hh(b,c,d,a,k[6],23,76029189);
|
|
115
|
+
a=hh(a,b,c,d,k[9],4,-640364487);d=hh(d,a,b,c,k[12],11,-421815835);c=hh(c,d,a,b,k[15],16,530742520);b=hh(b,c,d,a,k[2],23,-995338651);
|
|
116
|
+
a=ii(a,b,c,d,k[0],6,-198630844);d=ii(d,a,b,c,k[7],10,1126891415);c=ii(c,d,a,b,k[14],15,-1416354905);b=ii(b,c,d,a,k[5],21,-57434055);
|
|
117
|
+
a=ii(a,b,c,d,k[12],6,1700485571);d=ii(d,a,b,c,k[3],10,-1894986606);c=ii(c,d,a,b,k[10],15,-1051523);b=ii(b,c,d,a,k[1],21,-2054922799);
|
|
118
|
+
a=ii(a,b,c,d,k[8],6,1873313359);d=ii(d,a,b,c,k[15],10,-30611744);c=ii(c,d,a,b,k[6],15,-1560198380);b=ii(b,c,d,a,k[13],21,1309151649);
|
|
119
|
+
a=ii(a,b,c,d,k[4],6,-145523070);d=ii(d,a,b,c,k[11],10,-1120210379);c=ii(c,d,a,b,k[2],15,718787259);b=ii(b,c,d,a,k[9],21,-343485551);
|
|
120
|
+
x[0]=add32(a,x[0]);x[1]=add32(b,x[1]);x[2]=add32(c,x[2]);x[3]=add32(d,x[3]);
|
|
121
|
+
}
|
|
122
|
+
function cmn(q,a,b,x,s,t){a=add32(add32(a,q),add32(x,t));return add32((a<<s)|(a>>>(32-s)),b)}
|
|
123
|
+
function ff(a,b,c,d,x,s,t){return cmn((b&c)|((~b)&d),a,b,x,s,t)}
|
|
124
|
+
function gg(a,b,c,d,x,s,t){return cmn((b&d)|(c&(~d)),a,b,x,s,t)}
|
|
125
|
+
function hh(a,b,c,d,x,s,t){return cmn(b^c^d,a,b,x,s,t)}
|
|
126
|
+
function ii(a,b,c,d,x,s,t){return cmn(c^(b|(~d)),a,b,x,s,t)}
|
|
127
|
+
function add32(a,b){return(a+b)&0xFFFFFFFF}
|
|
128
|
+
|
|
129
|
+
var n = str.length;
|
|
130
|
+
var state = [1732584193,-271733879,-1732584194,271733878];
|
|
131
|
+
var tail = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
|
132
|
+
var i;
|
|
133
|
+
for (i = 64; i <= n; i += 64) {
|
|
134
|
+
var blk = [];
|
|
135
|
+
for (var j = i - 64; j < i; j += 4) {
|
|
136
|
+
blk.push(str.charCodeAt(j)|(str.charCodeAt(j+1)<<8)|(str.charCodeAt(j+2)<<16)|(str.charCodeAt(j+3)<<24));
|
|
137
|
+
}
|
|
138
|
+
md5cycle(state, blk);
|
|
139
|
+
}
|
|
140
|
+
for (var j = 0; j < 16; j++) tail[j] = 0;
|
|
141
|
+
for (i = i - 64; i < n; i++) {
|
|
142
|
+
tail[i>>2] |= str.charCodeAt(i) << ((i%4)<<3);
|
|
143
|
+
}
|
|
144
|
+
tail[i>>2] |= 0x80 << ((i%4)<<3);
|
|
145
|
+
if (i > 55) { md5cycle(state, tail); for (j = 0; j < 16; j++) tail[j] = 0; }
|
|
146
|
+
tail[14] = n * 8;
|
|
147
|
+
md5cycle(state, tail);
|
|
148
|
+
|
|
149
|
+
var hex_chr = '0123456789abcdef';
|
|
150
|
+
var s = '';
|
|
151
|
+
for (i = 0; i < 4; i++) {
|
|
152
|
+
for (j = 0; j < 4; j++) {
|
|
153
|
+
s += hex_chr.charAt((state[i] >> (j*8+4)) & 0x0F) + hex_chr.charAt((state[i] >> (j*8)) & 0x0F);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return s;
|
|
157
|
+
},
|
|
158
|
+
sha256: function(str) {
|
|
159
|
+
// Minimal pure-JS SHA-256
|
|
160
|
+
var K = [0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
|
|
161
|
+
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
|
|
162
|
+
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
|
|
163
|
+
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
|
|
164
|
+
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
|
|
165
|
+
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
|
|
166
|
+
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
|
|
167
|
+
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2];
|
|
168
|
+
function rr(x,n){return(x>>>n)|(x<<(32-n))}
|
|
169
|
+
var H=[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19];
|
|
170
|
+
var msg=[];
|
|
171
|
+
for(var i=0;i<str.length;i++)msg.push(str.charCodeAt(i));
|
|
172
|
+
msg.push(0x80);
|
|
173
|
+
var l=msg.length;
|
|
174
|
+
while(l%64!==56){msg.push(0);l++;}
|
|
175
|
+
var bits=str.length*8;
|
|
176
|
+
for(i=7;i>=0;i--)msg.push((bits>>>(i*8))&0xff);
|
|
177
|
+
for(var offset=0;offset<msg.length;offset+=64){
|
|
178
|
+
var W=[];
|
|
179
|
+
for(i=0;i<16;i++)W[i]=(msg[offset+i*4]<<24)|(msg[offset+i*4+1]<<16)|(msg[offset+i*4+2]<<8)|msg[offset+i*4+3];
|
|
180
|
+
for(i=16;i<64;i++){
|
|
181
|
+
var s0=rr(W[i-15],7)^rr(W[i-15],18)^(W[i-15]>>>3);
|
|
182
|
+
var s1=rr(W[i-2],17)^rr(W[i-2],19)^(W[i-2]>>>10);
|
|
183
|
+
W[i]=(W[i-16]+s0+W[i-7]+s1)|0;
|
|
184
|
+
}
|
|
185
|
+
var a=H[0],b=H[1],c=H[2],d=H[3],e=H[4],f=H[5],g=H[6],h=H[7];
|
|
186
|
+
for(i=0;i<64;i++){
|
|
187
|
+
var S1=rr(e,6)^rr(e,11)^rr(e,25);
|
|
188
|
+
var ch=(e&f)^((~e)&g);
|
|
189
|
+
var t1=(h+S1+ch+K[i]+W[i])|0;
|
|
190
|
+
var S0=rr(a,2)^rr(a,13)^rr(a,22);
|
|
191
|
+
var maj=(a&b)^(a&c)^(b&c);
|
|
192
|
+
var t2=(S0+maj)|0;
|
|
193
|
+
h=g;g=f;f=e;e=(d+t1)|0;d=c;c=b;b=a;a=(t1+t2)|0;
|
|
194
|
+
}
|
|
195
|
+
H[0]=(H[0]+a)|0;H[1]=(H[1]+b)|0;H[2]=(H[2]+c)|0;H[3]=(H[3]+d)|0;
|
|
196
|
+
H[4]=(H[4]+e)|0;H[5]=(H[5]+f)|0;H[6]=(H[6]+g)|0;H[7]=(H[7]+h)|0;
|
|
197
|
+
}
|
|
198
|
+
var hex='';
|
|
199
|
+
for(i=0;i<8;i++)for(var j=7;j>=0;j--)hex+='0123456789abcdef'.charAt((H[i]>>>(j*4))&0xf);
|
|
200
|
+
return hex;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// ── json ──
|
|
205
|
+
helpers.json = {
|
|
206
|
+
safeParse: function(str) {
|
|
207
|
+
try { return { ok: true, value: JSON.parse(str) }; }
|
|
208
|
+
catch(e) { return { ok: false, error: e.message }; }
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// ── array ──
|
|
213
|
+
helpers.array = {
|
|
214
|
+
chunk: function(arr, size) {
|
|
215
|
+
var result = [];
|
|
216
|
+
for (var i = 0; i < arr.length; i += size) {
|
|
217
|
+
result.push(arr.slice(i, i + size));
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
},
|
|
221
|
+
flatten: function(arr) {
|
|
222
|
+
var result = [];
|
|
223
|
+
for (var i = 0; i < arr.length; i++) {
|
|
224
|
+
if (Array.isArray(arr[i])) {
|
|
225
|
+
result = result.concat(this.flatten(arr[i]));
|
|
226
|
+
} else {
|
|
227
|
+
result.push(arr[i]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
},
|
|
232
|
+
unique: function(arr) {
|
|
233
|
+
var seen = {};
|
|
234
|
+
var result = [];
|
|
235
|
+
for (var i = 0; i < arr.length; i++) {
|
|
236
|
+
var key = JSON.stringify(arr[i]);
|
|
237
|
+
if (!seen[key]) {
|
|
238
|
+
seen[key] = true;
|
|
239
|
+
result.push(arr[i]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// ── string ──
|
|
247
|
+
helpers.string = {
|
|
248
|
+
camelCase: function(s) {
|
|
249
|
+
return s.replace(/[-_\\s]+(\\w)/g, function(_, c) { return c.toUpperCase(); })
|
|
250
|
+
.replace(/^\\w/, function(c) { return c.toLowerCase(); });
|
|
251
|
+
},
|
|
252
|
+
snakeCase: function(s) {
|
|
253
|
+
return s.replace(/([A-Z])/g, '_$1').toLowerCase()
|
|
254
|
+
.replace(/[-\\s]+/g, '_')
|
|
255
|
+
.replace(/^_/, '');
|
|
256
|
+
},
|
|
257
|
+
truncate: function(s, len) {
|
|
258
|
+
if (s.length <= len) return s;
|
|
259
|
+
return s.slice(0, len - 3) + '...';
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Expose to global scope
|
|
264
|
+
globalThis.helpers = helpers;
|
|
265
|
+
})();
|
|
266
|
+
`;
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/server/sandbox/QuickJSSandbox.ts
|
|
269
|
+
/**
|
|
270
|
+
* QuickJSSandbox — WASM-isolated JavaScript execution engine.
|
|
271
|
+
*
|
|
272
|
+
* Uses quickjs-emscripten to run untrusted code inside a QuickJS WASM
|
|
273
|
+
* runtime. Each `execute()` call spins up a fresh runtime (no state
|
|
274
|
+
* leakage across calls) with configurable timeout and memory limits.
|
|
275
|
+
*
|
|
276
|
+
* Provides stronger isolation than the existing Node.js vm-based
|
|
277
|
+
* ExecutionSandbox because the guest code runs inside WebAssembly —
|
|
278
|
+
* it cannot reach Node.js APIs, the filesystem, or the network even
|
|
279
|
+
* if it escapes the QuickJS VM.
|
|
280
|
+
*/
|
|
281
|
+
const DEFAULT_TIMEOUT_MS = SANDBOX_EXEC_TIMEOUT_MS;
|
|
282
|
+
const DEFAULT_MEMORY_LIMIT_BYTES = SANDBOX_MEMORY_LIMIT_MB * 1024 * 1024;
|
|
283
|
+
const DEFAULT_MAX_BRIDGE_CALLS = 10;
|
|
284
|
+
/**
|
|
285
|
+
* Marshal a host value into a QuickJS handle.
|
|
286
|
+
*
|
|
287
|
+
* Supports primitives, arrays, and plain objects. Anything else
|
|
288
|
+
* is converted to its JSON representation (string).
|
|
289
|
+
*/
|
|
290
|
+
function marshalToQuickJS(ctx, value) {
|
|
291
|
+
if (value === null || value === void 0) return ctx.undefined;
|
|
292
|
+
switch (typeof value) {
|
|
293
|
+
case "string": return ctx.newString(value);
|
|
294
|
+
case "number": return ctx.newNumber(value);
|
|
295
|
+
case "boolean": return value ? ctx.true : ctx.false;
|
|
296
|
+
case "object": {
|
|
297
|
+
if (Array.isArray(value)) {
|
|
298
|
+
const arr = ctx.newArray();
|
|
299
|
+
for (let i = 0; i < value.length; i++) {
|
|
300
|
+
const elem = marshalToQuickJS(ctx, value[i]);
|
|
301
|
+
ctx.setProp(arr, i, elem);
|
|
302
|
+
elem.dispose();
|
|
303
|
+
}
|
|
304
|
+
return arr;
|
|
305
|
+
}
|
|
306
|
+
const obj = ctx.newObject();
|
|
307
|
+
for (const [k, v] of Object.entries(value)) {
|
|
308
|
+
const prop = marshalToQuickJS(ctx, v);
|
|
309
|
+
ctx.setProp(obj, k, prop);
|
|
310
|
+
prop.dispose();
|
|
311
|
+
}
|
|
312
|
+
return obj;
|
|
313
|
+
}
|
|
314
|
+
default: return ctx.newString(String(value));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Unmarshal a QuickJS handle back to a host value.
|
|
319
|
+
*/
|
|
320
|
+
function unmarshalFromQuickJS(ctx, handle) {
|
|
321
|
+
switch (ctx.typeof(handle)) {
|
|
322
|
+
case "undefined": return;
|
|
323
|
+
case "number": return ctx.getNumber(handle);
|
|
324
|
+
case "string": return ctx.getString(handle);
|
|
325
|
+
case "boolean": return ctx.dump(handle);
|
|
326
|
+
case "object": return ctx.dump(handle);
|
|
327
|
+
default: return ctx.dump(handle);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
var QuickJSSandbox = class {
|
|
331
|
+
bridge = null;
|
|
332
|
+
/**
|
|
333
|
+
* Set an optional MCP bridge for host tool invocation from sandbox.
|
|
334
|
+
*/
|
|
335
|
+
setBridge(bridge) {
|
|
336
|
+
this.bridge = bridge;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Execute JavaScript code inside a fresh WASM-isolated QuickJS runtime.
|
|
340
|
+
*
|
|
341
|
+
* Every call creates a new runtime + context, evaluates code, and tears
|
|
342
|
+
* it down. There is zero state leakage between calls.
|
|
343
|
+
*/
|
|
344
|
+
async execute(code, options = {}) {
|
|
345
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
346
|
+
const memoryLimitBytes = options.memoryLimitBytes ?? DEFAULT_MEMORY_LIMIT_BYTES;
|
|
347
|
+
const runtime = (await getQuickJS()).newRuntime();
|
|
348
|
+
runtime.setMemoryLimit(memoryLimitBytes);
|
|
349
|
+
const startTime = Date.now();
|
|
350
|
+
let timedOut = false;
|
|
351
|
+
runtime.setInterruptHandler(() => {
|
|
352
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
353
|
+
timedOut = true;
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
return false;
|
|
357
|
+
});
|
|
358
|
+
const context = runtime.newContext();
|
|
359
|
+
const logs = [];
|
|
360
|
+
try {
|
|
361
|
+
this._injectConsole(context, logs);
|
|
362
|
+
this._injectHelpers(context);
|
|
363
|
+
if (this.bridge) this._injectBridge(context, this.bridge, logs);
|
|
364
|
+
if (options.globals) this._injectGlobals(context, options.globals);
|
|
365
|
+
const result = context.evalCode(code, "sandbox-eval.js");
|
|
366
|
+
if (result.error) {
|
|
367
|
+
const errorMsg = context.dump(result.error);
|
|
368
|
+
result.error.dispose();
|
|
369
|
+
if (timedOut) return {
|
|
370
|
+
ok: false,
|
|
371
|
+
error: "Execution timed out",
|
|
372
|
+
timedOut: true,
|
|
373
|
+
durationMs: Date.now() - startTime,
|
|
374
|
+
logs
|
|
375
|
+
};
|
|
376
|
+
return {
|
|
377
|
+
ok: false,
|
|
378
|
+
error: typeof errorMsg === "object" ? JSON.stringify(errorMsg) : String(errorMsg),
|
|
379
|
+
timedOut: false,
|
|
380
|
+
durationMs: Date.now() - startTime,
|
|
381
|
+
logs
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const output = unmarshalFromQuickJS(context, result.value);
|
|
385
|
+
result.value.dispose();
|
|
386
|
+
return {
|
|
387
|
+
ok: true,
|
|
388
|
+
output,
|
|
389
|
+
timedOut: false,
|
|
390
|
+
durationMs: Date.now() - startTime,
|
|
391
|
+
logs
|
|
392
|
+
};
|
|
393
|
+
} catch (err) {
|
|
394
|
+
return {
|
|
395
|
+
ok: false,
|
|
396
|
+
error: err instanceof Error ? err.message : String(err),
|
|
397
|
+
timedOut,
|
|
398
|
+
durationMs: Date.now() - startTime,
|
|
399
|
+
logs
|
|
400
|
+
};
|
|
401
|
+
} finally {
|
|
402
|
+
context.dispose();
|
|
403
|
+
runtime.dispose();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Execute JavaScript code with multi-round MCP bridge orchestration.
|
|
408
|
+
*
|
|
409
|
+
* The sandbox script can call `mcp.call(name, args)` which enqueues
|
|
410
|
+
* the request. After each evaluation round, pending calls are resolved
|
|
411
|
+
* asynchronously on the host side, and results are injected back as
|
|
412
|
+
* `__bridgeResults` for the next round.
|
|
413
|
+
*
|
|
414
|
+
* The script should check `typeof __bridgeResults !== 'undefined'`
|
|
415
|
+
* to determine if it's in a continuation round and read prior results.
|
|
416
|
+
*
|
|
417
|
+
* @param code - JavaScript source to evaluate
|
|
418
|
+
* @param bridge - MCPBridge instance for tool dispatch
|
|
419
|
+
* @param options - Orchestration options (maxBridgeCalls, bridgeAllowlist, etc.)
|
|
420
|
+
*/
|
|
421
|
+
async executeWithOrchestration(code, bridge, options = {}) {
|
|
422
|
+
const maxBridgeCalls = options.maxBridgeCalls ?? DEFAULT_MAX_BRIDGE_CALLS;
|
|
423
|
+
const startTime = Date.now();
|
|
424
|
+
const allLogs = [];
|
|
425
|
+
const allBridgeCalls = [];
|
|
426
|
+
if (options.bridgeAllowlist) bridge.setAllowlist(options.bridgeAllowlist);
|
|
427
|
+
let bridgeResults = {};
|
|
428
|
+
let lastOutput;
|
|
429
|
+
let round = 0;
|
|
430
|
+
while (round <= maxBridgeCalls) {
|
|
431
|
+
const roundGlobals = {
|
|
432
|
+
...options.globals,
|
|
433
|
+
__bridgeRound: round
|
|
434
|
+
};
|
|
435
|
+
if (round > 0) roundGlobals.__bridgeResults = bridgeResults;
|
|
436
|
+
const roundResult = await this._executeOneRound(code, bridge, {
|
|
437
|
+
...options,
|
|
438
|
+
globals: roundGlobals
|
|
439
|
+
});
|
|
440
|
+
allLogs.push(...roundResult.logs);
|
|
441
|
+
if (!roundResult.ok || roundResult.timedOut) return {
|
|
442
|
+
...roundResult,
|
|
443
|
+
logs: allLogs,
|
|
444
|
+
durationMs: Date.now() - startTime,
|
|
445
|
+
bridgeCallCount: allBridgeCalls.length,
|
|
446
|
+
bridgeCalls: allBridgeCalls
|
|
447
|
+
};
|
|
448
|
+
lastOutput = roundResult.output;
|
|
449
|
+
if (!bridge.hasPending()) break;
|
|
450
|
+
const pending = bridge.drainPending();
|
|
451
|
+
const roundResults = {};
|
|
452
|
+
for (const req of pending) try {
|
|
453
|
+
const result = await bridge.call(req.toolName, req.args);
|
|
454
|
+
roundResults[req.id] = result;
|
|
455
|
+
allBridgeCalls.push({
|
|
456
|
+
toolName: req.toolName,
|
|
457
|
+
args: req.args,
|
|
458
|
+
result
|
|
459
|
+
});
|
|
460
|
+
} catch (err) {
|
|
461
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
462
|
+
roundResults[req.id] = {
|
|
463
|
+
__error: true,
|
|
464
|
+
message: errorMsg
|
|
465
|
+
};
|
|
466
|
+
allBridgeCalls.push({
|
|
467
|
+
toolName: req.toolName,
|
|
468
|
+
args: req.args,
|
|
469
|
+
result: {
|
|
470
|
+
__error: true,
|
|
471
|
+
message: errorMsg
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
bridgeResults = {
|
|
476
|
+
...bridgeResults,
|
|
477
|
+
...roundResults
|
|
478
|
+
};
|
|
479
|
+
round++;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
ok: true,
|
|
483
|
+
output: lastOutput,
|
|
484
|
+
timedOut: false,
|
|
485
|
+
durationMs: Date.now() - startTime,
|
|
486
|
+
logs: allLogs,
|
|
487
|
+
bridgeCallCount: allBridgeCalls.length,
|
|
488
|
+
bridgeCalls: allBridgeCalls
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Run a single evaluation round inside a fresh QuickJS runtime.
|
|
493
|
+
* Used internally by executeWithOrchestration.
|
|
494
|
+
*/
|
|
495
|
+
async _executeOneRound(code, bridge, options = {}) {
|
|
496
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
497
|
+
const memoryLimitBytes = options.memoryLimitBytes ?? DEFAULT_MEMORY_LIMIT_BYTES;
|
|
498
|
+
const runtime = (await getQuickJS()).newRuntime();
|
|
499
|
+
runtime.setMemoryLimit(memoryLimitBytes);
|
|
500
|
+
const startTime = Date.now();
|
|
501
|
+
let timedOut = false;
|
|
502
|
+
runtime.setInterruptHandler(() => {
|
|
503
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
504
|
+
timedOut = true;
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
return false;
|
|
508
|
+
});
|
|
509
|
+
const context = runtime.newContext();
|
|
510
|
+
const logs = [];
|
|
511
|
+
try {
|
|
512
|
+
this._injectConsole(context, logs);
|
|
513
|
+
this._injectHelpers(context);
|
|
514
|
+
this._injectBridgeForOrchestration(context, bridge, logs);
|
|
515
|
+
if (options.globals) this._injectGlobals(context, options.globals);
|
|
516
|
+
const result = context.evalCode(code, "sandbox-eval.js");
|
|
517
|
+
if (result.error) {
|
|
518
|
+
const errorMsg = context.dump(result.error);
|
|
519
|
+
result.error.dispose();
|
|
520
|
+
if (timedOut) return {
|
|
521
|
+
ok: false,
|
|
522
|
+
error: "Execution timed out",
|
|
523
|
+
timedOut: true,
|
|
524
|
+
durationMs: Date.now() - startTime,
|
|
525
|
+
logs
|
|
526
|
+
};
|
|
527
|
+
return {
|
|
528
|
+
ok: false,
|
|
529
|
+
error: typeof errorMsg === "object" ? JSON.stringify(errorMsg) : String(errorMsg),
|
|
530
|
+
timedOut: false,
|
|
531
|
+
durationMs: Date.now() - startTime,
|
|
532
|
+
logs
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
const output = unmarshalFromQuickJS(context, result.value);
|
|
536
|
+
result.value.dispose();
|
|
537
|
+
return {
|
|
538
|
+
ok: true,
|
|
539
|
+
output,
|
|
540
|
+
timedOut: false,
|
|
541
|
+
durationMs: Date.now() - startTime,
|
|
542
|
+
logs
|
|
543
|
+
};
|
|
544
|
+
} catch (err) {
|
|
545
|
+
return {
|
|
546
|
+
ok: false,
|
|
547
|
+
error: err instanceof Error ? err.message : String(err),
|
|
548
|
+
timedOut,
|
|
549
|
+
durationMs: Date.now() - startTime,
|
|
550
|
+
logs
|
|
551
|
+
};
|
|
552
|
+
} finally {
|
|
553
|
+
context.dispose();
|
|
554
|
+
runtime.dispose();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Inject a `console` object into the sandbox whose `log` method
|
|
559
|
+
* pushes stringified arguments into the captured `logs` array.
|
|
560
|
+
*/
|
|
561
|
+
_injectConsole(ctx, logs) {
|
|
562
|
+
const consoleObj = ctx.newObject();
|
|
563
|
+
const logFn = ctx.newFunction("log", (...args) => {
|
|
564
|
+
const parts = args.map((a) => {
|
|
565
|
+
const val = unmarshalFromQuickJS(ctx, a);
|
|
566
|
+
return typeof val === "string" ? val : JSON.stringify(val);
|
|
567
|
+
});
|
|
568
|
+
logs.push(parts.join(" "));
|
|
569
|
+
});
|
|
570
|
+
ctx.setProp(consoleObj, "log", logFn);
|
|
571
|
+
ctx.setProp(consoleObj, "warn", logFn);
|
|
572
|
+
ctx.setProp(consoleObj, "error", logFn);
|
|
573
|
+
ctx.setProp(ctx.global, "console", consoleObj);
|
|
574
|
+
logFn.dispose();
|
|
575
|
+
consoleObj.dispose();
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Inject user-supplied global variables into the QuickJS context.
|
|
579
|
+
*/
|
|
580
|
+
_injectGlobals(ctx, globals) {
|
|
581
|
+
for (const [key, value] of Object.entries(globals)) {
|
|
582
|
+
const handle = marshalToQuickJS(ctx, value);
|
|
583
|
+
ctx.setProp(ctx.global, key, handle);
|
|
584
|
+
handle.dispose();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Inject pre-built helper libraries (base64, hex, hash, etc.) into the
|
|
589
|
+
* sandbox global scope by evaluating the helper source code.
|
|
590
|
+
*/
|
|
591
|
+
_injectHelpers(ctx) {
|
|
592
|
+
const result = ctx.evalCode(SANDBOX_HELPER_SOURCE, "sandbox-helpers.js");
|
|
593
|
+
if (result.error) {
|
|
594
|
+
ctx.dump(result.error);
|
|
595
|
+
result.error.dispose();
|
|
596
|
+
} else result.value.dispose();
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Inject the `mcp` bridge object into the sandbox (legacy sync stub).
|
|
600
|
+
*
|
|
601
|
+
* Because QuickJS doesn't natively support async host functions in sync
|
|
602
|
+
* mode, `mcp.call()` and `mcp.listTools()` are exposed as synchronous
|
|
603
|
+
* functions. Bridge calls capture the request; the caller should use
|
|
604
|
+
* `MCPBridge.call()` from the host side for actual async dispatch.
|
|
605
|
+
*
|
|
606
|
+
* For sandbox scripts that need bridge results inline, the host orchestrator
|
|
607
|
+
* (AutoCorrectionLoop or handler) resolves bridge calls between executions.
|
|
608
|
+
*/
|
|
609
|
+
_injectBridge(ctx, bridge, logs) {
|
|
610
|
+
const mcpObj = ctx.newObject();
|
|
611
|
+
const callFn = ctx.newFunction("call", (nameHandle, argsHandle) => {
|
|
612
|
+
const name = ctx.getString(nameHandle);
|
|
613
|
+
const args = ctx.dump(argsHandle) ?? {};
|
|
614
|
+
logs.push(`[mcp.call] ${name}(${JSON.stringify(args)})`);
|
|
615
|
+
return marshalToQuickJS(ctx, {
|
|
616
|
+
pending: true,
|
|
617
|
+
tool: name
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
const listFn = ctx.newFunction("listTools", () => {
|
|
621
|
+
return marshalToQuickJS(ctx, bridge.listAvailableTools());
|
|
622
|
+
});
|
|
623
|
+
ctx.setProp(mcpObj, "call", callFn);
|
|
624
|
+
ctx.setProp(mcpObj, "listTools", listFn);
|
|
625
|
+
ctx.setProp(ctx.global, "mcp", mcpObj);
|
|
626
|
+
callFn.dispose();
|
|
627
|
+
listFn.dispose();
|
|
628
|
+
mcpObj.dispose();
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Inject the `mcp` bridge object for orchestration mode.
|
|
632
|
+
*
|
|
633
|
+
* `mcp.call()` enqueues the request via `bridge.enqueue()` and returns
|
|
634
|
+
* a `{ __bridgeCall: true, callId }` marker. The orchestration loop
|
|
635
|
+
* resolves these calls between rounds and injects results into
|
|
636
|
+
* `__bridgeResults[callId]`.
|
|
637
|
+
*/
|
|
638
|
+
_injectBridgeForOrchestration(ctx, bridge, logs) {
|
|
639
|
+
const mcpObj = ctx.newObject();
|
|
640
|
+
const callFn = ctx.newFunction("call", (nameHandle, argsHandle) => {
|
|
641
|
+
const name = ctx.getString(nameHandle);
|
|
642
|
+
const args = ctx.dump(argsHandle) ?? {};
|
|
643
|
+
try {
|
|
644
|
+
const callId = bridge.enqueue(name, args);
|
|
645
|
+
logs.push(`[mcp.call] enqueued ${name}(${JSON.stringify(args)}) → ${callId}`);
|
|
646
|
+
return marshalToQuickJS(ctx, {
|
|
647
|
+
__bridgeCall: true,
|
|
648
|
+
callId
|
|
649
|
+
});
|
|
650
|
+
} catch (err) {
|
|
651
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
652
|
+
logs.push(`[mcp.call] rejected ${name}: ${errorMsg}`);
|
|
653
|
+
return marshalToQuickJS(ctx, {
|
|
654
|
+
__bridgeCall: false,
|
|
655
|
+
error: errorMsg
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
const listFn = ctx.newFunction("listTools", () => {
|
|
660
|
+
return marshalToQuickJS(ctx, bridge.listAvailableTools());
|
|
661
|
+
});
|
|
662
|
+
ctx.setProp(mcpObj, "call", callFn);
|
|
663
|
+
ctx.setProp(mcpObj, "listTools", listFn);
|
|
664
|
+
ctx.setProp(ctx.global, "mcp", mcpObj);
|
|
665
|
+
callFn.dispose();
|
|
666
|
+
listFn.dispose();
|
|
667
|
+
mcpObj.dispose();
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region src/server/sandbox/MCPBridge.ts
|
|
672
|
+
/**
|
|
673
|
+
* MCPBridge — Allows sandboxed scripts to invoke host MCP tools.
|
|
674
|
+
*
|
|
675
|
+
* The bridge wraps `executeToolWithTracking` and is injected as the
|
|
676
|
+
* `mcp` global inside QuickJS. It validates tool names against the
|
|
677
|
+
* registered tool set before dispatching, preventing arbitrary
|
|
678
|
+
* function calls from the sandbox.
|
|
679
|
+
*/
|
|
680
|
+
var MCPBridge = class {
|
|
681
|
+
ctx;
|
|
682
|
+
allowlist = null;
|
|
683
|
+
pendingCalls = [];
|
|
684
|
+
constructor(ctx) {
|
|
685
|
+
this.ctx = ctx;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Restrict callable tools to a specific set.
|
|
689
|
+
* Pass `null` to allow all registered tools (default).
|
|
690
|
+
*/
|
|
691
|
+
setAllowlist(toolNames) {
|
|
692
|
+
this.allowlist = toolNames ? new Set(toolNames) : null;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Enqueue a tool call request from the sandbox.
|
|
696
|
+
* Returns a unique callId that the sandbox can use to look up the result.
|
|
697
|
+
*/
|
|
698
|
+
enqueue(toolName, args = {}) {
|
|
699
|
+
if (!(this.ctx.selectedTools?.map((t) => t.name) ?? []).includes(toolName)) throw new Error(`Tool "${toolName}" is not a registered MCP tool`);
|
|
700
|
+
if (this.allowlist && !this.allowlist.has(toolName)) throw new Error(`Tool "${toolName}" is not in the sandbox allowlist`);
|
|
701
|
+
const id = randomUUID().slice(0, 8);
|
|
702
|
+
this.pendingCalls.push({
|
|
703
|
+
id,
|
|
704
|
+
toolName,
|
|
705
|
+
args
|
|
706
|
+
});
|
|
707
|
+
return id;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Drain all pending call requests. Returns the queued calls and clears the queue.
|
|
711
|
+
*/
|
|
712
|
+
drainPending() {
|
|
713
|
+
const calls = [...this.pendingCalls];
|
|
714
|
+
this.pendingCalls.length = 0;
|
|
715
|
+
return calls;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Check whether there are pending calls waiting to be resolved.
|
|
719
|
+
*/
|
|
720
|
+
hasPending() {
|
|
721
|
+
return this.pendingCalls.length > 0;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Call a registered MCP tool by name.
|
|
725
|
+
*
|
|
726
|
+
* @throws Error if tool does not exist or is not in the allowlist.
|
|
727
|
+
*/
|
|
728
|
+
async call(toolName, args = {}) {
|
|
729
|
+
if (this.allowlist && !this.allowlist.has(toolName)) throw new Error(`Tool "${toolName}" is not in the sandbox allowlist`);
|
|
730
|
+
if (!this.listAvailableTools().includes(toolName)) throw new Error(`Tool "${toolName}" is not a registered MCP tool`);
|
|
731
|
+
const response = await this.ctx.executeToolWithTracking(toolName, args);
|
|
732
|
+
if (response.content && Array.isArray(response.content)) {
|
|
733
|
+
const textParts = [];
|
|
734
|
+
for (const item of response.content) if (item.type === "text") textParts.push(item.text);
|
|
735
|
+
const combined = textParts.join("\n");
|
|
736
|
+
try {
|
|
737
|
+
return JSON.parse(combined);
|
|
738
|
+
} catch {
|
|
739
|
+
return combined;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return response;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Return the names of all tools callable from the sandbox.
|
|
746
|
+
*/
|
|
747
|
+
listAvailableTools() {
|
|
748
|
+
const allTools = this.ctx.selectedTools.map((t) => t.name);
|
|
749
|
+
if (this.allowlist) return allTools.filter((n) => this.allowlist.has(n));
|
|
750
|
+
return allTools;
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
//#endregion
|
|
754
|
+
//#region src/server/sandbox/SessionScratchpad.ts
|
|
755
|
+
/**
|
|
756
|
+
* SessionScratchpad — Per-session key/value store for sandbox scripts.
|
|
757
|
+
*
|
|
758
|
+
* Values persist across script executions within the same session.
|
|
759
|
+
* All values are serialized/deserialized via JSON to prevent live
|
|
760
|
+
* object references leaking across sandbox contexts.
|
|
761
|
+
*/
|
|
762
|
+
var SessionScratchpad = class {
|
|
763
|
+
store = /* @__PURE__ */ new Map();
|
|
764
|
+
/**
|
|
765
|
+
* Set a value for a key in a session's scratchpad.
|
|
766
|
+
* Value is JSON-serialized for safety.
|
|
767
|
+
*/
|
|
768
|
+
set(sessionId, key, value) {
|
|
769
|
+
let session = this.store.get(sessionId);
|
|
770
|
+
if (!session) {
|
|
771
|
+
session = /* @__PURE__ */ new Map();
|
|
772
|
+
this.store.set(sessionId, session);
|
|
773
|
+
}
|
|
774
|
+
session.set(key, JSON.stringify(value));
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Get a value by key from a session's scratchpad.
|
|
778
|
+
* Returns undefined if key doesn't exist.
|
|
779
|
+
*/
|
|
780
|
+
get(sessionId, key) {
|
|
781
|
+
const session = this.store.get(sessionId);
|
|
782
|
+
if (!session) return void 0;
|
|
783
|
+
const raw = session.get(key);
|
|
784
|
+
if (raw === void 0) return void 0;
|
|
785
|
+
try {
|
|
786
|
+
return JSON.parse(raw);
|
|
787
|
+
} catch {
|
|
788
|
+
return raw;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Get all key/value pairs for a session.
|
|
793
|
+
*/
|
|
794
|
+
getAll(sessionId) {
|
|
795
|
+
const session = this.store.get(sessionId);
|
|
796
|
+
if (!session) return {};
|
|
797
|
+
const result = {};
|
|
798
|
+
for (const [k, v] of session) try {
|
|
799
|
+
result[k] = JSON.parse(v);
|
|
800
|
+
} catch {
|
|
801
|
+
result[k] = v;
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Get all keys for a session.
|
|
807
|
+
*/
|
|
808
|
+
keys(sessionId) {
|
|
809
|
+
const session = this.store.get(sessionId);
|
|
810
|
+
if (!session) return [];
|
|
811
|
+
return Array.from(session.keys());
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Clear all state for a specific session.
|
|
815
|
+
*/
|
|
816
|
+
clear(sessionId) {
|
|
817
|
+
this.store.delete(sessionId);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Clear all sessions (server shutdown).
|
|
821
|
+
*/
|
|
822
|
+
clearAll() {
|
|
823
|
+
this.store.clear();
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/server/sandbox/AutoCorrectionLoop.ts
|
|
828
|
+
/**
|
|
829
|
+
* Execute code in the sandbox with automatic retry on error.
|
|
830
|
+
*
|
|
831
|
+
* @param sandbox - QuickJSSandbox instance
|
|
832
|
+
* @param code - JavaScript source to execute
|
|
833
|
+
* @param options - Sandbox execution options
|
|
834
|
+
* @param maxRetries - Maximum number of retries (default 2)
|
|
835
|
+
* @returns Result from the final execution attempt
|
|
836
|
+
*/
|
|
837
|
+
async function executeWithRetry(sandbox, code, options = {}, maxRetries = 2) {
|
|
838
|
+
let lastResult = null;
|
|
839
|
+
let currentCode = code;
|
|
840
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
841
|
+
lastResult = await sandbox.execute(currentCode, options);
|
|
842
|
+
if (lastResult.ok) return {
|
|
843
|
+
...lastResult,
|
|
844
|
+
retryCount: attempt
|
|
845
|
+
};
|
|
846
|
+
if (lastResult.timedOut) return {
|
|
847
|
+
...lastResult,
|
|
848
|
+
retryCount: attempt
|
|
849
|
+
};
|
|
850
|
+
if (attempt < maxRetries) currentCode = `/* Previous error (attempt ${attempt + 1}): ${lastResult.error ?? "unknown error"} */\n${code}`;
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
...lastResult,
|
|
854
|
+
retryCount: maxRetries
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
//#endregion
|
|
858
|
+
//#region src/server/domains/sandbox/handlers.ts
|
|
859
|
+
/**
|
|
860
|
+
* SandboxToolHandlers — Handles sandbox domain MCP tool calls.
|
|
861
|
+
*/
|
|
862
|
+
var SandboxToolHandlers = class {
|
|
863
|
+
ctx;
|
|
864
|
+
scratchpad = new SessionScratchpad();
|
|
865
|
+
constructor(ctx) {
|
|
866
|
+
this.ctx = ctx;
|
|
867
|
+
}
|
|
868
|
+
async handleExecuteSandboxScript(args) {
|
|
869
|
+
const code = args.code;
|
|
870
|
+
const sessionId = args.sessionId ?? void 0;
|
|
871
|
+
const timeoutMs = args.timeoutMs ?? void 0;
|
|
872
|
+
const autoCorrect = args.autoCorrect ?? false;
|
|
873
|
+
if (!code || typeof code !== "string") return { content: [{
|
|
874
|
+
type: "text",
|
|
875
|
+
text: JSON.stringify({
|
|
876
|
+
ok: false,
|
|
877
|
+
error: "code parameter is required"
|
|
878
|
+
})
|
|
879
|
+
}] };
|
|
880
|
+
const sandbox = new QuickJSSandbox();
|
|
881
|
+
const bridge = new MCPBridge(this.ctx);
|
|
882
|
+
sandbox.setBridge(bridge);
|
|
883
|
+
const options = {};
|
|
884
|
+
if (timeoutMs !== void 0) {
|
|
885
|
+
const MAX_TIMEOUT = SANDBOX_MAX_TIMEOUT_MS;
|
|
886
|
+
options.timeoutMs = Math.min(Math.max(1, Number.isFinite(timeoutMs) ? timeoutMs : 0), MAX_TIMEOUT);
|
|
887
|
+
}
|
|
888
|
+
if (sessionId) {
|
|
889
|
+
options.sessionId = sessionId;
|
|
890
|
+
const scratchpadState = this.scratchpad.getAll(sessionId);
|
|
891
|
+
options.globals = {
|
|
892
|
+
...options.globals,
|
|
893
|
+
__scratchpad: scratchpadState
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
let result;
|
|
897
|
+
if (autoCorrect) result = await executeWithRetry(sandbox, code, options);
|
|
898
|
+
else result = await sandbox.execute(code, options);
|
|
899
|
+
if (sessionId && result.ok && result.output && typeof result.output === "object") {
|
|
900
|
+
const output = result.output;
|
|
901
|
+
if (output.__scratchpad && typeof output.__scratchpad === "object") for (const [k, v] of Object.entries(output.__scratchpad)) this.scratchpad.set(sessionId, k, v);
|
|
902
|
+
}
|
|
903
|
+
return { content: [{
|
|
904
|
+
type: "text",
|
|
905
|
+
text: [
|
|
906
|
+
`**Status:** ${result.ok ? "✓ Success" : "✗ Failed"}`,
|
|
907
|
+
result.timedOut ? "**Timed out:** yes" : "",
|
|
908
|
+
`**Duration:** ${result.durationMs}ms`,
|
|
909
|
+
result.logs.length > 0 ? `**Console output:**\n\`\`\`\n${result.logs.join("\n")}\n\`\`\`` : "",
|
|
910
|
+
result.output !== void 0 ? `**Result:** ${JSON.stringify(result.output)}` : "",
|
|
911
|
+
result.error ? `**Error:** ${result.error}` : ""
|
|
912
|
+
].filter(Boolean).join("\n")
|
|
913
|
+
}] };
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
//#endregion
|
|
917
|
+
export { SandboxToolHandlers };
|