@node9/proxy 1.0.14 → 1.0.15

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
@@ -40,6 +40,8 @@ var import_prompts = require("@inquirer/prompts");
40
40
  var import_fs2 = __toESM(require("fs"));
41
41
  var import_path4 = __toESM(require("path"));
42
42
  var import_os2 = __toESM(require("os"));
43
+ var import_net = __toESM(require("net"));
44
+ var import_crypto = require("crypto");
43
45
  var import_picomatch = __toESM(require("picomatch"));
44
46
  var import_sh_syntax = require("sh-syntax");
45
47
 
@@ -1335,7 +1337,7 @@ function getPersistentDecision(toolName) {
1335
1337
  }
1336
1338
  return null;
1337
1339
  }
1338
- async function askDaemon(toolName, args, meta, signal, riskMetadata) {
1340
+ async function askDaemon(toolName, args, meta, signal, riskMetadata, activityId) {
1339
1341
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
1340
1342
  const checkCtrl = new AbortController();
1341
1343
  const checkTimer = setTimeout(() => checkCtrl.abort(), 5e3);
@@ -1350,6 +1352,12 @@ async function askDaemon(toolName, args, meta, signal, riskMetadata) {
1350
1352
  args,
1351
1353
  agent: meta?.agent,
1352
1354
  mcpServer: meta?.mcpServer,
1355
+ fromCLI: true,
1356
+ // Pass the flight-recorder ID so the daemon uses the same UUID for
1357
+ // activity-result as the CLI used for the pending activity event.
1358
+ // Without this, the two UUIDs never match and tail.ts never resolves
1359
+ // the pending item.
1360
+ activityId,
1353
1361
  ...riskMetadata && { riskMetadata }
1354
1362
  }),
1355
1363
  signal: checkCtrl.signal
@@ -1404,7 +1412,45 @@ async function resolveViaDaemon(id, decision, internalToken) {
1404
1412
  signal: AbortSignal.timeout(3e3)
1405
1413
  });
1406
1414
  }
1415
+ var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path4.default.join(import_os2.default.tmpdir(), "node9-activity.sock");
1416
+ function notifyActivity(data) {
1417
+ return new Promise((resolve) => {
1418
+ try {
1419
+ const payload = JSON.stringify(data);
1420
+ const sock = import_net.default.createConnection(ACTIVITY_SOCKET_PATH);
1421
+ sock.on("connect", () => {
1422
+ sock.on("close", resolve);
1423
+ sock.end(payload);
1424
+ });
1425
+ sock.on("error", resolve);
1426
+ } catch {
1427
+ resolve();
1428
+ }
1429
+ });
1430
+ }
1407
1431
  async function authorizeHeadless(toolName, args, allowTerminalFallback = false, meta, options) {
1432
+ if (!options?.calledFromDaemon) {
1433
+ const actId = (0, import_crypto.randomUUID)();
1434
+ const actTs = Date.now();
1435
+ await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
1436
+ const result = await _authorizeHeadlessCore(toolName, args, allowTerminalFallback, meta, {
1437
+ ...options,
1438
+ activityId: actId
1439
+ });
1440
+ if (!result.noApprovalMechanism) {
1441
+ await notifyActivity({
1442
+ id: actId,
1443
+ tool: toolName,
1444
+ ts: actTs,
1445
+ status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
1446
+ label: result.blockedByLabel
1447
+ });
1448
+ }
1449
+ return result;
1450
+ }
1451
+ return _authorizeHeadlessCore(toolName, args, allowTerminalFallback, meta, options);
1452
+ }
1453
+ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = false, meta, options) {
1408
1454
  if (process.env.NODE9_PAUSED === "1") return { approved: true, checkedBy: "paused" };
1409
1455
  const pauseState = checkPause();
1410
1456
  if (pauseState.paused) return { approved: true, checkedBy: "paused" };
@@ -1440,6 +1486,7 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
1440
1486
  blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
1441
1487
  };
1442
1488
  }
1489
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "dlp-review-flagged", meta);
1443
1490
  explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
1444
1491
  }
1445
1492
  }
@@ -1662,7 +1709,14 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
1662
1709
  console.error(import_chalk2.default.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
1663
1710
  `));
1664
1711
  }
1665
- const daemonDecision = await askDaemon(toolName, args, meta, signal, riskMetadata);
1712
+ const daemonDecision = await askDaemon(
1713
+ toolName,
1714
+ args,
1715
+ meta,
1716
+ signal,
1717
+ riskMetadata,
1718
+ options?.activityId
1719
+ );
1666
1720
  if (daemonDecision === "abandoned") throw new Error("Abandoned");
1667
1721
  const isApproved = daemonDecision === "allow";
1668
1722
  return {
@@ -1866,7 +1920,10 @@ function getConfig() {
1866
1920
  for (const rule of shield.smartRules) {
1867
1921
  if (!existingRuleNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
1868
1922
  }
1869
- for (const word of shield.dangerousWords) mergedPolicy.dangerousWords.push(word);
1923
+ const existingWords = new Set(mergedPolicy.dangerousWords);
1924
+ for (const word of shield.dangerousWords) {
1925
+ if (!existingWords.has(word)) mergedPolicy.dangerousWords.push(word);
1926
+ }
1870
1927
  }
1871
1928
  const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
1872
1929
  for (const rule of ADVISORY_SMART_RULES) {
package/dist/index.mjs CHANGED
@@ -4,6 +4,8 @@ import { confirm } from "@inquirer/prompts";
4
4
  import fs2 from "fs";
5
5
  import path4 from "path";
6
6
  import os2 from "os";
7
+ import net from "net";
8
+ import { randomUUID } from "crypto";
7
9
  import pm from "picomatch";
8
10
  import { parse } from "sh-syntax";
9
11
 
@@ -1299,7 +1301,7 @@ function getPersistentDecision(toolName) {
1299
1301
  }
1300
1302
  return null;
1301
1303
  }
1302
- async function askDaemon(toolName, args, meta, signal, riskMetadata) {
1304
+ async function askDaemon(toolName, args, meta, signal, riskMetadata, activityId) {
1303
1305
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
1304
1306
  const checkCtrl = new AbortController();
1305
1307
  const checkTimer = setTimeout(() => checkCtrl.abort(), 5e3);
@@ -1314,6 +1316,12 @@ async function askDaemon(toolName, args, meta, signal, riskMetadata) {
1314
1316
  args,
1315
1317
  agent: meta?.agent,
1316
1318
  mcpServer: meta?.mcpServer,
1319
+ fromCLI: true,
1320
+ // Pass the flight-recorder ID so the daemon uses the same UUID for
1321
+ // activity-result as the CLI used for the pending activity event.
1322
+ // Without this, the two UUIDs never match and tail.ts never resolves
1323
+ // the pending item.
1324
+ activityId,
1317
1325
  ...riskMetadata && { riskMetadata }
1318
1326
  }),
1319
1327
  signal: checkCtrl.signal
@@ -1368,7 +1376,45 @@ async function resolveViaDaemon(id, decision, internalToken) {
1368
1376
  signal: AbortSignal.timeout(3e3)
1369
1377
  });
1370
1378
  }
1379
+ var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path4.join(os2.tmpdir(), "node9-activity.sock");
1380
+ function notifyActivity(data) {
1381
+ return new Promise((resolve) => {
1382
+ try {
1383
+ const payload = JSON.stringify(data);
1384
+ const sock = net.createConnection(ACTIVITY_SOCKET_PATH);
1385
+ sock.on("connect", () => {
1386
+ sock.on("close", resolve);
1387
+ sock.end(payload);
1388
+ });
1389
+ sock.on("error", resolve);
1390
+ } catch {
1391
+ resolve();
1392
+ }
1393
+ });
1394
+ }
1371
1395
  async function authorizeHeadless(toolName, args, allowTerminalFallback = false, meta, options) {
1396
+ if (!options?.calledFromDaemon) {
1397
+ const actId = randomUUID();
1398
+ const actTs = Date.now();
1399
+ await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
1400
+ const result = await _authorizeHeadlessCore(toolName, args, allowTerminalFallback, meta, {
1401
+ ...options,
1402
+ activityId: actId
1403
+ });
1404
+ if (!result.noApprovalMechanism) {
1405
+ await notifyActivity({
1406
+ id: actId,
1407
+ tool: toolName,
1408
+ ts: actTs,
1409
+ status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
1410
+ label: result.blockedByLabel
1411
+ });
1412
+ }
1413
+ return result;
1414
+ }
1415
+ return _authorizeHeadlessCore(toolName, args, allowTerminalFallback, meta, options);
1416
+ }
1417
+ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = false, meta, options) {
1372
1418
  if (process.env.NODE9_PAUSED === "1") return { approved: true, checkedBy: "paused" };
1373
1419
  const pauseState = checkPause();
1374
1420
  if (pauseState.paused) return { approved: true, checkedBy: "paused" };
@@ -1404,6 +1450,7 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
1404
1450
  blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
1405
1451
  };
1406
1452
  }
1453
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "dlp-review-flagged", meta);
1407
1454
  explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
1408
1455
  }
1409
1456
  }
@@ -1626,7 +1673,14 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
1626
1673
  console.error(chalk2.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
1627
1674
  `));
1628
1675
  }
1629
- const daemonDecision = await askDaemon(toolName, args, meta, signal, riskMetadata);
1676
+ const daemonDecision = await askDaemon(
1677
+ toolName,
1678
+ args,
1679
+ meta,
1680
+ signal,
1681
+ riskMetadata,
1682
+ options?.activityId
1683
+ );
1630
1684
  if (daemonDecision === "abandoned") throw new Error("Abandoned");
1631
1685
  const isApproved = daemonDecision === "allow";
1632
1686
  return {
@@ -1830,7 +1884,10 @@ function getConfig() {
1830
1884
  for (const rule of shield.smartRules) {
1831
1885
  if (!existingRuleNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
1832
1886
  }
1833
- for (const word of shield.dangerousWords) mergedPolicy.dangerousWords.push(word);
1887
+ const existingWords = new Set(mergedPolicy.dangerousWords);
1888
+ for (const word of shield.dangerousWords) {
1889
+ if (!existingWords.has(word)) mergedPolicy.dangerousWords.push(word);
1890
+ }
1834
1891
  }
1835
1892
  const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
1836
1893
  for (const rule of ADVISORY_SMART_RULES) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code & MCP.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",