@node9/proxy 1.19.3 → 1.19.4

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/cli.js CHANGED
@@ -1708,7 +1708,11 @@ function extractCanonicalFindings(call, ctx) {
1708
1708
  })
1709
1709
  );
1710
1710
  }
1711
- if (PRIVILEGE_ESCALATION_RE.test(command)) {
1711
+ const ast = analyzeShellCommand(command);
1712
+ const sudoVariant = ast.actions.includes("sudo") || ast.actions.includes("su");
1713
+ const chmodVariant = ast.actions.includes("chmod") && (ast.allTokens.includes("777") || ast.allTokens.includes("0777") || ast.allTokens.includes("+x"));
1714
+ const chownVariant = ast.actions.includes("chown") && ast.allTokens.includes("root");
1715
+ if (sudoVariant || chmodVariant || chownVariant) {
1712
1716
  out.push(
1713
1717
  makeFinding({
1714
1718
  type: "privilege-escalation",
@@ -1840,7 +1844,7 @@ function* stringValues(obj, depth = 0) {
1840
1844
  }
1841
1845
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
1842
1846
  }
1843
- var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto2, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE, PRIVILEGE_ESCALATION_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE;
1847
+ var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto2, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE;
1844
1848
  var init_dist = __esm({
1845
1849
  "packages/policy-engine/dist/index.mjs"() {
1846
1850
  "use strict";
@@ -3246,7 +3250,6 @@ var init_dist = __esm({
3246
3250
  LOOP_THRESHOLD_FOR_WASTE = 3;
3247
3251
  COST_PER_LOOP_ITER_USD = 6e-3;
3248
3252
  DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
3249
- PRIVILEGE_ESCALATION_RE = /\b(sudo|su)\b\s+[a-z]|\bchmod\s+(0?777|\+x)\b|\bchown\s+root\b/i;
3250
3253
  SENSITIVE_PATH_RE = /\.aws\/(credentials|config)\b|\.ssh\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)\b|\.env(\.|$|\b)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
3251
3254
  FILE_TOOLS = /* @__PURE__ */ new Set([
3252
3255
  "read",
@@ -3266,7 +3269,7 @@ var init_dist = __esm({
3266
3269
  PII_PHONE_RE = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/;
3267
3270
  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/;
3268
3271
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
3269
- CANONICAL_EXTRACTOR_VERSION = "canonical-v1";
3272
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
3270
3273
  DEDUPE_PREVIEW_LEN = 120;
3271
3274
  TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
3272
3275
  /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
@@ -4807,7 +4810,7 @@ async function waitForDaemonDecision(id, signal) {
4807
4810
  if (signal) signal.removeEventListener("abort", onAbort);
4808
4811
  }
4809
4812
  }
4810
- async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
4813
+ async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
4811
4814
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
4812
4815
  const res = await fetch(`${base}/check`, {
4813
4816
  method: "POST",
@@ -4818,7 +4821,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
4818
4821
  slackDelegated: true,
4819
4822
  agent: meta?.agent,
4820
4823
  mcpServer: meta?.mcpServer,
4821
- ...riskMetadata && { riskMetadata }
4824
+ ...riskMetadata && { riskMetadata },
4825
+ // fromCLI=true tells the daemon the CLI already sent the activity
4826
+ // event via socket. Same contract as registerDaemonEntry — without
4827
+ // it the daemon double-emits 'activity' for cloud-enforced flows.
4828
+ fromCLI: socketActivitySent !== false,
4829
+ activityId
4822
4830
  }),
4823
4831
  signal: AbortSignal.timeout(3e3)
4824
4832
  });
@@ -5963,7 +5971,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
5963
5971
  let daemonAllowCount = 1;
5964
5972
  if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
5965
5973
  if (cloudEnforced && cloudRequestId) {
5966
- const viewer = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
5974
+ const viewer = await notifyDaemonViewer(
5975
+ toolName,
5976
+ args,
5977
+ meta,
5978
+ riskMetadata,
5979
+ options?.activityId,
5980
+ options?.socketActivitySent
5981
+ ).catch(() => null);
5967
5982
  viewerId = viewer?.id ?? null;
5968
5983
  daemonEntryId = viewerId;
5969
5984
  if (viewer) daemonAllowCount = viewer.allowCount;
@@ -8056,6 +8071,7 @@ __export(scan_watermark_exports, {
8056
8071
  markUploadComplete: () => markUploadComplete,
8057
8072
  saveWatermark: () => saveWatermark,
8058
8073
  scanDelta: () => scanDelta,
8074
+ tickForensicBroadcast: () => tickForensicBroadcast,
8059
8075
  tickScanWatcher: () => tickScanWatcher
8060
8076
  });
8061
8077
  function freshWatermark() {
@@ -8259,6 +8275,25 @@ function extractFindingsFromLine(line, sessionId, lineIndex) {
8259
8275
  }
8260
8276
  return findings;
8261
8277
  }
8278
+ async function tickForensicBroadcast(offsets) {
8279
+ const out = [];
8280
+ const files = listJsonlFiles();
8281
+ for (const file of files) {
8282
+ const size = fileSize(file);
8283
+ const offset = offsets.get(file);
8284
+ if (offset === void 0) {
8285
+ offsets.set(file, size);
8286
+ continue;
8287
+ }
8288
+ if (size <= offset) continue;
8289
+ const sessionId = import_path19.default.basename(file, ".jsonl");
8290
+ const newOffset = await scanDelta(file, offset, (obj, lineIndex) => {
8291
+ out.push(...extractFindingsFromLine(obj, sessionId, lineIndex));
8292
+ });
8293
+ offsets.set(file, newOffset);
8294
+ }
8295
+ return out;
8296
+ }
8262
8297
  function markUploadComplete() {
8263
8298
  const state = loadWatermark();
8264
8299
  if (state.status === "schema-future") return;
@@ -11027,6 +11062,19 @@ data: ${JSON.stringify(data)}
11027
11062
  }
11028
11063
  });
11029
11064
  }
11065
+ function broadcastForensic(finding) {
11066
+ const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
11067
+ const event = {
11068
+ type: "forensic",
11069
+ id: `fnd_${(0, import_crypto6.randomUUID)()}`,
11070
+ ts: Date.now(),
11071
+ sessionId: finding.sessionId,
11072
+ category: finding.type,
11073
+ severity
11074
+ };
11075
+ if (finding.patternName !== void 0) event.patternName = finding.patternName;
11076
+ broadcast("forensic", event);
11077
+ }
11030
11078
  function abandonPending() {
11031
11079
  setAbandonTimer(null);
11032
11080
  pending.forEach((entry, id) => {
@@ -11210,7 +11258,7 @@ function bindActivitySocket() {
11210
11258
  });
11211
11259
  activitySocketServer = unixServer;
11212
11260
  }
11213
- var import_net2, import_fs21, import_path23, import_os19, import_crypto6, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
11261
+ var import_net2, import_fs21, import_path23, import_os19, import_crypto6, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, CRITICAL_FORENSIC_CATEGORIES, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
11214
11262
  var init_state2 = __esm({
11215
11263
  "src/daemon/state.ts"() {
11216
11264
  "use strict";
@@ -11260,6 +11308,11 @@ var init_state2 = __esm({
11260
11308
  INPUT_PRICE_PER_1M = 3;
11261
11309
  OUTPUT_PRICE_PER_1M = 15;
11262
11310
  BYTES_PER_TOKEN = 4;
11311
+ CRITICAL_FORENSIC_CATEGORIES = /* @__PURE__ */ new Set([
11312
+ "privilege-escalation",
11313
+ "destructive-op",
11314
+ "eval-of-remote"
11315
+ ]);
11263
11316
  WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
11264
11317
  "write",
11265
11318
  "write_file",
@@ -11600,7 +11653,26 @@ function startCloudSync() {
11600
11653
  const recurring = setInterval(() => void syncOnce(), intervalMs);
11601
11654
  recurring.unref();
11602
11655
  }
11603
- var import_fs22, import_https2, import_os20, import_path24, FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS;
11656
+ function startForensicBroadcast() {
11657
+ const tick = async () => {
11658
+ try {
11659
+ const findings = await tickForensicBroadcast(forensicBroadcastOffsets);
11660
+ for (const f of findings) broadcastForensic(f);
11661
+ } catch (err2) {
11662
+ const msg = err2 instanceof Error ? err2.message : String(err2);
11663
+ appendToLog(HOOK_DEBUG_LOG, {
11664
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11665
+ kind: "forensic-broadcast-error",
11666
+ error: msg
11667
+ });
11668
+ }
11669
+ };
11670
+ const initial = setTimeout(() => void tick(), FORENSIC_INITIAL_DELAY_MS);
11671
+ initial.unref();
11672
+ const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
11673
+ recurring.unref();
11674
+ }
11675
+ var import_fs22, import_https2, import_os20, import_path24, FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
11604
11676
  var init_sync = __esm({
11605
11677
  "src/daemon/sync.ts"() {
11606
11678
  "use strict";
@@ -11612,6 +11684,8 @@ var init_sync = __esm({
11612
11684
  init_blast();
11613
11685
  init_dist();
11614
11686
  init_scan_watermark();
11687
+ init_state2();
11688
+ init_audit();
11615
11689
  FINDING_TO_SIGNAL3 = {
11616
11690
  dlp: "dlpFindings",
11617
11691
  pii: "piiFindings",
@@ -11628,6 +11702,9 @@ var init_sync = __esm({
11628
11702
  DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept/policies/sync";
11629
11703
  DEFAULT_INTERVAL_HOURS = 5;
11630
11704
  MIN_INTERVAL_HOURS = 1;
11705
+ FORENSIC_BROADCAST_INTERVAL_MS = 3e4;
11706
+ FORENSIC_INITIAL_DELAY_MS = 5e3;
11707
+ forensicBroadcastOffsets = /* @__PURE__ */ new Map();
11631
11708
  }
11632
11709
  });
11633
11710
 
@@ -11855,6 +11932,7 @@ var init_mcp_tools = __esm({
11855
11932
  function startDaemon() {
11856
11933
  startCostSync();
11857
11934
  startCloudSync();
11935
+ startForensicBroadcast();
11858
11936
  startDlpScanner();
11859
11937
  loadInsightCounts();
11860
11938
  const internalToken = (0, import_crypto7.randomUUID)();
@@ -20785,6 +20863,17 @@ program.command("tail").description("Stream live agent activity to the terminal"
20785
20863
  process.exit(1);
20786
20864
  }
20787
20865
  });
20866
+ program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
20867
+ try {
20868
+ const dashboardPath = import_path49.default.join(__dirname, "dashboard.mjs");
20869
+ const dynamicImport = new Function("id", "return import(id)");
20870
+ const mod = await dynamicImport(`file://${dashboardPath}`);
20871
+ await mod.startMonitor();
20872
+ } catch (err2) {
20873
+ console.error(import_chalk31.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
20874
+ process.exit(1);
20875
+ }
20876
+ });
20788
20877
  registerWatchCommand(program);
20789
20878
  registerMcpGatewayCommand(program);
20790
20879
  registerMcpServerCommand(program);
package/dist/cli.mjs CHANGED
@@ -1692,7 +1692,11 @@ function extractCanonicalFindings(call, ctx) {
1692
1692
  })
1693
1693
  );
1694
1694
  }
1695
- if (PRIVILEGE_ESCALATION_RE.test(command)) {
1695
+ const ast = analyzeShellCommand(command);
1696
+ const sudoVariant = ast.actions.includes("sudo") || ast.actions.includes("su");
1697
+ const chmodVariant = ast.actions.includes("chmod") && (ast.allTokens.includes("777") || ast.allTokens.includes("0777") || ast.allTokens.includes("+x"));
1698
+ const chownVariant = ast.actions.includes("chown") && ast.allTokens.includes("root");
1699
+ if (sudoVariant || chmodVariant || chownVariant) {
1696
1700
  out.push(
1697
1701
  makeFinding({
1698
1702
  type: "privilege-escalation",
@@ -1824,7 +1828,7 @@ function* stringValues(obj, depth = 0) {
1824
1828
  }
1825
1829
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
1826
1830
  }
1827
- var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE, PRIVILEGE_ESCALATION_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE;
1831
+ var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE;
1828
1832
  var init_dist = __esm({
1829
1833
  "packages/policy-engine/dist/index.mjs"() {
1830
1834
  "use strict";
@@ -3224,7 +3228,6 @@ var init_dist = __esm({
3224
3228
  LOOP_THRESHOLD_FOR_WASTE = 3;
3225
3229
  COST_PER_LOOP_ITER_USD = 6e-3;
3226
3230
  DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
3227
- PRIVILEGE_ESCALATION_RE = /\b(sudo|su)\b\s+[a-z]|\bchmod\s+(0?777|\+x)\b|\bchown\s+root\b/i;
3228
3231
  SENSITIVE_PATH_RE = /\.aws\/(credentials|config)\b|\.ssh\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)\b|\.env(\.|$|\b)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
3229
3232
  FILE_TOOLS = /* @__PURE__ */ new Set([
3230
3233
  "read",
@@ -3244,7 +3247,7 @@ var init_dist = __esm({
3244
3247
  PII_PHONE_RE = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/;
3245
3248
  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/;
3246
3249
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
3247
- CANONICAL_EXTRACTOR_VERSION = "canonical-v1";
3250
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
3248
3251
  DEDUPE_PREVIEW_LEN = 120;
3249
3252
  TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
3250
3253
  /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
@@ -4788,7 +4791,7 @@ async function waitForDaemonDecision(id, signal) {
4788
4791
  if (signal) signal.removeEventListener("abort", onAbort);
4789
4792
  }
4790
4793
  }
4791
- async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
4794
+ async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
4792
4795
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
4793
4796
  const res = await fetch(`${base}/check`, {
4794
4797
  method: "POST",
@@ -4799,7 +4802,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
4799
4802
  slackDelegated: true,
4800
4803
  agent: meta?.agent,
4801
4804
  mcpServer: meta?.mcpServer,
4802
- ...riskMetadata && { riskMetadata }
4805
+ ...riskMetadata && { riskMetadata },
4806
+ // fromCLI=true tells the daemon the CLI already sent the activity
4807
+ // event via socket. Same contract as registerDaemonEntry — without
4808
+ // it the daemon double-emits 'activity' for cloud-enforced flows.
4809
+ fromCLI: socketActivitySent !== false,
4810
+ activityId
4803
4811
  }),
4804
4812
  signal: AbortSignal.timeout(3e3)
4805
4813
  });
@@ -5940,7 +5948,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
5940
5948
  let daemonAllowCount = 1;
5941
5949
  if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
5942
5950
  if (cloudEnforced && cloudRequestId) {
5943
- const viewer = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
5951
+ const viewer = await notifyDaemonViewer(
5952
+ toolName,
5953
+ args,
5954
+ meta,
5955
+ riskMetadata,
5956
+ options?.activityId,
5957
+ options?.socketActivitySent
5958
+ ).catch(() => null);
5944
5959
  viewerId = viewer?.id ?? null;
5945
5960
  daemonEntryId = viewerId;
5946
5961
  if (viewer) daemonAllowCount = viewer.allowCount;
@@ -8030,6 +8045,7 @@ __export(scan_watermark_exports, {
8030
8045
  markUploadComplete: () => markUploadComplete,
8031
8046
  saveWatermark: () => saveWatermark,
8032
8047
  scanDelta: () => scanDelta,
8048
+ tickForensicBroadcast: () => tickForensicBroadcast,
8033
8049
  tickScanWatcher: () => tickScanWatcher
8034
8050
  });
8035
8051
  import fs17 from "fs";
@@ -8237,6 +8253,25 @@ function extractFindingsFromLine(line, sessionId, lineIndex) {
8237
8253
  }
8238
8254
  return findings;
8239
8255
  }
8256
+ async function tickForensicBroadcast(offsets) {
8257
+ const out = [];
8258
+ const files = listJsonlFiles();
8259
+ for (const file of files) {
8260
+ const size = fileSize(file);
8261
+ const offset = offsets.get(file);
8262
+ if (offset === void 0) {
8263
+ offsets.set(file, size);
8264
+ continue;
8265
+ }
8266
+ if (size <= offset) continue;
8267
+ const sessionId = path19.basename(file, ".jsonl");
8268
+ const newOffset = await scanDelta(file, offset, (obj, lineIndex) => {
8269
+ out.push(...extractFindingsFromLine(obj, sessionId, lineIndex));
8270
+ });
8271
+ offsets.set(file, newOffset);
8272
+ }
8273
+ return out;
8274
+ }
8240
8275
  function markUploadComplete() {
8241
8276
  const state = loadWatermark();
8242
8277
  if (state.status === "schema-future") return;
@@ -11006,6 +11041,19 @@ data: ${JSON.stringify(data)}
11006
11041
  }
11007
11042
  });
11008
11043
  }
11044
+ function broadcastForensic(finding) {
11045
+ const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
11046
+ const event = {
11047
+ type: "forensic",
11048
+ id: `fnd_${randomUUID3()}`,
11049
+ ts: Date.now(),
11050
+ sessionId: finding.sessionId,
11051
+ category: finding.type,
11052
+ severity
11053
+ };
11054
+ if (finding.patternName !== void 0) event.patternName = finding.patternName;
11055
+ broadcast("forensic", event);
11056
+ }
11009
11057
  function abandonPending() {
11010
11058
  setAbandonTimer(null);
11011
11059
  pending.forEach((entry, id) => {
@@ -11189,7 +11237,7 @@ function bindActivitySocket() {
11189
11237
  });
11190
11238
  activitySocketServer = unixServer;
11191
11239
  }
11192
- var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
11240
+ var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, CRITICAL_FORENSIC_CATEGORIES, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
11193
11241
  var init_state2 = __esm({
11194
11242
  "src/daemon/state.ts"() {
11195
11243
  "use strict";
@@ -11234,6 +11282,11 @@ var init_state2 = __esm({
11234
11282
  INPUT_PRICE_PER_1M = 3;
11235
11283
  OUTPUT_PRICE_PER_1M = 15;
11236
11284
  BYTES_PER_TOKEN = 4;
11285
+ CRITICAL_FORENSIC_CATEGORIES = /* @__PURE__ */ new Set([
11286
+ "privilege-escalation",
11287
+ "destructive-op",
11288
+ "eval-of-remote"
11289
+ ]);
11237
11290
  WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
11238
11291
  "write",
11239
11292
  "write_file",
@@ -11578,7 +11631,26 @@ function startCloudSync() {
11578
11631
  const recurring = setInterval(() => void syncOnce(), intervalMs);
11579
11632
  recurring.unref();
11580
11633
  }
11581
- var FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS;
11634
+ function startForensicBroadcast() {
11635
+ const tick = async () => {
11636
+ try {
11637
+ const findings = await tickForensicBroadcast(forensicBroadcastOffsets);
11638
+ for (const f of findings) broadcastForensic(f);
11639
+ } catch (err2) {
11640
+ const msg = err2 instanceof Error ? err2.message : String(err2);
11641
+ appendToLog(HOOK_DEBUG_LOG, {
11642
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11643
+ kind: "forensic-broadcast-error",
11644
+ error: msg
11645
+ });
11646
+ }
11647
+ };
11648
+ const initial = setTimeout(() => void tick(), FORENSIC_INITIAL_DELAY_MS);
11649
+ initial.unref();
11650
+ const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
11651
+ recurring.unref();
11652
+ }
11653
+ var FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
11582
11654
  var init_sync = __esm({
11583
11655
  "src/daemon/sync.ts"() {
11584
11656
  "use strict";
@@ -11586,6 +11658,8 @@ var init_sync = __esm({
11586
11658
  init_blast();
11587
11659
  init_dist();
11588
11660
  init_scan_watermark();
11661
+ init_state2();
11662
+ init_audit();
11589
11663
  FINDING_TO_SIGNAL3 = {
11590
11664
  dlp: "dlpFindings",
11591
11665
  pii: "piiFindings",
@@ -11602,6 +11676,9 @@ var init_sync = __esm({
11602
11676
  DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept/policies/sync";
11603
11677
  DEFAULT_INTERVAL_HOURS = 5;
11604
11678
  MIN_INTERVAL_HOURS = 1;
11679
+ FORENSIC_BROADCAST_INTERVAL_MS = 3e4;
11680
+ FORENSIC_INITIAL_DELAY_MS = 5e3;
11681
+ forensicBroadcastOffsets = /* @__PURE__ */ new Map();
11605
11682
  }
11606
11683
  });
11607
11684
 
@@ -11835,6 +11912,7 @@ import chalk6 from "chalk";
11835
11912
  function startDaemon() {
11836
11913
  startCostSync();
11837
11914
  startCloudSync();
11915
+ startForensicBroadcast();
11838
11916
  startDlpScanner();
11839
11917
  loadInsightCounts();
11840
11918
  const internalToken = randomUUID4();
@@ -20757,6 +20835,17 @@ program.command("tail").description("Stream live agent activity to the terminal"
20757
20835
  process.exit(1);
20758
20836
  }
20759
20837
  });
20838
+ program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
20839
+ try {
20840
+ const dashboardPath = path49.join(__dirname, "dashboard.mjs");
20841
+ const dynamicImport = new Function("id", "return import(id)");
20842
+ const mod = await dynamicImport(`file://${dashboardPath}`);
20843
+ await mod.startMonitor();
20844
+ } catch (err2) {
20845
+ console.error(chalk31.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
20846
+ process.exit(1);
20847
+ }
20848
+ });
20760
20849
  registerWatchCommand(program);
20761
20850
  registerMcpGatewayCommand(program);
20762
20851
  registerMcpServerCommand(program);