@node9/proxy 1.5.0 → 1.5.2

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
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,19 +30,52 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
30
- // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
33
- protect: () => protect
33
+ // src/audit/hasher.ts
34
+ function canonicalise(value) {
35
+ return _canonicalise(value, /* @__PURE__ */ new WeakSet());
36
+ }
37
+ function _canonicalise(value, seen) {
38
+ if (value === null || typeof value !== "object") return value;
39
+ if (value instanceof Date) return value.toISOString();
40
+ if (value instanceof RegExp) return value.toString();
41
+ if (Buffer.isBuffer(value)) return value.toString("base64");
42
+ if (seen.has(value)) return "[Circular]";
43
+ seen.add(value);
44
+ let result;
45
+ if (Array.isArray(value)) {
46
+ result = value.map((v) => _canonicalise(v, seen));
47
+ } else {
48
+ const obj = value;
49
+ result = Object.fromEntries(
50
+ Object.keys(obj).sort().map((k) => [k, _canonicalise(obj[k], seen)])
51
+ );
52
+ }
53
+ seen.delete(value);
54
+ return result;
55
+ }
56
+ function hashArgs(args) {
57
+ const canonical = JSON.stringify(canonicalise(args) ?? null);
58
+ return (0, import_crypto.createHash)("sha256").update(canonical).digest("hex").slice(0, 32);
59
+ }
60
+ var import_crypto;
61
+ var init_hasher = __esm({
62
+ "src/audit/hasher.ts"() {
63
+ "use strict";
64
+ import_crypto = require("crypto");
65
+ }
34
66
  });
35
- module.exports = __toCommonJS(src_exports);
36
67
 
37
68
  // src/audit/index.ts
38
- var import_fs = __toESM(require("fs"));
39
- var import_path = __toESM(require("path"));
40
- var import_os = __toESM(require("os"));
41
- var LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
42
- var HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
69
+ var audit_exports = {};
70
+ __export(audit_exports, {
71
+ HOOK_DEBUG_LOG: () => HOOK_DEBUG_LOG,
72
+ LOCAL_AUDIT_LOG: () => LOCAL_AUDIT_LOG,
73
+ appendConfigAudit: () => appendConfigAudit,
74
+ appendHookDebug: () => appendHookDebug,
75
+ appendLocalAudit: () => appendLocalAudit,
76
+ appendToLog: () => appendToLog,
77
+ redactSecrets: () => redactSecrets
78
+ });
43
79
  function redactSecrets(text) {
44
80
  if (!text) return text;
45
81
  let redacted = text;
@@ -61,24 +97,24 @@ function appendToLog(logPath, entry) {
61
97
  } catch {
62
98
  }
63
99
  }
64
- function appendHookDebug(toolName, args, meta) {
65
- const safeArgs = args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {};
100
+ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
101
+ const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
66
102
  appendToLog(HOOK_DEBUG_LOG, {
67
103
  ts: (/* @__PURE__ */ new Date()).toISOString(),
68
104
  tool: toolName,
69
- args: safeArgs,
105
+ ...argsField,
70
106
  agent: meta?.agent,
71
107
  mcpServer: meta?.mcpServer,
72
108
  hostname: import_os.default.hostname(),
73
109
  cwd: process.cwd()
74
110
  });
75
111
  }
76
- function appendLocalAudit(toolName, args, decision, checkedBy, meta) {
77
- const safeArgs = args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {};
112
+ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
113
+ const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
78
114
  appendToLog(LOCAL_AUDIT_LOG, {
79
115
  ts: (/* @__PURE__ */ new Date()).toISOString(),
80
116
  tool: toolName,
81
- args: safeArgs,
117
+ ...argsField,
82
118
  decision,
83
119
  checkedBy,
84
120
  agent: meta?.agent,
@@ -86,6 +122,35 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta) {
86
122
  hostname: import_os.default.hostname()
87
123
  });
88
124
  }
125
+ function appendConfigAudit(entry) {
126
+ appendToLog(LOCAL_AUDIT_LOG, {
127
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
128
+ ...entry,
129
+ hostname: import_os.default.hostname()
130
+ });
131
+ }
132
+ var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG;
133
+ var init_audit = __esm({
134
+ "src/audit/index.ts"() {
135
+ "use strict";
136
+ import_fs = __toESM(require("fs"));
137
+ import_path = __toESM(require("path"));
138
+ import_os = __toESM(require("os"));
139
+ init_hasher();
140
+ LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
141
+ HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
142
+ }
143
+ });
144
+
145
+ // src/index.ts
146
+ var src_exports = {};
147
+ __export(src_exports, {
148
+ protect: () => protect
149
+ });
150
+ module.exports = __toCommonJS(src_exports);
151
+
152
+ // src/core.ts
153
+ init_audit();
89
154
 
90
155
  // src/config/index.ts
91
156
  var import_fs3 = __toESM(require("fs"));
@@ -154,7 +219,8 @@ var ConfigFileSchema = import_zod.z.object({
154
219
  environment: import_zod.z.string().optional(),
155
220
  slackEnabled: import_zod.z.boolean().optional(),
156
221
  enableTrustSessions: import_zod.z.boolean().optional(),
157
- allowGlobalPause: import_zod.z.boolean().optional()
222
+ allowGlobalPause: import_zod.z.boolean().optional(),
223
+ auditHashArgs: import_zod.z.boolean().optional()
158
224
  }).optional(),
159
225
  policy: import_zod.z.object({
160
226
  sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
@@ -450,6 +516,7 @@ var DEFAULT_CONFIG = {
450
516
  approvalTimeoutMs: 12e4,
451
517
  // 120-second auto-deny timeout
452
518
  flightRecorder: true,
519
+ auditHashArgs: true,
453
520
  approvers: { native: true, browser: true, cloud: false, terminal: true }
454
521
  },
455
522
  policy: {
@@ -2017,6 +2084,45 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
2017
2084
  const { id, allowCount } = await res.json();
2018
2085
  return { id, allowCount: allowCount ?? 1 };
2019
2086
  }
2087
+ async function notifyTaint(filePath, source) {
2088
+ if (!isDaemonRunning()) return;
2089
+ const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
2090
+ try {
2091
+ await fetch(`${base}/taint`, {
2092
+ method: "POST",
2093
+ headers: { "Content-Type": "application/json" },
2094
+ body: JSON.stringify({ path: filePath, source }),
2095
+ signal: AbortSignal.timeout(1e3)
2096
+ });
2097
+ } catch {
2098
+ }
2099
+ }
2100
+ async function checkTaint(paths) {
2101
+ if (paths.length === 0) return { tainted: false };
2102
+ if (!isDaemonRunning()) return { tainted: false, daemonUnavailable: true };
2103
+ const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
2104
+ try {
2105
+ const res = await fetch(`${base}/taint/check`, {
2106
+ method: "POST",
2107
+ headers: { "Content-Type": "application/json" },
2108
+ body: JSON.stringify({ paths }),
2109
+ signal: AbortSignal.timeout(2e3)
2110
+ });
2111
+ return await res.json();
2112
+ } catch (err) {
2113
+ try {
2114
+ const { appendToLog: appendToLog2, HOOK_DEBUG_LOG: HOOK_DEBUG_LOG2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
2115
+ appendToLog2(HOOK_DEBUG_LOG2, {
2116
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2117
+ event: "checkTaint-error",
2118
+ error: String(err),
2119
+ paths
2120
+ });
2121
+ } catch {
2122
+ }
2123
+ return { tainted: false, daemonUnavailable: true };
2124
+ }
2125
+ }
2020
2126
  async function resolveViaDaemon(id, decision, internalToken, source) {
2021
2127
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
2022
2128
  await fetch(`${base}/resolve/${id}`, {
@@ -2031,7 +2137,7 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
2031
2137
  var import_net = __toESM(require("net"));
2032
2138
  var import_path13 = __toESM(require("path"));
2033
2139
  var import_os9 = __toESM(require("os"));
2034
- var import_crypto = require("crypto");
2140
+ var import_crypto2 = require("crypto");
2035
2141
 
2036
2142
  // src/ui/native.ts
2037
2143
  var import_child_process2 = require("child_process");
@@ -2353,9 +2459,13 @@ end run`;
2353
2459
  });
2354
2460
  }
2355
2461
 
2462
+ // src/auth/orchestrator.ts
2463
+ init_audit();
2464
+
2356
2465
  // src/auth/cloud.ts
2357
2466
  var import_fs9 = __toESM(require("fs"));
2358
2467
  var import_os8 = __toESM(require("os"));
2468
+ init_audit();
2359
2469
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2360
2470
  return fetch(`${creds.apiUrl}/audit`, {
2361
2471
  method: "POST",
@@ -2464,6 +2574,51 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2464
2574
  }
2465
2575
 
2466
2576
  // src/auth/orchestrator.ts
2577
+ var WRITE_TOOLS = /* @__PURE__ */ new Set([
2578
+ "write",
2579
+ "write_file",
2580
+ "create_file",
2581
+ "edit",
2582
+ "multiedit",
2583
+ "str_replace_based_edit_tool",
2584
+ "replace",
2585
+ "notebook_edit",
2586
+ "notebookedit"
2587
+ ]);
2588
+ function isWriteTool(toolName) {
2589
+ const t = toolName.toLowerCase().replace(/[^a-z_]/g, "_");
2590
+ return WRITE_TOOLS.has(t);
2591
+ }
2592
+ function extractFilePaths(toolName, args) {
2593
+ const paths = [];
2594
+ if (!args || typeof args !== "object" || Array.isArray(args)) return paths;
2595
+ const a = args;
2596
+ for (const key of ["file_path", "path", "filename", "source", "src", "input"]) {
2597
+ if (typeof a[key] === "string" && a[key]) paths.push(a[key]);
2598
+ }
2599
+ const cmd = typeof a.command === "string" ? a.command : typeof a.cmd === "string" ? a.cmd : "";
2600
+ if (cmd) {
2601
+ for (const m of cmd.matchAll(/(?:-T|--upload-file|--data(?:-binary)?)\s+@?(\S+)/g)) {
2602
+ paths.push(m[1]);
2603
+ }
2604
+ for (const m of cmd.matchAll(/\b(?:scp|rsync)\s+(?:-\S+\s+)*(\S+)\s+\S+@/g)) {
2605
+ paths.push(m[1]);
2606
+ }
2607
+ for (const m of cmd.matchAll(/<\s*(\S+)/g)) {
2608
+ paths.push(m[1]);
2609
+ }
2610
+ }
2611
+ return paths.filter(Boolean);
2612
+ }
2613
+ function isNetworkTool(toolName, args) {
2614
+ const t = toolName.toLowerCase();
2615
+ if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
2616
+ const a = args;
2617
+ const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
2618
+ return /\b(curl|wget|scp|rsync|nc|ncat|netcat|ssh)\b/.test(cmd);
2619
+ }
2620
+ return false;
2621
+ }
2467
2622
  var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path13.default.join(import_os9.default.tmpdir(), "node9-activity.sock");
2468
2623
  function notifyActivity(data) {
2469
2624
  return new Promise((resolve) => {
@@ -2482,7 +2637,7 @@ function notifyActivity(data) {
2482
2637
  }
2483
2638
  async function authorizeHeadless(toolName, args, meta, options) {
2484
2639
  if (!options?.calledFromDaemon) {
2485
- const actId = (0, import_crypto.randomUUID)();
2640
+ const actId = (0, import_crypto2.randomUUID)();
2486
2641
  const actTs = Date.now();
2487
2642
  await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
2488
2643
  const result = await _authorizeHeadlessCore(toolName, args, meta, {
@@ -2494,7 +2649,7 @@ async function authorizeHeadless(toolName, args, meta, options) {
2494
2649
  id: actId,
2495
2650
  tool: toolName,
2496
2651
  ts: actTs,
2497
- status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
2652
+ status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
2498
2653
  label: result.blockedByLabel
2499
2654
  });
2500
2655
  }
@@ -2508,6 +2663,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2508
2663
  if (pauseState.paused) return { approved: true, checkedBy: "paused" };
2509
2664
  const creds = getCredentials();
2510
2665
  const config = getConfig(options?.cwd);
2666
+ const hashAuditArgs = config.settings.auditHashArgs === true;
2511
2667
  const isTestEnv2 = !!(process.env.VITEST || process.env.NODE_ENV === "test" || process.env.CI || process.env.NODE9_TESTING === "1");
2512
2668
  const approvers = {
2513
2669
  ...config.settings.approvers || { native: true, browser: true, cloud: true, terminal: true }
@@ -2518,13 +2674,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2518
2674
  approvers.terminal = false;
2519
2675
  }
2520
2676
  if (config.settings.enableHookLogDebug && !isTestEnv2) {
2521
- appendHookDebug(toolName, args, meta);
2677
+ appendHookDebug(toolName, args, meta, hashAuditArgs);
2522
2678
  }
2523
2679
  const isManual = meta?.agent === "Terminal";
2524
2680
  let explainableLabel = "Local Config";
2525
2681
  let policyMatchedField;
2526
2682
  let policyMatchedWord;
2527
2683
  let riskMetadata;
2684
+ let taintWarning = null;
2685
+ if (isNetworkTool(toolName, args)) {
2686
+ const filePaths = extractFilePaths(toolName, args);
2687
+ if (filePaths.length > 0) {
2688
+ const taintResult = await checkTaint(filePaths);
2689
+ if (taintResult.tainted && taintResult.record) {
2690
+ const { path: taintedPath, source: taintSource } = taintResult.record;
2691
+ taintWarning = `\u26A0\uFE0F ${taintedPath} was flagged by ${taintSource} \u2014 this file may contain sensitive data`;
2692
+ } else if (taintResult.daemonUnavailable) {
2693
+ taintWarning = `\u26A0\uFE0F Taint service unavailable \u2014 cannot verify if ${filePaths.join(", ")} is clean`;
2694
+ }
2695
+ }
2696
+ }
2528
2697
  if (config.policy.dlp.enabled && (!isIgnoredTool(toolName) || config.policy.dlp.scanIgnoredTools)) {
2529
2698
  const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : {};
2530
2699
  const filePath = String(argsObj.file_path ?? argsObj.path ?? argsObj.filename ?? "");
@@ -2532,7 +2701,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2532
2701
  if (dlpMatch) {
2533
2702
  const dlpReason = `\u{1F6A8} DATA LOSS PREVENTION: ${dlpMatch.patternName} detected in field "${dlpMatch.fieldPath}" (${dlpMatch.redactedSample})`;
2534
2703
  if (dlpMatch.severity === "block") {
2535
- if (!isManual) appendLocalAudit(toolName, args, "deny", "dlp-block", meta);
2704
+ if (!isManual) appendLocalAudit(toolName, args, "deny", "dlp-block", meta, true);
2705
+ if (isWriteTool(toolName) && filePath) {
2706
+ await notifyTaint(filePath, `DLP:${dlpMatch.patternName}`);
2707
+ }
2536
2708
  return {
2537
2709
  approved: false,
2538
2710
  reason: dlpReason,
@@ -2540,7 +2712,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2540
2712
  blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
2541
2713
  };
2542
2714
  }
2543
- if (!isManual) appendLocalAudit(toolName, args, "allow", "dlp-review-flagged", meta);
2715
+ if (!isManual)
2716
+ appendLocalAudit(toolName, args, "allow", "dlp-review-flagged", meta, hashAuditArgs);
2544
2717
  explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
2545
2718
  }
2546
2719
  }
@@ -2548,7 +2721,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2548
2721
  if (!isIgnoredTool(toolName)) {
2549
2722
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
2550
2723
  if (policyResult.decision === "review") {
2551
- appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
2724
+ appendLocalAudit(toolName, args, "allow", "audit-mode", meta, hashAuditArgs);
2552
2725
  if (approvers.cloud && creds?.apiKey) {
2553
2726
  await auditLocalAllow(toolName, args, "audit-mode", creds, meta);
2554
2727
  }
@@ -2556,22 +2729,23 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2556
2729
  }
2557
2730
  return { approved: true, checkedBy: "audit" };
2558
2731
  }
2559
- if (!isIgnoredTool(toolName)) {
2732
+ if (!taintWarning && !isIgnoredTool(toolName)) {
2560
2733
  if (getActiveTrustSession(toolName)) {
2561
2734
  if (approvers.cloud && creds?.apiKey)
2562
2735
  await auditLocalAllow(toolName, args, "trust", creds, meta);
2563
- if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta);
2736
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
2564
2737
  return { approved: true, checkedBy: "trust" };
2565
2738
  }
2566
2739
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
2567
2740
  if (policyResult.decision === "allow") {
2568
2741
  if (approvers.cloud && creds?.apiKey)
2569
2742
  auditLocalAllow(toolName, args, "local-policy", creds, meta);
2570
- if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta);
2743
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
2571
2744
  return { approved: true, checkedBy: "local-policy" };
2572
2745
  }
2573
2746
  if (policyResult.decision === "block") {
2574
- if (!isManual) appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta);
2747
+ if (!isManual)
2748
+ appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
2575
2749
  return {
2576
2750
  approved: false,
2577
2751
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
@@ -2590,15 +2764,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2590
2764
  policyMatchedWord,
2591
2765
  policyResult.ruleName
2592
2766
  );
2593
- const persistent = getPersistentDecision(toolName);
2767
+ const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
2594
2768
  if (persistent === "allow") {
2595
2769
  if (approvers.cloud && creds?.apiKey)
2596
2770
  await auditLocalAllow(toolName, args, "persistent", creds, meta);
2597
- if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta);
2771
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
2598
2772
  return { approved: true, checkedBy: "persistent" };
2599
2773
  }
2600
2774
  if (persistent === "deny") {
2601
- if (!isManual) appendLocalAudit(toolName, args, "deny", "persistent-deny", meta);
2775
+ if (!isManual)
2776
+ appendLocalAudit(toolName, args, "deny", "persistent-deny", meta, hashAuditArgs);
2602
2777
  return {
2603
2778
  approved: false,
2604
2779
  reason: `This tool ("${toolName}") is explicitly listed in your 'Always Deny' list.`,
@@ -2606,10 +2781,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2606
2781
  blockedByLabel: "Persistent User Rule"
2607
2782
  };
2608
2783
  }
2609
- } else {
2610
- if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta);
2784
+ } else if (!taintWarning) {
2785
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
2611
2786
  return { approved: true };
2612
2787
  }
2788
+ if (taintWarning) {
2789
+ explainableLabel = "\u{1F534} Node9 Taint (Exfiltration Prevention)";
2790
+ riskMetadata = computeRiskMetadata(
2791
+ args,
2792
+ 7,
2793
+ explainableLabel,
2794
+ void 0,
2795
+ void 0,
2796
+ taintWarning
2797
+ );
2798
+ }
2613
2799
  let cloudRequestId = null;
2614
2800
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
2615
2801
  if (cloudEnforced) {
@@ -2628,7 +2814,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2628
2814
  };
2629
2815
  }
2630
2816
  cloudRequestId = initResult.requestId || null;
2631
- explainableLabel = "Organization Policy (SaaS)";
2817
+ if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
2632
2818
  } catch {
2633
2819
  }
2634
2820
  }
@@ -2815,7 +3001,8 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
2815
3001
  args,
2816
3002
  finalResult.approved ? "allow" : "deny",
2817
3003
  finalResult.checkedBy || finalResult.blockedBy || "unknown",
2818
- meta
3004
+ meta,
3005
+ hashAuditArgs
2819
3006
  );
2820
3007
  }
2821
3008
  return finalResult;