@papicandela/mcx-core 0.2.2 → 0.2.5
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/.turbo/turbo-build.log +2 -2
- package/dist/index.js +302 -108
- package/package.json +8 -2
- package/src/adapter.ts +8 -4
- package/src/config.ts +6 -0
- package/src/executor.ts +5 -3
- package/src/sandbox/analyzer/analyzer.test.ts +3 -3
- package/src/sandbox/analyzer/analyzer.ts +2 -2
- package/src/sandbox/analyzer/rules/no-dangerous-globals.ts +135 -33
- package/src/sandbox/analyzer/rules/no-infinite-loop.ts +40 -13
- package/src/sandbox/bun-worker.ts +67 -11
- package/src/sandbox/network-policy.ts +110 -54
- package/src/type-generator.ts +23 -9
package/dist/index.js
CHANGED
|
@@ -32,80 +32,132 @@ function generateNetworkIsolationCode(policy) {
|
|
|
32
32
|
if (policy.mode === "blocked") {
|
|
33
33
|
return `
|
|
34
34
|
// Network isolation: BLOCKED
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
35
|
+
(function() {
|
|
36
|
+
const blockedFetch = async function() {
|
|
37
|
+
throw new Error('Network access is blocked in sandbox. Use adapters instead.');
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
40
|
+
value: blockedFetch,
|
|
41
|
+
writable: false,
|
|
42
|
+
configurable: false
|
|
43
|
+
});
|
|
44
|
+
})();
|
|
39
45
|
|
|
40
46
|
// Block XMLHttpRequest
|
|
41
|
-
globalThis
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
47
|
+
Object.defineProperty(globalThis, 'XMLHttpRequest', {
|
|
48
|
+
value: class {
|
|
49
|
+
constructor() {
|
|
50
|
+
throw new Error('XMLHttpRequest is blocked in sandbox.');
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
writable: false,
|
|
54
|
+
configurable: false
|
|
55
|
+
});
|
|
46
56
|
|
|
47
57
|
// Block WebSocket
|
|
48
|
-
globalThis
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
58
|
+
Object.defineProperty(globalThis, 'WebSocket', {
|
|
59
|
+
value: class {
|
|
60
|
+
constructor() {
|
|
61
|
+
throw new Error('WebSocket is blocked in sandbox.');
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
writable: false,
|
|
65
|
+
configurable: false
|
|
66
|
+
});
|
|
53
67
|
|
|
54
68
|
// Block EventSource (SSE)
|
|
55
|
-
globalThis
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
69
|
+
Object.defineProperty(globalThis, 'EventSource', {
|
|
70
|
+
value: class {
|
|
71
|
+
constructor() {
|
|
72
|
+
throw new Error('EventSource is blocked in sandbox.');
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
writable: false,
|
|
76
|
+
configurable: false
|
|
77
|
+
});
|
|
60
78
|
`;
|
|
61
79
|
}
|
|
62
80
|
const domainsJson = JSON.stringify(policy.domains);
|
|
63
81
|
return `
|
|
64
82
|
// Network isolation: ALLOWED (whitelist)
|
|
65
|
-
|
|
66
|
-
const
|
|
83
|
+
(function() {
|
|
84
|
+
const _domains = ${domainsJson};
|
|
85
|
+
const _real_fetch = globalThis.fetch;
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return __allowed_domains.some(d => hostname === d || hostname.endsWith('.' + d));
|
|
72
|
-
} catch {
|
|
73
|
-
return false;
|
|
87
|
+
// Block private/link-local IPs to prevent DNS rebinding attacks
|
|
88
|
+
function _isPrivateIp(hostname) {
|
|
89
|
+
return /^(localhost|127\\.|10\\.|192\\.168\\.|172\\.(1[6-9]|2\\d|3[01])\\.|169\\.254\\.|\\[::1\\]|\\[fc|\\[fd)/.test(hostname);
|
|
74
90
|
}
|
|
75
|
-
}
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
92
|
+
function _isUrlAllowed(url) {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = new URL(url);
|
|
95
|
+
// Only allow http/https protocols
|
|
96
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return false;
|
|
97
|
+
const hostname = parsed.hostname;
|
|
98
|
+
if (!hostname || _isPrivateIp(hostname)) return false;
|
|
99
|
+
return _domains.some(d => d && (hostname === d || hostname.endsWith('.' + d)));
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
82
103
|
}
|
|
83
|
-
|
|
84
|
-
|
|
104
|
+
|
|
105
|
+
const whitelistedFetch = async function(url, options) {
|
|
106
|
+
// Safely extract URL string from various input types
|
|
107
|
+
let urlStr;
|
|
108
|
+
try {
|
|
109
|
+
if (typeof url === 'string') urlStr = url;
|
|
110
|
+
else if (url instanceof URL) urlStr = url.toString();
|
|
111
|
+
else if (url && typeof url.url === 'string') urlStr = url.url;
|
|
112
|
+
else throw new Error('Invalid URL type');
|
|
113
|
+
} catch {
|
|
114
|
+
throw new Error('Network access blocked: could not determine request URL.');
|
|
115
|
+
}
|
|
116
|
+
if (!_isUrlAllowed(urlStr)) {
|
|
117
|
+
throw new Error('Network access blocked: domain not in allowed list.');
|
|
118
|
+
}
|
|
119
|
+
return _real_fetch(url, options);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
123
|
+
value: whitelistedFetch,
|
|
124
|
+
writable: false,
|
|
125
|
+
configurable: false
|
|
126
|
+
});
|
|
127
|
+
})();
|
|
85
128
|
|
|
86
129
|
// Block XMLHttpRequest (not easily whitelistable)
|
|
87
|
-
globalThis
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
130
|
+
Object.defineProperty(globalThis, 'XMLHttpRequest', {
|
|
131
|
+
value: class {
|
|
132
|
+
constructor() {
|
|
133
|
+
throw new Error('XMLHttpRequest is blocked. Use fetch() with allowed domains.');
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
writable: false,
|
|
137
|
+
configurable: false
|
|
138
|
+
});
|
|
92
139
|
|
|
93
|
-
// Block WebSocket
|
|
94
|
-
globalThis
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
throw new Error('WebSocket
|
|
140
|
+
// Block WebSocket - opaque error to prevent allowlist enumeration
|
|
141
|
+
Object.defineProperty(globalThis, 'WebSocket', {
|
|
142
|
+
value: class {
|
|
143
|
+
constructor() {
|
|
144
|
+
throw new Error('WebSocket is not supported in sandbox.');
|
|
98
145
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
146
|
+
},
|
|
147
|
+
writable: false,
|
|
148
|
+
configurable: false
|
|
149
|
+
});
|
|
102
150
|
|
|
103
151
|
// Block EventSource
|
|
104
|
-
globalThis
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
152
|
+
Object.defineProperty(globalThis, 'EventSource', {
|
|
153
|
+
value: class {
|
|
154
|
+
constructor() {
|
|
155
|
+
throw new Error('EventSource is blocked in sandbox.');
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
writable: false,
|
|
159
|
+
configurable: false
|
|
160
|
+
});
|
|
109
161
|
`;
|
|
110
162
|
}
|
|
111
163
|
|
|
@@ -5587,24 +5639,31 @@ function isLiteralTrue(node) {
|
|
|
5587
5639
|
return false;
|
|
5588
5640
|
return node.type === "Literal" && node.value === true;
|
|
5589
5641
|
}
|
|
5590
|
-
function
|
|
5642
|
+
function hasExitStatement(node) {
|
|
5591
5643
|
if (node.type === "BreakStatement")
|
|
5592
5644
|
return true;
|
|
5645
|
+
if (node.type === "ReturnStatement")
|
|
5646
|
+
return true;
|
|
5647
|
+
if (node.type === "ThrowStatement")
|
|
5648
|
+
return true;
|
|
5593
5649
|
if (node.type === "WhileStatement" || node.type === "ForStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement" || node.type === "DoWhileStatement" || node.type === "SwitchStatement") {
|
|
5594
5650
|
return false;
|
|
5595
5651
|
}
|
|
5652
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
5653
|
+
return false;
|
|
5654
|
+
}
|
|
5596
5655
|
for (const key of Object.keys(node)) {
|
|
5597
5656
|
const child = node[key];
|
|
5598
5657
|
if (child && typeof child === "object") {
|
|
5599
5658
|
if (Array.isArray(child)) {
|
|
5600
5659
|
for (const item of child) {
|
|
5601
5660
|
if (item && typeof item === "object" && "type" in item) {
|
|
5602
|
-
if (
|
|
5661
|
+
if (hasExitStatement(item))
|
|
5603
5662
|
return true;
|
|
5604
5663
|
}
|
|
5605
5664
|
}
|
|
5606
5665
|
} else if ("type" in child) {
|
|
5607
|
-
if (
|
|
5666
|
+
if (hasExitStatement(child))
|
|
5608
5667
|
return true;
|
|
5609
5668
|
}
|
|
5610
5669
|
}
|
|
@@ -5614,25 +5673,35 @@ function hasBreak(node) {
|
|
|
5614
5673
|
var rule = {
|
|
5615
5674
|
name: "no-infinite-loop",
|
|
5616
5675
|
severity: "error",
|
|
5617
|
-
description: "Disallow infinite loops without
|
|
5618
|
-
visits: ["WhileStatement", "ForStatement"],
|
|
5676
|
+
description: "Disallow infinite loops without exit statements",
|
|
5677
|
+
visits: ["WhileStatement", "ForStatement", "DoWhileStatement"],
|
|
5619
5678
|
visitors: {
|
|
5620
5679
|
WhileStatement(node, context) {
|
|
5621
5680
|
const whileNode = node;
|
|
5622
|
-
if (isLiteralTrue(whileNode.test) && !
|
|
5681
|
+
if (isLiteralTrue(whileNode.test) && !hasExitStatement(whileNode.body)) {
|
|
5623
5682
|
context.report({
|
|
5624
5683
|
severity: "error",
|
|
5625
|
-
message: "Infinite loop: while(true) without break",
|
|
5684
|
+
message: "Infinite loop: while(true) without break/return/throw",
|
|
5626
5685
|
line: context.getLine(node)
|
|
5627
5686
|
});
|
|
5628
5687
|
}
|
|
5629
5688
|
},
|
|
5630
5689
|
ForStatement(node, context) {
|
|
5631
5690
|
const forNode = node;
|
|
5632
|
-
if (!forNode.test && !
|
|
5691
|
+
if (!forNode.test && !hasExitStatement(forNode.body)) {
|
|
5633
5692
|
context.report({
|
|
5634
5693
|
severity: "error",
|
|
5635
|
-
message: "Infinite loop: for(;;) without break",
|
|
5694
|
+
message: "Infinite loop: for(;;) without break/return/throw",
|
|
5695
|
+
line: context.getLine(node)
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5698
|
+
},
|
|
5699
|
+
DoWhileStatement(node, context) {
|
|
5700
|
+
const doWhileNode = node;
|
|
5701
|
+
if (isLiteralTrue(doWhileNode.test) && !hasExitStatement(doWhileNode.body)) {
|
|
5702
|
+
context.report({
|
|
5703
|
+
severity: "error",
|
|
5704
|
+
message: "Infinite loop: do...while(true) without break/return/throw",
|
|
5636
5705
|
line: context.getLine(node)
|
|
5637
5706
|
});
|
|
5638
5707
|
}
|
|
@@ -5938,49 +6007,100 @@ var EVAL_NAME = "ev" + "al";
|
|
|
5938
6007
|
var FUNC_CONSTRUCTOR = "Func" + "tion";
|
|
5939
6008
|
var REQUIRE_NAME = "req" + "uire";
|
|
5940
6009
|
var PROCESS_NAME = "pro" + "cess";
|
|
6010
|
+
var DANGEROUS_GLOBALS = ["globalThis", "self", "window"];
|
|
6011
|
+
function isConstructorOnFunction(node) {
|
|
6012
|
+
const prop = node.property;
|
|
6013
|
+
const isConstructorAccess = !node.computed && prop.type === "Identifier" && prop.name === "constructor" || node.computed && prop.type === "Literal" && prop.value === "constructor";
|
|
6014
|
+
if (!isConstructorAccess)
|
|
6015
|
+
return false;
|
|
6016
|
+
const obj = node.object;
|
|
6017
|
+
if (obj.type === "FunctionExpression" || obj.type === "ArrowFunctionExpression") {
|
|
6018
|
+
return true;
|
|
6019
|
+
}
|
|
6020
|
+
if (obj.type === "CallExpression") {
|
|
6021
|
+
const call = obj;
|
|
6022
|
+
if (call.callee.type === "MemberExpression") {
|
|
6023
|
+
const callee = call.callee;
|
|
6024
|
+
if (callee.object.type === "Identifier" && callee.object.name === "Object" && callee.property.type === "Identifier" && callee.property.name === "getPrototypeOf") {
|
|
6025
|
+
return true;
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
return false;
|
|
6030
|
+
}
|
|
5941
6031
|
var rule5 = {
|
|
5942
6032
|
name: "no-dangerous-globals",
|
|
5943
6033
|
severity: "warn",
|
|
5944
|
-
description: "
|
|
5945
|
-
visits: ["CallExpression", "NewExpression", "
|
|
6034
|
+
description: "Block dangerous globals that could escape sandbox",
|
|
6035
|
+
visits: ["CallExpression", "NewExpression", "MemberExpression"],
|
|
5946
6036
|
visitors: {
|
|
5947
6037
|
CallExpression(node, context) {
|
|
5948
6038
|
const callExpr = node;
|
|
5949
|
-
|
|
6039
|
+
const calleeName = callExpr.callee.type === "Identifier" ? callExpr.callee.name : null;
|
|
6040
|
+
if (calleeName === EVAL_NAME) {
|
|
5950
6041
|
context.report({
|
|
5951
|
-
severity: "
|
|
5952
|
-
message: `${EVAL_NAME}() is
|
|
6042
|
+
severity: "error",
|
|
6043
|
+
message: `${EVAL_NAME}() is blocked in sandbox - potential code injection`,
|
|
5953
6044
|
line: context.getLine(node)
|
|
5954
6045
|
});
|
|
5955
6046
|
return;
|
|
5956
6047
|
}
|
|
5957
|
-
if (
|
|
6048
|
+
if (calleeName === FUNC_CONSTRUCTOR) {
|
|
5958
6049
|
context.report({
|
|
5959
|
-
severity: "
|
|
5960
|
-
message: `${
|
|
6050
|
+
severity: "error",
|
|
6051
|
+
message: `${FUNC_CONSTRUCTOR}() is blocked in sandbox - potential code injection`,
|
|
5961
6052
|
line: context.getLine(node)
|
|
5962
6053
|
});
|
|
5963
6054
|
return;
|
|
5964
6055
|
}
|
|
5965
|
-
|
|
5966
|
-
NewExpression(node, context) {
|
|
5967
|
-
const newExpr = node;
|
|
5968
|
-
if (newExpr.callee.type === "Identifier" && newExpr.callee.name === FUNC_CONSTRUCTOR) {
|
|
6056
|
+
if (calleeName === REQUIRE_NAME) {
|
|
5969
6057
|
context.report({
|
|
5970
|
-
severity: "
|
|
5971
|
-
message: `${
|
|
6058
|
+
severity: "error",
|
|
6059
|
+
message: `${REQUIRE_NAME}() is blocked in sandbox - could access dangerous modules`,
|
|
5972
6060
|
line: context.getLine(node)
|
|
5973
6061
|
});
|
|
6062
|
+
return;
|
|
6063
|
+
}
|
|
6064
|
+
if (callExpr.callee.type === "MemberExpression") {
|
|
6065
|
+
if (isConstructorOnFunction(callExpr.callee)) {
|
|
6066
|
+
context.report({
|
|
6067
|
+
severity: "error",
|
|
6068
|
+
message: `Accessing .constructor on functions is blocked - potential sandbox escape`,
|
|
6069
|
+
line: context.getLine(node)
|
|
6070
|
+
});
|
|
6071
|
+
return;
|
|
6072
|
+
}
|
|
5974
6073
|
}
|
|
5975
6074
|
},
|
|
5976
|
-
|
|
5977
|
-
const
|
|
5978
|
-
if (
|
|
6075
|
+
NewExpression(node, context) {
|
|
6076
|
+
const newExpr = node;
|
|
6077
|
+
if (newExpr.callee.type === "Identifier" && newExpr.callee.name === FUNC_CONSTRUCTOR) {
|
|
5979
6078
|
context.report({
|
|
5980
|
-
severity: "
|
|
5981
|
-
message:
|
|
6079
|
+
severity: "error",
|
|
6080
|
+
message: `${FUNC_CONSTRUCTOR} constructor is blocked in sandbox - potential code injection`,
|
|
5982
6081
|
line: context.getLine(node)
|
|
5983
6082
|
});
|
|
6083
|
+
return;
|
|
6084
|
+
}
|
|
6085
|
+
if (newExpr.callee.type === "MemberExpression") {
|
|
6086
|
+
if (isConstructorOnFunction(newExpr.callee)) {
|
|
6087
|
+
context.report({
|
|
6088
|
+
severity: "error",
|
|
6089
|
+
message: `Accessing .constructor on functions is blocked - potential sandbox escape`,
|
|
6090
|
+
line: context.getLine(node)
|
|
6091
|
+
});
|
|
6092
|
+
return;
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
if (newExpr.callee.type === "MemberExpression") {
|
|
6096
|
+
const member = newExpr.callee;
|
|
6097
|
+
if (member.object.type === "Identifier" && DANGEROUS_GLOBALS.includes(member.object.name) && member.property.type === "Identifier" && member.property.name === FUNC_CONSTRUCTOR) {
|
|
6098
|
+
context.report({
|
|
6099
|
+
severity: "error",
|
|
6100
|
+
message: `${FUNC_CONSTRUCTOR} constructor is blocked in sandbox - potential code injection`,
|
|
6101
|
+
line: context.getLine(node)
|
|
6102
|
+
});
|
|
6103
|
+
}
|
|
5984
6104
|
}
|
|
5985
6105
|
},
|
|
5986
6106
|
MemberExpression(node, context) {
|
|
@@ -5991,6 +6111,14 @@ var rule5 = {
|
|
|
5991
6111
|
message: `'${PROCESS_NAME}' is not available in sandbox`,
|
|
5992
6112
|
line: context.getLine(node)
|
|
5993
6113
|
});
|
|
6114
|
+
return;
|
|
6115
|
+
}
|
|
6116
|
+
if (memberExpr.object.type === "Identifier" && DANGEROUS_GLOBALS.includes(memberExpr.object.name) && memberExpr.property.type === "Identifier" && memberExpr.property.name === FUNC_CONSTRUCTOR) {
|
|
6117
|
+
context.report({
|
|
6118
|
+
severity: "error",
|
|
6119
|
+
message: `Accessing ${FUNC_CONSTRUCTOR} via globals is blocked - potential sandbox escape`,
|
|
6120
|
+
line: context.getLine(node)
|
|
6121
|
+
});
|
|
5994
6122
|
}
|
|
5995
6123
|
}
|
|
5996
6124
|
}
|
|
@@ -6130,7 +6258,7 @@ function analyze(code, config = {}) {
|
|
|
6130
6258
|
const errors = findings.filter((f) => f.severity === "error");
|
|
6131
6259
|
const elapsed = performance.now() - start;
|
|
6132
6260
|
if (elapsed > 50) {
|
|
6133
|
-
console.
|
|
6261
|
+
console.error(`[mcx-analyzer] Exceeded 50ms budget: ${elapsed.toFixed(1)}ms`);
|
|
6134
6262
|
}
|
|
6135
6263
|
return { warnings, errors, elapsed };
|
|
6136
6264
|
}
|
|
@@ -6210,14 +6338,17 @@ class BunWorkerSandbox {
|
|
|
6210
6338
|
const url = URL.createObjectURL(blob);
|
|
6211
6339
|
const worker = new Worker(url);
|
|
6212
6340
|
let resolved = false;
|
|
6341
|
+
let timeoutId;
|
|
6213
6342
|
const cleanup = () => {
|
|
6214
6343
|
if (!resolved) {
|
|
6215
6344
|
resolved = true;
|
|
6345
|
+
if (timeoutId)
|
|
6346
|
+
clearTimeout(timeoutId);
|
|
6216
6347
|
worker.terminate();
|
|
6217
6348
|
URL.revokeObjectURL(url);
|
|
6218
6349
|
}
|
|
6219
6350
|
};
|
|
6220
|
-
|
|
6351
|
+
timeoutId = setTimeout(() => {
|
|
6221
6352
|
if (!resolved) {
|
|
6222
6353
|
cleanup();
|
|
6223
6354
|
resolve({
|
|
@@ -6229,6 +6360,8 @@ class BunWorkerSandbox {
|
|
|
6229
6360
|
}
|
|
6230
6361
|
}, this.config.timeout);
|
|
6231
6362
|
worker.onmessage = async (event) => {
|
|
6363
|
+
if (resolved)
|
|
6364
|
+
return;
|
|
6232
6365
|
const { type, ...data2 } = event.data;
|
|
6233
6366
|
if (type === "ready") {
|
|
6234
6367
|
worker.postMessage({ type: "execute", data: { code: normalizedCode } });
|
|
@@ -6246,7 +6379,6 @@ class BunWorkerSandbox {
|
|
|
6246
6379
|
worker.postMessage({ type: "adapter_result", data: { id, error } });
|
|
6247
6380
|
}
|
|
6248
6381
|
} else if (type === "result") {
|
|
6249
|
-
clearTimeout(timeoutId);
|
|
6250
6382
|
cleanup();
|
|
6251
6383
|
resolve({
|
|
6252
6384
|
success: data2.success,
|
|
@@ -6258,7 +6390,6 @@ class BunWorkerSandbox {
|
|
|
6258
6390
|
}
|
|
6259
6391
|
};
|
|
6260
6392
|
worker.onerror = (error) => {
|
|
6261
|
-
clearTimeout(timeoutId);
|
|
6262
6393
|
cleanup();
|
|
6263
6394
|
resolve({
|
|
6264
6395
|
success: false,
|
|
@@ -6287,11 +6418,27 @@ class BunWorkerSandbox {
|
|
|
6287
6418
|
const pendingCalls = new Map();
|
|
6288
6419
|
let callId = 0;
|
|
6289
6420
|
|
|
6421
|
+
// Safe stringify that handles BigInt and circular refs
|
|
6422
|
+
const safeStr = (val) => {
|
|
6423
|
+
if (typeof val !== 'object' || val === null) return String(val);
|
|
6424
|
+
try {
|
|
6425
|
+
const seen = new WeakSet();
|
|
6426
|
+
return JSON.stringify(val, (k, v) => {
|
|
6427
|
+
if (typeof v === 'bigint') return v.toString() + 'n';
|
|
6428
|
+
if (typeof v === 'object' && v !== null) {
|
|
6429
|
+
if (seen.has(v)) return '[Circular]';
|
|
6430
|
+
seen.add(v);
|
|
6431
|
+
}
|
|
6432
|
+
return v;
|
|
6433
|
+
});
|
|
6434
|
+
} catch { return String(val); }
|
|
6435
|
+
};
|
|
6436
|
+
|
|
6290
6437
|
const console = {
|
|
6291
|
-
log: (...args) => logs.push(args.map(
|
|
6292
|
-
warn: (...args) => logs.push('[WARN] ' + args.map(
|
|
6293
|
-
error: (...args) => logs.push('[ERROR] ' + args.map(
|
|
6294
|
-
info: (...args) => logs.push('[INFO] ' + args.map(
|
|
6438
|
+
log: (...args) => logs.push(args.map(safeStr).join(' ')),
|
|
6439
|
+
warn: (...args) => logs.push('[WARN] ' + args.map(safeStr).join(' ')),
|
|
6440
|
+
error: (...args) => logs.push('[ERROR] ' + args.map(safeStr).join(' ')),
|
|
6441
|
+
info: (...args) => logs.push('[INFO] ' + args.map(safeStr).join(' ')),
|
|
6295
6442
|
};
|
|
6296
6443
|
globalThis.console = console;
|
|
6297
6444
|
|
|
@@ -6341,21 +6488,41 @@ class BunWorkerSandbox {
|
|
|
6341
6488
|
return arr.slice(0, n);
|
|
6342
6489
|
};
|
|
6343
6490
|
|
|
6491
|
+
// SECURITY: Reserved keys that must not be overwritten by user-provided variables/globals
|
|
6492
|
+
const RESERVED_KEYS = new Set([
|
|
6493
|
+
'onmessage', 'postMessage', 'close', 'terminate', 'self',
|
|
6494
|
+
'constructor', 'prototype', '__proto__',
|
|
6495
|
+
'pendingCalls', 'callId', 'logs', 'console', 'adapters',
|
|
6496
|
+
'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',
|
|
6497
|
+
'pick', 'table', 'count', 'sum', 'first', 'safeStr'
|
|
6498
|
+
]);
|
|
6499
|
+
|
|
6344
6500
|
self.onmessage = async (event) => {
|
|
6345
6501
|
const { type, data } = event.data;
|
|
6346
6502
|
|
|
6347
6503
|
if (type === 'init') {
|
|
6348
6504
|
const { variables, adapterMethods, globals } = data;
|
|
6349
6505
|
|
|
6506
|
+
// Inject user variables (skip reserved keys to prevent internal state corruption)
|
|
6350
6507
|
for (const [key, value] of Object.entries(variables || {})) {
|
|
6508
|
+
if (RESERVED_KEYS.has(key)) {
|
|
6509
|
+
logs.push('[WARN] Skipped reserved variable key: ' + key);
|
|
6510
|
+
continue;
|
|
6511
|
+
}
|
|
6351
6512
|
globalThis[key] = value;
|
|
6352
6513
|
}
|
|
6353
6514
|
|
|
6515
|
+
// Inject sandbox globals (skip reserved keys)
|
|
6354
6516
|
for (const [key, value] of Object.entries(globals || {})) {
|
|
6517
|
+
if (RESERVED_KEYS.has(key)) {
|
|
6518
|
+
logs.push('[WARN] Skipped reserved globals key: ' + key);
|
|
6519
|
+
continue;
|
|
6520
|
+
}
|
|
6355
6521
|
globalThis[key] = value;
|
|
6356
6522
|
}
|
|
6357
6523
|
|
|
6358
|
-
|
|
6524
|
+
// Create adapter proxies and freeze them to prevent user code modification
|
|
6525
|
+
const adaptersObj = {};
|
|
6359
6526
|
for (const [adapterName, methods] of Object.entries(adapterMethods)) {
|
|
6360
6527
|
const adapterObj = {};
|
|
6361
6528
|
for (const methodName of methods) {
|
|
@@ -6373,9 +6540,23 @@ class BunWorkerSandbox {
|
|
|
6373
6540
|
});
|
|
6374
6541
|
};
|
|
6375
6542
|
}
|
|
6376
|
-
|
|
6377
|
-
|
|
6543
|
+
// Freeze individual adapter to prevent method tampering
|
|
6544
|
+
Object.freeze(adapterObj);
|
|
6545
|
+
adaptersObj[adapterName] = adapterObj;
|
|
6546
|
+
// Also expose at top level but as non-writable
|
|
6547
|
+
Object.defineProperty(globalThis, adapterName, {
|
|
6548
|
+
value: adapterObj,
|
|
6549
|
+
writable: false,
|
|
6550
|
+
configurable: false
|
|
6551
|
+
});
|
|
6378
6552
|
}
|
|
6553
|
+
// Freeze the adapters namespace and make it non-writable
|
|
6554
|
+
Object.freeze(adaptersObj);
|
|
6555
|
+
Object.defineProperty(globalThis, 'adapters', {
|
|
6556
|
+
value: adaptersObj,
|
|
6557
|
+
writable: false,
|
|
6558
|
+
configurable: false
|
|
6559
|
+
});
|
|
6379
6560
|
|
|
6380
6561
|
self.postMessage({ type: 'ready' });
|
|
6381
6562
|
}
|
|
@@ -6398,10 +6579,12 @@ class BunWorkerSandbox {
|
|
|
6398
6579
|
const result = await fn();
|
|
6399
6580
|
self.postMessage({ type: 'result', success: true, value: result, logs });
|
|
6400
6581
|
} catch (err) {
|
|
6582
|
+
// Truncate stack to 5 lines to prevent context bloat
|
|
6583
|
+
const stack = err.stack ? err.stack.split('\\n').slice(0, 5).join('\\n') : undefined;
|
|
6401
6584
|
self.postMessage({
|
|
6402
6585
|
type: 'result',
|
|
6403
6586
|
success: false,
|
|
6404
|
-
error: { name: err.name, message: err.message, stack
|
|
6587
|
+
error: { name: err.name, message: err.message, stack },
|
|
6405
6588
|
logs
|
|
6406
6589
|
});
|
|
6407
6590
|
}
|
|
@@ -10411,12 +10594,12 @@ function createParameterValidator(params) {
|
|
|
10411
10594
|
default:
|
|
10412
10595
|
schema = exports_external.unknown();
|
|
10413
10596
|
}
|
|
10414
|
-
if (param.default !== undefined) {
|
|
10415
|
-
schema = schema.default(param.default);
|
|
10416
|
-
}
|
|
10417
10597
|
if (!param.required) {
|
|
10418
10598
|
schema = schema.optional();
|
|
10419
10599
|
}
|
|
10600
|
+
if (param.default !== undefined) {
|
|
10601
|
+
schema = schema.default(param.default);
|
|
10602
|
+
}
|
|
10420
10603
|
shape[param.name] = schema;
|
|
10421
10604
|
}
|
|
10422
10605
|
return exports_external.object(shape);
|
|
@@ -10559,7 +10742,10 @@ var DEFAULT_CONFIG2 = {
|
|
|
10559
10742
|
timeout: 5000,
|
|
10560
10743
|
memoryLimit: 128,
|
|
10561
10744
|
allowAsync: true,
|
|
10562
|
-
globals: {}
|
|
10745
|
+
globals: {},
|
|
10746
|
+
networkPolicy: DEFAULT_NETWORK_POLICY,
|
|
10747
|
+
normalizeCode: true,
|
|
10748
|
+
analysis: DEFAULT_ANALYSIS_CONFIG
|
|
10563
10749
|
},
|
|
10564
10750
|
adaptersDir: "./adapters",
|
|
10565
10751
|
skillsDir: "./skills"
|
|
@@ -10653,21 +10839,22 @@ function generateTypes(adapters, options = {}) {
|
|
|
10653
10839
|
const safeName = sanitizeIdentifier(adapter.name);
|
|
10654
10840
|
for (const [toolName, tool] of Object.entries(adapter.tools)) {
|
|
10655
10841
|
if (tool.parameters && Object.keys(tool.parameters).length > 0) {
|
|
10656
|
-
const
|
|
10842
|
+
const safeToolNameForType = sanitizeIdentifier(toolName);
|
|
10843
|
+
const inputTypeName = `${capitalize(safeName)}_${capitalize(safeToolNameForType)}_Input`;
|
|
10657
10844
|
lines.push(generateInputInterface(inputTypeName, tool.parameters, includeDescriptions));
|
|
10658
10845
|
lines.push("");
|
|
10659
10846
|
}
|
|
10660
10847
|
}
|
|
10661
10848
|
if (includeDescriptions && adapter.description) {
|
|
10662
|
-
lines.push(`/** ${adapter.description} */`);
|
|
10849
|
+
lines.push(`/** ${sanitizeJSDoc(adapter.description)} */`);
|
|
10663
10850
|
}
|
|
10664
10851
|
lines.push(`declare const ${safeName}: {`);
|
|
10665
10852
|
for (const [toolName, tool] of Object.entries(adapter.tools)) {
|
|
10666
10853
|
const safeToolName = sanitizeIdentifier(toolName);
|
|
10667
10854
|
const hasParams = tool.parameters && Object.keys(tool.parameters).length > 0;
|
|
10668
|
-
const inputTypeName = `${capitalize(safeName)}_${capitalize(
|
|
10855
|
+
const inputTypeName = `${capitalize(safeName)}_${capitalize(safeToolName)}_Input`;
|
|
10669
10856
|
if (includeDescriptions && tool.description) {
|
|
10670
|
-
lines.push(` /** ${tool.description} */`);
|
|
10857
|
+
lines.push(` /** ${sanitizeJSDoc(tool.description)} */`);
|
|
10671
10858
|
}
|
|
10672
10859
|
const paramStr = hasParams ? `params: ${inputTypeName}` : "";
|
|
10673
10860
|
const returnType = asyncResults ? "Promise<unknown>" : "unknown";
|
|
@@ -10681,20 +10868,21 @@ function generateTypes(adapters, options = {}) {
|
|
|
10681
10868
|
}
|
|
10682
10869
|
function generateTypesSummary(adapters) {
|
|
10683
10870
|
return adapters.map((adapter) => {
|
|
10684
|
-
const
|
|
10685
|
-
return
|
|
10871
|
+
const count = Object.keys(adapter.tools).length;
|
|
10872
|
+
return `- ${adapter.name} (${count} methods)`;
|
|
10686
10873
|
}).join(`
|
|
10687
10874
|
`);
|
|
10688
10875
|
}
|
|
10689
10876
|
function generateInputInterface(typeName, parameters, includeDescriptions) {
|
|
10690
10877
|
const lines = [`interface ${typeName} {`];
|
|
10691
10878
|
for (const [paramName, param] of Object.entries(parameters)) {
|
|
10879
|
+
const safeParamName = sanitizeIdentifier(paramName);
|
|
10692
10880
|
if (includeDescriptions && param.description) {
|
|
10693
|
-
lines.push(` /** ${param.description} */`);
|
|
10881
|
+
lines.push(` /** ${sanitizeJSDoc(param.description)} */`);
|
|
10694
10882
|
}
|
|
10695
10883
|
const tsType = paramTypeToTS(param.type);
|
|
10696
10884
|
const optional = param.required === false ? "?" : "";
|
|
10697
|
-
lines.push(` ${
|
|
10885
|
+
lines.push(` ${safeParamName}${optional}: ${tsType};`);
|
|
10698
10886
|
}
|
|
10699
10887
|
lines.push("}");
|
|
10700
10888
|
return lines.join(`
|
|
@@ -10767,6 +10955,9 @@ function sanitizeIdentifier(name) {
|
|
|
10767
10955
|
function capitalize(str) {
|
|
10768
10956
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
10769
10957
|
}
|
|
10958
|
+
function sanitizeJSDoc(text) {
|
|
10959
|
+
return text.replace(/\*\//g, "* /").replace(/[\r\n]+/g, " ");
|
|
10960
|
+
}
|
|
10770
10961
|
// src/executor.ts
|
|
10771
10962
|
import { pathToFileURL } from "node:url";
|
|
10772
10963
|
class MCXExecutor {
|
|
@@ -10823,7 +11014,7 @@ class MCXExecutor {
|
|
|
10823
11014
|
}
|
|
10824
11015
|
registerAdapter(adapter) {
|
|
10825
11016
|
if (this.adapters.has(adapter.name)) {
|
|
10826
|
-
console.
|
|
11017
|
+
console.error(`[MCX] Adapter "${adapter.name}" is being overwritten`);
|
|
10827
11018
|
}
|
|
10828
11019
|
this.adapters.set(adapter.name, adapter);
|
|
10829
11020
|
}
|
|
@@ -10838,7 +11029,7 @@ class MCXExecutor {
|
|
|
10838
11029
|
}
|
|
10839
11030
|
registerSkill(skill) {
|
|
10840
11031
|
if (this.skills.has(skill.name)) {
|
|
10841
|
-
console.
|
|
11032
|
+
console.error(`[MCX] Skill "${skill.name}" is being overwritten`);
|
|
10842
11033
|
}
|
|
10843
11034
|
this.skills.set(skill.name, skill);
|
|
10844
11035
|
}
|
|
@@ -10905,12 +11096,15 @@ class MCXExecutor {
|
|
|
10905
11096
|
};
|
|
10906
11097
|
} catch (error) {
|
|
10907
11098
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
11099
|
+
const stack = err.stack ? err.stack.split(`
|
|
11100
|
+
`).slice(0, 5).join(`
|
|
11101
|
+
`) : undefined;
|
|
10908
11102
|
return {
|
|
10909
11103
|
success: false,
|
|
10910
11104
|
error: {
|
|
10911
11105
|
name: err.name,
|
|
10912
11106
|
message: err.message,
|
|
10913
|
-
stack
|
|
11107
|
+
stack
|
|
10914
11108
|
},
|
|
10915
11109
|
logs: [],
|
|
10916
11110
|
executionTime: performance.now() - startTime
|