@papicandela/mcx-core 0.2.2 → 0.2.6

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/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
- const __original_fetch = globalThis.fetch;
36
- globalThis.fetch = async function(url, options) {
37
- throw new Error('Network access is blocked in sandbox. Use adapters instead.');
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.XMLHttpRequest = class {
42
- constructor() {
43
- throw new Error('XMLHttpRequest is blocked in sandbox.');
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.WebSocket = class {
49
- constructor(url) {
50
- throw new Error('WebSocket is blocked in sandbox.');
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.EventSource = class {
56
- constructor(url) {
57
- throw new Error('EventSource is blocked in sandbox.');
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
- const __allowed_domains = ${domainsJson};
66
- const __original_fetch = globalThis.fetch;
83
+ (function() {
84
+ const _domains = ${domainsJson};
85
+ const _real_fetch = globalThis.fetch;
67
86
 
68
- function __isUrlAllowed(url) {
69
- try {
70
- const hostname = new URL(url).hostname;
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
- globalThis.fetch = async function(url, options) {
78
- const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.toString() : url.url;
79
- if (!__isUrlAllowed(urlStr)) {
80
- const hostname = new URL(urlStr).hostname;
81
- throw new Error(\`Network access blocked: \${hostname} not in allowed domains: \${__allowed_domains.join(', ')}\`);
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
- return __original_fetch(url, options);
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.XMLHttpRequest = class {
88
- constructor() {
89
- throw new Error('XMLHttpRequest is blocked. Use fetch() with allowed domains.');
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 (would need separate whitelist)
94
- globalThis.WebSocket = class {
95
- constructor(url) {
96
- if (!__isUrlAllowed(url)) {
97
- throw new Error('WebSocket blocked: domain not in allowed list.');
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
- throw new Error('WebSocket not supported in sandbox even for allowed domains.');
100
- }
101
- };
146
+ },
147
+ writable: false,
148
+ configurable: false
149
+ });
102
150
 
103
151
  // Block EventSource
104
- globalThis.EventSource = class {
105
- constructor(url) {
106
- throw new Error('EventSource is blocked in sandbox.');
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 hasBreak(node) {
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 (hasBreak(item))
5661
+ if (hasExitStatement(item))
5603
5662
  return true;
5604
5663
  }
5605
5664
  }
5606
5665
  } else if ("type" in child) {
5607
- if (hasBreak(child))
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 break statements",
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) && !hasBreak(whileNode.body)) {
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 && !hasBreak(forNode.body)) {
5691
+ if (!forNode.test && !hasExitStatement(forNode.body)) {
5692
+ context.report({
5693
+ severity: "error",
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)) {
5633
5702
  context.report({
5634
5703
  severity: "error",
5635
- message: "Infinite loop: for(;;) without break",
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: "Warn about dangerous globals not available in sandbox",
5945
- visits: ["CallExpression", "NewExpression", "Identifier", "MemberExpression"],
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
- if (callExpr.callee.type === "Identifier" && callExpr.callee.name === EVAL_NAME) {
6039
+ const calleeName = callExpr.callee.type === "Identifier" ? callExpr.callee.name : null;
6040
+ if (calleeName === EVAL_NAME) {
5950
6041
  context.report({
5951
- severity: "warn",
5952
- message: `${EVAL_NAME}() is not available in sandbox`,
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 (callExpr.callee.type === "Identifier" && callExpr.callee.name === REQUIRE_NAME) {
6048
+ if (calleeName === FUNC_CONSTRUCTOR) {
5958
6049
  context.report({
5959
- severity: "warn",
5960
- message: `${REQUIRE_NAME}() is not available in sandbox - use adapters instead`,
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: "warn",
5971
- message: `${FUNC_CONSTRUCTOR} constructor is not recommended in sandbox`,
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
- Identifier(node, context) {
5977
- const identifier = node;
5978
- if (identifier.name === PROCESS_NAME) {
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: "warn",
5981
- message: `'${PROCESS_NAME}' is not available in sandbox`,
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.warn(`[mcx-analyzer] Exceeded 50ms budget: ${elapsed.toFixed(1)}ms`);
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
- const timeoutId = setTimeout(() => {
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(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')),
6292
- warn: (...args) => logs.push('[WARN] ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')),
6293
- error: (...args) => logs.push('[ERROR] ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')),
6294
- info: (...args) => logs.push('[INFO] ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')),
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,25 +6488,73 @@ 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
- globalThis.adapters = {};
6524
+ // Levenshtein distance for fuzzy matching
6525
+ const levenshtein = (a, b) => {
6526
+ if (a.length === 0) return b.length;
6527
+ if (b.length === 0) return a.length;
6528
+ const matrix = [];
6529
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
6530
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
6531
+ for (let i = 1; i <= b.length; i++) {
6532
+ for (let j = 1; j <= a.length; j++) {
6533
+ matrix[i][j] = b[i-1] === a[j-1]
6534
+ ? matrix[i-1][j-1]
6535
+ : Math.min(matrix[i-1][j-1] + 1, matrix[i][j-1] + 1, matrix[i-1][j] + 1);
6536
+ }
6537
+ }
6538
+ return matrix[b.length][a.length];
6539
+ };
6540
+
6541
+ // Find similar method names
6542
+ const findSimilar = (name, methods, maxDist = 3) => {
6543
+ const normalized = name.toLowerCase().replace(/[-_]/g, '');
6544
+ return methods
6545
+ .map(m => ({ method: m, dist: levenshtein(normalized, m.toLowerCase().replace(/[-_]/g, '')) }))
6546
+ .filter(x => x.dist <= maxDist)
6547
+ .sort((a, b) => a.dist - b.dist)
6548
+ .slice(0, 3)
6549
+ .map(x => x.method);
6550
+ };
6551
+
6552
+ // Create adapter proxies with helpful error messages
6553
+ const adaptersObj = {};
6359
6554
  for (const [adapterName, methods] of Object.entries(adapterMethods)) {
6360
- const adapterObj = {};
6555
+ const methodsImpl = {};
6361
6556
  for (const methodName of methods) {
6362
- adapterObj[methodName] = async (...args) => {
6557
+ methodsImpl[methodName] = async (...args) => {
6363
6558
  const id = ++callId;
6364
6559
  return new Promise((resolve, reject) => {
6365
6560
  pendingCalls.set(id, { resolve, reject });
@@ -6373,9 +6568,33 @@ class BunWorkerSandbox {
6373
6568
  });
6374
6569
  };
6375
6570
  }
6376
- globalThis.adapters[adapterName] = adapterObj;
6377
- globalThis[adapterName] = adapterObj;
6571
+
6572
+ // Use Proxy to intercept undefined method calls
6573
+ const adapterProxy = new Proxy(methodsImpl, {
6574
+ get(target, prop) {
6575
+ if (prop in target) return target[prop];
6576
+ if (typeof prop === 'symbol') return undefined;
6577
+ const similar = findSimilar(String(prop), methods);
6578
+ const suggestion = similar.length > 0
6579
+ ? '. Did you mean: ' + similar.join(', ') + '?'
6580
+ : '. Available: ' + methods.slice(0, 5).join(', ') + (methods.length > 5 ? '...' : '');
6581
+ throw new Error(adapterName + '.' + String(prop) + ' is not a function' + suggestion);
6582
+ }
6583
+ });
6584
+
6585
+ adaptersObj[adapterName] = adapterProxy;
6586
+ // Also expose at top level but as non-writable
6587
+ Object.defineProperty(globalThis, adapterName, {
6588
+ value: adapterProxy,
6589
+ writable: false,
6590
+ configurable: false
6591
+ });
6378
6592
  }
6593
+ Object.defineProperty(globalThis, 'adapters', {
6594
+ value: adaptersObj,
6595
+ writable: false,
6596
+ configurable: false
6597
+ });
6379
6598
 
6380
6599
  self.postMessage({ type: 'ready' });
6381
6600
  }
@@ -6398,10 +6617,12 @@ class BunWorkerSandbox {
6398
6617
  const result = await fn();
6399
6618
  self.postMessage({ type: 'result', success: true, value: result, logs });
6400
6619
  } catch (err) {
6620
+ // Truncate stack to 5 lines to prevent context bloat
6621
+ const stack = err.stack ? err.stack.split('\\n').slice(0, 5).join('\\n') : undefined;
6401
6622
  self.postMessage({
6402
6623
  type: 'result',
6403
6624
  success: false,
6404
- error: { name: err.name, message: err.message, stack: err.stack },
6625
+ error: { name: err.name, message: err.message, stack },
6405
6626
  logs
6406
6627
  });
6407
6628
  }
@@ -10411,12 +10632,12 @@ function createParameterValidator(params) {
10411
10632
  default:
10412
10633
  schema = exports_external.unknown();
10413
10634
  }
10414
- if (param.default !== undefined) {
10415
- schema = schema.default(param.default);
10416
- }
10417
10635
  if (!param.required) {
10418
10636
  schema = schema.optional();
10419
10637
  }
10638
+ if (param.default !== undefined) {
10639
+ schema = schema.default(param.default);
10640
+ }
10420
10641
  shape[param.name] = schema;
10421
10642
  }
10422
10643
  return exports_external.object(shape);
@@ -10559,7 +10780,10 @@ var DEFAULT_CONFIG2 = {
10559
10780
  timeout: 5000,
10560
10781
  memoryLimit: 128,
10561
10782
  allowAsync: true,
10562
- globals: {}
10783
+ globals: {},
10784
+ networkPolicy: DEFAULT_NETWORK_POLICY,
10785
+ normalizeCode: true,
10786
+ analysis: DEFAULT_ANALYSIS_CONFIG
10563
10787
  },
10564
10788
  adaptersDir: "./adapters",
10565
10789
  skillsDir: "./skills"
@@ -10653,21 +10877,22 @@ function generateTypes(adapters, options = {}) {
10653
10877
  const safeName = sanitizeIdentifier(adapter.name);
10654
10878
  for (const [toolName, tool] of Object.entries(adapter.tools)) {
10655
10879
  if (tool.parameters && Object.keys(tool.parameters).length > 0) {
10656
- const inputTypeName = `${capitalize(safeName)}_${capitalize(toolName)}_Input`;
10880
+ const safeToolNameForType = sanitizeIdentifier(toolName);
10881
+ const inputTypeName = `${capitalize(safeName)}_${capitalize(safeToolNameForType)}_Input`;
10657
10882
  lines.push(generateInputInterface(inputTypeName, tool.parameters, includeDescriptions));
10658
10883
  lines.push("");
10659
10884
  }
10660
10885
  }
10661
10886
  if (includeDescriptions && adapter.description) {
10662
- lines.push(`/** ${adapter.description} */`);
10887
+ lines.push(`/** ${sanitizeJSDoc(adapter.description)} */`);
10663
10888
  }
10664
10889
  lines.push(`declare const ${safeName}: {`);
10665
10890
  for (const [toolName, tool] of Object.entries(adapter.tools)) {
10666
10891
  const safeToolName = sanitizeIdentifier(toolName);
10667
10892
  const hasParams = tool.parameters && Object.keys(tool.parameters).length > 0;
10668
- const inputTypeName = `${capitalize(safeName)}_${capitalize(toolName)}_Input`;
10893
+ const inputTypeName = `${capitalize(safeName)}_${capitalize(safeToolName)}_Input`;
10669
10894
  if (includeDescriptions && tool.description) {
10670
- lines.push(` /** ${tool.description} */`);
10895
+ lines.push(` /** ${sanitizeJSDoc(tool.description)} */`);
10671
10896
  }
10672
10897
  const paramStr = hasParams ? `params: ${inputTypeName}` : "";
10673
10898
  const returnType = asyncResults ? "Promise<unknown>" : "unknown";
@@ -10680,21 +10905,64 @@ function generateTypes(adapters, options = {}) {
10680
10905
  `).trim();
10681
10906
  }
10682
10907
  function generateTypesSummary(adapters) {
10683
- return adapters.map((adapter) => {
10684
- const methods = Object.keys(adapter.tools).join(", ");
10685
- return `${adapter.name}: { ${methods} }`;
10686
- }).join(`
10908
+ const byDomain = new Map;
10909
+ for (const adapter of adapters) {
10910
+ const domain = inferDomain(adapter);
10911
+ if (!byDomain.has(domain)) {
10912
+ byDomain.set(domain, []);
10913
+ }
10914
+ byDomain.get(domain).push(adapter);
10915
+ }
10916
+ if (byDomain.size <= 1 || adapters.length < 4) {
10917
+ return adapters.map((adapter) => {
10918
+ const count = Object.keys(adapter.tools).length;
10919
+ return `- ${adapter.name} (${count} methods)`;
10920
+ }).join(`
10921
+ `);
10922
+ }
10923
+ const lines = [];
10924
+ for (const [domain, domainAdapters] of byDomain) {
10925
+ const adapterList = domainAdapters.map((a) => `${a.name}(${Object.keys(a.tools).length})`).join(", ");
10926
+ lines.push(`[${domain}] ${adapterList}`);
10927
+ }
10928
+ return lines.join(`
10687
10929
  `);
10688
10930
  }
10931
+ function inferDomain(adapter) {
10932
+ if (adapter.domain)
10933
+ return adapter.domain;
10934
+ const name = adapter.name.toLowerCase();
10935
+ const desc = (adapter.description || "").toLowerCase();
10936
+ const combined = `${name} ${desc}`;
10937
+ const domains = {
10938
+ payments: ["stripe", "paypal", "square", "payment", "checkout", "billing", "invoice"],
10939
+ database: ["supabase", "postgres", "mysql", "mongodb", "redis", "database", "sql", "query"],
10940
+ email: ["sendgrid", "mailgun", "postmark", "email", "smtp", "mail"],
10941
+ storage: ["s3", "cloudflare", "storage", "blob", "file", "upload"],
10942
+ auth: ["auth", "oauth", "login", "jwt", "clerk", "auth0"],
10943
+ ai: ["openai", "anthropic", "claude", "gpt", "llm", "ai", "ml"],
10944
+ messaging: ["slack", "discord", "telegram", "twilio", "sms", "chat"],
10945
+ crm: ["hubspot", "salesforce", "crm", "customer"],
10946
+ analytics: ["analytics", "metrics", "tracking", "mixpanel", "amplitude"],
10947
+ devtools: ["github", "gitlab", "jira", "linear", "chrome", "devtools", "ci", "cd"]
10948
+ };
10949
+ for (const [domain, keywords2] of Object.entries(domains)) {
10950
+ if (keywords2.some((k) => combined.includes(k))) {
10951
+ return domain;
10952
+ }
10953
+ }
10954
+ return "general";
10955
+ }
10689
10956
  function generateInputInterface(typeName, parameters, includeDescriptions) {
10690
10957
  const lines = [`interface ${typeName} {`];
10691
10958
  for (const [paramName, param] of Object.entries(parameters)) {
10959
+ const safeParamName = sanitizeIdentifier(paramName);
10692
10960
  if (includeDescriptions && param.description) {
10693
- lines.push(` /** ${param.description} */`);
10961
+ lines.push(` /** ${sanitizeJSDoc(param.description)} */`);
10694
10962
  }
10695
10963
  const tsType = paramTypeToTS(param.type);
10696
- const optional = param.required === false ? "?" : "";
10697
- lines.push(` ${paramName}${optional}: ${tsType};`);
10964
+ const optional = param.required === true ? "" : "?";
10965
+ lines.push(` ${safeParamName}${optional}: ${tsType};`);
10698
10966
  }
10699
10967
  lines.push("}");
10700
10968
  return lines.join(`
@@ -10767,6 +11035,9 @@ function sanitizeIdentifier(name) {
10767
11035
  function capitalize(str) {
10768
11036
  return str.charAt(0).toUpperCase() + str.slice(1);
10769
11037
  }
11038
+ function sanitizeJSDoc(text) {
11039
+ return text.replace(/\*\//g, "* /").replace(/[\r\n]+/g, " ");
11040
+ }
10770
11041
  // src/executor.ts
10771
11042
  import { pathToFileURL } from "node:url";
10772
11043
  class MCXExecutor {
@@ -10823,7 +11094,7 @@ class MCXExecutor {
10823
11094
  }
10824
11095
  registerAdapter(adapter) {
10825
11096
  if (this.adapters.has(adapter.name)) {
10826
- console.warn(`Adapter "${adapter.name}" is being overwritten`);
11097
+ console.error(`[MCX] Adapter "${adapter.name}" is being overwritten`);
10827
11098
  }
10828
11099
  this.adapters.set(adapter.name, adapter);
10829
11100
  }
@@ -10838,7 +11109,7 @@ class MCXExecutor {
10838
11109
  }
10839
11110
  registerSkill(skill) {
10840
11111
  if (this.skills.has(skill.name)) {
10841
- console.warn(`Skill "${skill.name}" is being overwritten`);
11112
+ console.error(`[MCX] Skill "${skill.name}" is being overwritten`);
10842
11113
  }
10843
11114
  this.skills.set(skill.name, skill);
10844
11115
  }
@@ -10905,12 +11176,15 @@ class MCXExecutor {
10905
11176
  };
10906
11177
  } catch (error) {
10907
11178
  const err = error instanceof Error ? error : new Error(String(error));
11179
+ const stack = err.stack ? err.stack.split(`
11180
+ `).slice(0, 5).join(`
11181
+ `) : undefined;
10908
11182
  return {
10909
11183
  success: false,
10910
11184
  error: {
10911
11185
  name: err.name,
10912
11186
  message: err.message,
10913
- stack: err.stack
11187
+ stack
10914
11188
  },
10915
11189
  logs: [],
10916
11190
  executionTime: performance.now() - startTime
@@ -10976,6 +11250,7 @@ export {
10976
11250
  normalizeCode,
10977
11251
  mergeConfigs,
10978
11252
  isUrlAllowed,
11253
+ inferDomain,
10979
11254
  generateTypesSummary,
10980
11255
  generateTypes,
10981
11256
  formatFindings,