@node9/proxy 1.30.0 → 1.31.0

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.mjs CHANGED
@@ -263,7 +263,15 @@ var ConfigFileSchema = z.object({
263
263
  }).optional(),
264
264
  dlp: z.object({
265
265
  enabled: z.boolean().optional(),
266
- scanIgnoredTools: z.boolean().optional()
266
+ scanIgnoredTools: z.boolean().optional(),
267
+ pii: z.enum(["off", "block"]).optional()
268
+ }).optional(),
269
+ egress: z.object({
270
+ enabled: z.boolean().optional(),
271
+ mode: z.enum(["off", "review", "block"]).optional(),
272
+ allow: z.array(z.string()).optional(),
273
+ deny: z.array(z.string()).optional(),
274
+ allowPrivate: z.boolean().optional()
267
275
  }).optional(),
268
276
  loopDetection: z.object({
269
277
  enabled: z.boolean().optional(),
@@ -1245,6 +1253,201 @@ function extractLiteralArgs(callExpr) {
1245
1253
  }
1246
1254
  return { name, flags, paths };
1247
1255
  }
1256
+ var NET_BINARIES = /* @__PURE__ */ new Set(["curl", "wget", "scp", "ssh", "nc", "ncat", "netcat"]);
1257
+ var VALUE_FLAGS = {
1258
+ curl: /* @__PURE__ */ new Set([
1259
+ "-d",
1260
+ "--data",
1261
+ "--data-ascii",
1262
+ "--data-binary",
1263
+ "--data-raw",
1264
+ "--data-urlencode",
1265
+ "-F",
1266
+ "--form",
1267
+ "-H",
1268
+ "--header",
1269
+ "-X",
1270
+ "--request",
1271
+ "-o",
1272
+ "--output",
1273
+ "-T",
1274
+ "--upload-file",
1275
+ "-u",
1276
+ "--user",
1277
+ "-e",
1278
+ "--referer",
1279
+ "-A",
1280
+ "--user-agent",
1281
+ "-b",
1282
+ "--cookie",
1283
+ "-c",
1284
+ "--cookie-jar",
1285
+ "--connect-to",
1286
+ "--resolve",
1287
+ "--cacert",
1288
+ "--cert",
1289
+ "--key",
1290
+ "-x",
1291
+ "--proxy",
1292
+ "-m",
1293
+ "--max-time",
1294
+ "--retry"
1295
+ ]),
1296
+ wget: /* @__PURE__ */ new Set([
1297
+ "-O",
1298
+ "--output-document",
1299
+ "--post-data",
1300
+ "--post-file",
1301
+ "--header",
1302
+ "-U",
1303
+ "--user-agent",
1304
+ "--user",
1305
+ "--password",
1306
+ "-o",
1307
+ "--output-file",
1308
+ "-P",
1309
+ "--directory-prefix",
1310
+ "-t",
1311
+ "--tries",
1312
+ "-T",
1313
+ "--timeout"
1314
+ ]),
1315
+ scp: /* @__PURE__ */ new Set(["-i", "-F", "-l", "-o", "-c", "-S", "-P", "-J", "-D", "-W"]),
1316
+ ssh: /* @__PURE__ */ new Set([
1317
+ "-i",
1318
+ "-p",
1319
+ "-o",
1320
+ "-l",
1321
+ "-F",
1322
+ "-c",
1323
+ "-L",
1324
+ "-R",
1325
+ "-D",
1326
+ "-W",
1327
+ "-b",
1328
+ "-e",
1329
+ "-m",
1330
+ "-O",
1331
+ "-Q",
1332
+ "-S",
1333
+ "-J",
1334
+ "-w",
1335
+ "-B",
1336
+ "-I",
1337
+ "-E"
1338
+ ]),
1339
+ nc: /* @__PURE__ */ new Set(["-p", "-s", "-w", "-X", "-x", "-e", "-g", "-G", "-i", "-O", "-T", "-q", "-m"])
1340
+ };
1341
+ function resolveWordLiteral(w) {
1342
+ const parts = w?.Parts || [];
1343
+ let s = "";
1344
+ for (const p of parts) {
1345
+ const t = syntax.NodeType(p);
1346
+ if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
1347
+ else if (t === "SglQuoted") s += p.Value ?? "";
1348
+ else if (t === "DblQuoted") {
1349
+ const inner = p.Parts || [];
1350
+ if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
1351
+ s += inner.map((ip) => ip.Value ?? "").join("");
1352
+ } else {
1353
+ return null;
1354
+ }
1355
+ }
1356
+ return s;
1357
+ }
1358
+ function parseDestHost(token) {
1359
+ if (!token) return null;
1360
+ let t = token.trim();
1361
+ if (!t || t.startsWith("-")) return null;
1362
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(t)) {
1363
+ try {
1364
+ const h = new URL(t).hostname.toLowerCase();
1365
+ return h || null;
1366
+ } catch {
1367
+ return null;
1368
+ }
1369
+ }
1370
+ const at = t.lastIndexOf("@");
1371
+ if (at >= 0) t = t.slice(at + 1);
1372
+ t = t.split("/")[0];
1373
+ t = t.replace(/:\d+$/, "");
1374
+ t = t.split(":")[0];
1375
+ t = t.toLowerCase();
1376
+ if (t.length > 253) return null;
1377
+ if (t === "localhost") return t;
1378
+ if (/^[a-z0-9.-]+\.[a-z0-9.-]+$/.test(t)) return t;
1379
+ return null;
1380
+ }
1381
+ function destTokensForBinary(binary, args) {
1382
+ const valueFlags = VALUE_FLAGS[binary] ?? /* @__PURE__ */ new Set();
1383
+ const positionals = [];
1384
+ const urlFlagValues = [];
1385
+ for (let i = 0; i < args.length; i++) {
1386
+ const tok = args[i];
1387
+ if (tok === null) continue;
1388
+ if (tok.startsWith("-")) {
1389
+ if (tok.startsWith("--url=")) {
1390
+ urlFlagValues.push(tok.slice("--url=".length));
1391
+ continue;
1392
+ }
1393
+ if (tok === "--url") {
1394
+ const next = args[i + 1];
1395
+ if (typeof next === "string") urlFlagValues.push(next);
1396
+ i++;
1397
+ continue;
1398
+ }
1399
+ if (tok.includes("=")) continue;
1400
+ if (valueFlags.has(tok)) i++;
1401
+ continue;
1402
+ }
1403
+ positionals.push(tok);
1404
+ }
1405
+ switch (binary) {
1406
+ case "curl":
1407
+ case "wget":
1408
+ return [...urlFlagValues, ...positionals];
1409
+ case "ssh":
1410
+ return positionals.slice(0, 1);
1411
+ case "scp":
1412
+ return positionals.filter((p) => p.includes(":") || p.includes("@"));
1413
+ case "nc":
1414
+ case "ncat":
1415
+ case "netcat":
1416
+ return positionals.slice(0, 1);
1417
+ default:
1418
+ return [];
1419
+ }
1420
+ }
1421
+ function extractShellDestinations(command) {
1422
+ const f = parseShared(command);
1423
+ if (f === PARSE_FAIL) return [];
1424
+ const out = [];
1425
+ const seen = /* @__PURE__ */ new Set();
1426
+ try {
1427
+ syntax.Walk(f, (node) => {
1428
+ if (!node) return false;
1429
+ const n = node;
1430
+ if (syntax.NodeType(n) !== "CallExpr") return true;
1431
+ const callArgs = n.Args || [];
1432
+ if (callArgs.length === 0) return true;
1433
+ const name = (resolveWordLiteral(callArgs[0]) || "").toLowerCase();
1434
+ if (!NET_BINARIES.has(name)) return true;
1435
+ const rest = callArgs.slice(1).map((a) => resolveWordLiteral(a));
1436
+ for (const raw of destTokensForBinary(name, rest)) {
1437
+ const host = parseDestHost(raw);
1438
+ if (!host) continue;
1439
+ const key = `${name}:${host}`;
1440
+ if (seen.has(key)) continue;
1441
+ seen.add(key);
1442
+ out.push({ host, binary: name, raw });
1443
+ }
1444
+ return true;
1445
+ });
1446
+ } catch {
1447
+ return out;
1448
+ }
1449
+ return out;
1450
+ }
1248
1451
  var FS_OP_CACHE_MAX = 5e3;
1249
1452
  var fsOpCache = /* @__PURE__ */ new Map();
1250
1453
  function analyzeFsOperation(command) {
@@ -1374,6 +1577,83 @@ function analyzeShellCommand(command) {
1374
1577
  }
1375
1578
  return { actions, paths, allTokens };
1376
1579
  }
1580
+ var DEFAULT_EGRESS_ALLOWLIST = [
1581
+ "*.github.com",
1582
+ "*.githubusercontent.com",
1583
+ "*.npmjs.org",
1584
+ "pypi.org",
1585
+ "*.pythonhosted.org",
1586
+ "crates.io",
1587
+ "*.crates.io",
1588
+ "rubygems.org",
1589
+ "proxy.golang.org",
1590
+ "sum.golang.org",
1591
+ "*.anthropic.com",
1592
+ "*.openai.com",
1593
+ "*.googleapis.com",
1594
+ "*.docker.io",
1595
+ "*.docker.com",
1596
+ "deb.debian.org",
1597
+ "*.ubuntu.com"
1598
+ ];
1599
+ function hostMatches(host, pattern) {
1600
+ const h = host.toLowerCase();
1601
+ const p = pattern.toLowerCase().trim();
1602
+ if (!p) return false;
1603
+ if (p === "*") return true;
1604
+ if (p.startsWith("*.")) {
1605
+ const suffix = p.slice(2);
1606
+ return h === suffix || h.endsWith("." + suffix);
1607
+ }
1608
+ return h === p;
1609
+ }
1610
+ function matchesAny(host, patterns) {
1611
+ for (const p of patterns) if (hostMatches(host, p)) return true;
1612
+ return false;
1613
+ }
1614
+ function isPrivateHost(host) {
1615
+ const h = host.toLowerCase();
1616
+ if (h === "localhost" || h === "0.0.0.0") return true;
1617
+ if (h.endsWith(".local") || h.endsWith(".internal") || h.endsWith(".localhost")) return true;
1618
+ if (/^127\./.test(h)) return true;
1619
+ if (/^10\./.test(h)) return true;
1620
+ if (/^192\.168\./.test(h)) return true;
1621
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
1622
+ return false;
1623
+ }
1624
+ function evaluateEgress(dests, policy) {
1625
+ if (!policy.enabled) return null;
1626
+ let review = null;
1627
+ for (const d of dests) {
1628
+ if (matchesAny(d.host, policy.deny)) {
1629
+ return {
1630
+ verdict: "block",
1631
+ host: d.host,
1632
+ binary: d.binary,
1633
+ reason: `Egress to ${d.host} is on the deny list.`
1634
+ };
1635
+ }
1636
+ if (policy.allowPrivate && isPrivateHost(d.host)) continue;
1637
+ if (matchesAny(d.host, policy.allow) || matchesAny(d.host, DEFAULT_EGRESS_ALLOWLIST)) continue;
1638
+ if (policy.mode === "block") {
1639
+ return {
1640
+ verdict: "block",
1641
+ host: d.host,
1642
+ binary: d.binary,
1643
+ reason: `Egress to unknown host ${d.host} is blocked (egress policy: block).`
1644
+ };
1645
+ }
1646
+ if (policy.mode === "review" && !review) {
1647
+ review = {
1648
+ verdict: "review",
1649
+ host: d.host,
1650
+ binary: d.binary,
1651
+ reason: `${d.binary} is sending data to an unrecognized host (${d.host}). Approve this destination?`
1652
+ };
1653
+ }
1654
+ }
1655
+ return review;
1656
+ }
1377
1657
  var SOURCE_COMMANDS = /* @__PURE__ */ new Set([
1378
1658
  "cat",
1379
1659
  "head",
@@ -1939,6 +2219,22 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
1939
2219
  }
1940
2220
  const ptVerdict = pipeChainVerdict(shellCommand, isTrustedHost2);
1941
2221
  if (ptVerdict) return ptVerdict;
2222
+ if (config.policy.egress?.enabled) {
2223
+ const dests = extractShellDestinations(shellCommand);
2224
+ if (dests.length > 0) {
2225
+ const eg = evaluateEgress(dests, config.policy.egress);
2226
+ if (eg) {
2227
+ return {
2228
+ decision: eg.verdict,
2229
+ blockedByLabel: eg.verdict === "block" ? "\u{1F310} Node9 Egress (Blocked)" : "\u{1F310} Node9 Egress (Review)",
2230
+ reason: eg.reason,
2231
+ ruleName: `egress:${eg.binary}:${eg.host}`,
2232
+ ruleDescription: eg.reason,
2233
+ tier: eg.verdict === "block" ? 3 : 4
2234
+ };
2235
+ }
2236
+ }
2237
+ }
1942
2238
  const firstToken = analyzed.actions[0] ?? "";
1943
2239
  if (["ssh", "scp", "rsync"].includes(firstToken)) {
1944
2240
  const rawTokens = shellCommand.trim().split(/\s+/);
@@ -2860,6 +3156,32 @@ function evaluateLoopWindow(records, tool, args, threshold, windowMs, now) {
2860
3156
  const nextRecords = fresh.slice(-LOOP_MAX_RECORDS);
2861
3157
  return { nextRecords, count, looping: count >= threshold };
2862
3158
  }
3159
+ var PII_EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/;
3160
+ var PII_SSN_RE = /\b\d{3}-\d{2}-\d{4}\b/;
3161
+ var PII_PHONE_RE = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/;
3162
+ var PII_CC_RE = /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6\d{3})[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/;
3163
+ function detectPii(text) {
3164
+ const found = /* @__PURE__ */ new Set();
3165
+ if (/@/.test(text) && PII_EMAIL_RE.test(text)) found.add("Email");
3166
+ if (/-/.test(text) && PII_SSN_RE.test(text)) found.add("SSN");
3167
+ if (PII_PHONE_RE.test(text)) found.add("Phone");
3168
+ if (PII_CC_RE.test(text)) found.add("Credit Card");
3169
+ return [...found];
3170
+ }
3171
+ var REALTIME_PII_PATTERNS = ["SSN", "Credit Card"];
3172
+ var MAX_PII_SCAN_BYTES = 1e5;
3173
+ function detectArgsPii(args) {
3174
+ if (args === null || args === void 0) return [];
3175
+ let text;
3176
+ try {
3177
+ text = typeof args === "string" ? args : JSON.stringify(args);
3178
+ } catch {
3179
+ return [];
3180
+ }
3181
+ if (typeof text !== "string") return [];
3182
+ if (text.length > MAX_PII_SCAN_BYTES) text = text.slice(0, MAX_PII_SCAN_BYTES);
3183
+ return detectPii(text).filter((p) => REALTIME_PII_PATTERNS.includes(p));
3184
+ }
2863
3185
  var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
2864
3186
 
2865
3187
  // src/shields.ts
@@ -3147,7 +3469,8 @@ var DEFAULT_CONFIG = {
3147
3469
  description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
3148
3470
  }
3149
3471
  ],
3150
- dlp: { enabled: true, scanIgnoredTools: true },
3472
+ dlp: { enabled: true, scanIgnoredTools: true, pii: "off" },
3473
+ egress: { enabled: false, mode: "review", allow: [], deny: [], allowPrivate: true },
3151
3474
  loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 },
3152
3475
  skillPinning: { enabled: false, mode: "warn", roots: [] }
3153
3476
  },
@@ -3273,6 +3596,11 @@ function getConfig(cwd) {
3273
3596
  ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
3274
3597
  },
3275
3598
  dlp: { ...DEFAULT_CONFIG.policy.dlp },
3599
+ egress: {
3600
+ ...DEFAULT_CONFIG.policy.egress,
3601
+ allow: [...DEFAULT_CONFIG.policy.egress.allow],
3602
+ deny: [...DEFAULT_CONFIG.policy.egress.deny]
3603
+ },
3276
3604
  loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection },
3277
3605
  skillPinning: {
3278
3606
  ...DEFAULT_CONFIG.policy.skillPinning,
@@ -3323,6 +3651,15 @@ function getConfig(cwd) {
3323
3651
  const d = p.dlp;
3324
3652
  if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
3325
3653
  if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
3654
+ if (d.pii !== void 0) mergedPolicy.dlp.pii = d.pii;
3655
+ }
3656
+ if (p.egress) {
3657
+ const e = p.egress;
3658
+ if (e.enabled !== void 0) mergedPolicy.egress.enabled = e.enabled;
3659
+ if (e.mode !== void 0) mergedPolicy.egress.mode = e.mode;
3660
+ if (Array.isArray(e.allow)) mergedPolicy.egress.allow.push(...e.allow);
3661
+ if (Array.isArray(e.deny)) mergedPolicy.egress.deny.push(...e.deny);
3662
+ if (e.allowPrivate !== void 0) mergedPolicy.egress.allowPrivate = e.allowPrivate;
3326
3663
  }
3327
3664
  if (p.loopDetection) {
3328
3665
  const ld = p.loopDetection;
@@ -4065,6 +4402,15 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
4065
4402
  var isTestEnv = () => {
4066
4403
  return process.env.NODE_ENV === "test" || process.env.VITEST === "true" || !!process.env.VITEST || process.env.CI === "true" || !!process.env.CI || process.env.NODE9_TESTING === "1";
4067
4404
  };
4405
+ var MIN_INTERACTION_MS = 400;
4406
+ function resolveNativeDecision(opts) {
4407
+ const { code, output, elapsedMs, locked } = opts;
4408
+ if (locked) return "deny";
4409
+ const tooFast = elapsedMs < MIN_INTERACTION_MS;
4410
+ if (output.includes("Always Allow")) return tooFast ? "deny" : "always_allow";
4411
+ if (code === 0) return tooFast ? "deny" : "allow";
4412
+ return "deny";
4413
+ }
4068
4414
  function formatArgs(args, matchedField, matchedWord) {
4069
4415
  if (args === null || args === void 0) return { message: "(none)", intent: "EXEC" };
4070
4416
  let parsed = args;
@@ -4208,6 +4554,7 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
4208
4554
  );
4209
4555
  return new Promise((resolve) => {
4210
4556
  let childProcess = null;
4557
+ const startedAt = Date.now();
4211
4558
  const onAbort = () => {
4212
4559
  if (childProcess && childProcess.pid) {
4213
4560
  try {
@@ -4265,13 +4612,14 @@ end run`;
4265
4612
  }
4266
4613
  let output = "";
4267
4614
  childProcess?.stdout?.on("data", (d) => output += d.toString());
4268
- childProcess?.on("close", (code) => {
4615
+ childProcess?.on("error", () => {
4269
4616
  if (signal) signal.removeEventListener("abort", onAbort);
4270
- if (locked) return resolve("deny");
4271
- if (output.includes("Always Allow")) return resolve("always_allow");
4272
- if (code === 0) return resolve("allow");
4273
4617
  resolve("deny");
4274
4618
  });
4619
+ childProcess?.on("close", (code) => {
4620
+ if (signal) signal.removeEventListener("abort", onAbort);
4621
+ resolve(resolveNativeDecision({ code, output, elapsedMs: Date.now() - startedAt, locked }));
4622
+ });
4275
4623
  } catch {
4276
4624
  resolve("deny");
4277
4625
  }
@@ -4573,6 +4921,37 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4573
4921
  if (taintResult.tainted && taintResult.record) {
4574
4922
  const { path: taintedPath, source: taintSource } = taintResult.record;
4575
4923
  taintWarning = `\u26A0\uFE0F ${taintedPath} was flagged by ${taintSource} \u2014 this file may contain sensitive data`;
4924
+ if (config.policy.egress?.enabled) {
4925
+ const a = args && typeof args === "object" && !Array.isArray(args) ? args : {};
4926
+ const cmd = typeof a.command === "string" ? a.command : typeof a.cmd === "string" ? a.cmd : "";
4927
+ const dests = cmd ? extractShellDestinations(cmd) : [];
4928
+ const eg = dests.length > 0 ? evaluateEgress(dests, config.policy.egress) : null;
4929
+ if (eg) {
4930
+ if (!isManual)
4931
+ appendLocalAudit(
4932
+ toolName,
4933
+ args,
4934
+ "deny",
4935
+ isObserveMode ? "observe-mode-taint-egress-would-block" : "taint-egress-block",
4936
+ { ...meta, ruleName: `taint-egress:${eg.host}` },
4937
+ hashAuditArgs
4938
+ );
4939
+ if (isObserveMode) {
4940
+ return {
4941
+ approved: true,
4942
+ checkedBy: "audit",
4943
+ observeWouldBlock: true,
4944
+ blockedByLabel: "\u{1F534} Node9 Taint+Egress (Exfiltration)"
4945
+ };
4946
+ }
4947
+ return {
4948
+ approved: false,
4949
+ reason: `\u{1F534} EXFILTRATION BLOCKED: the tainted file "${taintedPath}" is being sent to untrusted host "${eg.host}". A flagged file leaving to an unrecognized destination is blocked outright.`,
4950
+ blockedBy: "local-config",
4951
+ blockedByLabel: "\u{1F534} Node9 Taint+Egress (Exfiltration Blocked)"
4952
+ };
4953
+ }
4954
+ }
4576
4955
  } else if (taintResult.daemonUnavailable) {
4577
4956
  taintWarning = `\u26A0\uFE0F Taint service unavailable \u2014 cannot verify if ${filePaths.join(", ")} is clean`;
4578
4957
  }
@@ -4621,6 +5000,35 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4621
5000
  explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
4622
5001
  }
4623
5002
  }
5003
+ if (config.policy.dlp.pii === "block" && (!isIgnoredTool2(toolName) || config.policy.dlp.scanIgnoredTools)) {
5004
+ const piiFound = detectArgsPii(args);
5005
+ if (piiFound.length > 0) {
5006
+ const piiReason = `\u{1F512} PII DETECTED: ${piiFound.join(", ")} found in tool arguments. Remove or tokenize personal data before passing it to a tool.`;
5007
+ if (!isManual)
5008
+ appendLocalAudit(
5009
+ toolName,
5010
+ args,
5011
+ "deny",
5012
+ isObserveMode ? "observe-mode-pii-would-block" : "pii-block",
5013
+ { ...meta, piiPatterns: piiFound.join(",") },
5014
+ true
5015
+ );
5016
+ if (isObserveMode) {
5017
+ return {
5018
+ approved: true,
5019
+ checkedBy: "audit",
5020
+ observeWouldBlock: true,
5021
+ blockedByLabel: "\u{1F512} Node9 PII (Detected)"
5022
+ };
5023
+ }
5024
+ return {
5025
+ approved: false,
5026
+ reason: piiReason,
5027
+ blockedBy: "local-config",
5028
+ blockedByLabel: "\u{1F512} Node9 PII (Detected)"
5029
+ };
5030
+ }
5031
+ }
4624
5032
  if (isObserveMode) {
4625
5033
  if (!isIgnoredTool2(toolName)) {
4626
5034
  const policyResult = await evaluatePolicy2(toolName, args, meta?.agent, options?.cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.30.0",
3
+ "version": "1.31.0",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",