@node9/proxy 1.9.3 → 1.10.1
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/README.md +32 -3
- package/dist/cli.js +1372 -625
- package/dist/cli.mjs +1354 -607
- package/dist/index.js +128 -25
- package/dist/index.mjs +126 -23
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -56,6 +56,11 @@ __export(audit_exports, {
|
|
|
56
56
|
import fs from "fs";
|
|
57
57
|
import path from "path";
|
|
58
58
|
import os from "os";
|
|
59
|
+
function isTestCall(toolName, args) {
|
|
60
|
+
if (toolName !== "Bash" && toolName !== "bash") return false;
|
|
61
|
+
const cmd = args?.command;
|
|
62
|
+
return typeof cmd === "string" && TEST_COMMAND_RE.test(cmd);
|
|
63
|
+
}
|
|
59
64
|
function redactSecrets(text) {
|
|
60
65
|
if (!text) return text;
|
|
61
66
|
let redacted = text;
|
|
@@ -91,12 +96,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
91
96
|
}
|
|
92
97
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
93
98
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
99
|
+
const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
|
|
94
100
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
95
101
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
96
102
|
tool: toolName,
|
|
97
103
|
...argsField,
|
|
98
104
|
decision,
|
|
99
105
|
checkedBy,
|
|
106
|
+
...testRun,
|
|
100
107
|
agent: meta?.agent,
|
|
101
108
|
mcpServer: meta?.mcpServer,
|
|
102
109
|
hostname: os.hostname()
|
|
@@ -109,13 +116,14 @@ function appendConfigAudit(entry) {
|
|
|
109
116
|
hostname: os.hostname()
|
|
110
117
|
});
|
|
111
118
|
}
|
|
112
|
-
var LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG;
|
|
119
|
+
var LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, TEST_COMMAND_RE;
|
|
113
120
|
var init_audit = __esm({
|
|
114
121
|
"src/audit/index.ts"() {
|
|
115
122
|
"use strict";
|
|
116
123
|
init_hasher();
|
|
117
124
|
LOCAL_AUDIT_LOG = path.join(os.homedir(), ".node9", "audit.log");
|
|
118
125
|
HOOK_DEBUG_LOG = path.join(os.homedir(), ".node9", "hook-debug.log");
|
|
126
|
+
TEST_COMMAND_RE = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
119
127
|
}
|
|
120
128
|
});
|
|
121
129
|
|
|
@@ -200,7 +208,8 @@ var ConfigFileSchema = z.object({
|
|
|
200
208
|
slackEnabled: z.boolean().optional(),
|
|
201
209
|
enableTrustSessions: z.boolean().optional(),
|
|
202
210
|
allowGlobalPause: z.boolean().optional(),
|
|
203
|
-
auditHashArgs: z.boolean().optional()
|
|
211
|
+
auditHashArgs: z.boolean().optional(),
|
|
212
|
+
agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional()
|
|
204
213
|
}).optional(),
|
|
205
214
|
policy: z.object({
|
|
206
215
|
sandboxPaths: z.array(z.string()).optional(),
|
|
@@ -216,6 +225,11 @@ var ConfigFileSchema = z.object({
|
|
|
216
225
|
dlp: z.object({
|
|
217
226
|
enabled: z.boolean().optional(),
|
|
218
227
|
scanIgnoredTools: z.boolean().optional()
|
|
228
|
+
}).optional(),
|
|
229
|
+
loopDetection: z.object({
|
|
230
|
+
enabled: z.boolean().optional(),
|
|
231
|
+
threshold: z.number().min(2).optional(),
|
|
232
|
+
windowSeconds: z.number().min(10).optional()
|
|
219
233
|
}).optional()
|
|
220
234
|
}).optional(),
|
|
221
235
|
environments: z.record(z.object({ requireApproval: z.boolean().optional() })).optional()
|
|
@@ -237,8 +251,8 @@ function sanitizeConfig(raw) {
|
|
|
237
251
|
}
|
|
238
252
|
}
|
|
239
253
|
const lines = result.error.issues.map((issue) => {
|
|
240
|
-
const
|
|
241
|
-
return ` \u2022 ${
|
|
254
|
+
const path15 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
255
|
+
return ` \u2022 ${path15}: ${issue.message}`;
|
|
242
256
|
});
|
|
243
257
|
return {
|
|
244
258
|
sanitized,
|
|
@@ -571,7 +585,8 @@ var DEFAULT_CONFIG = {
|
|
|
571
585
|
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."
|
|
572
586
|
}
|
|
573
587
|
],
|
|
574
|
-
dlp: { enabled: true, scanIgnoredTools: true }
|
|
588
|
+
dlp: { enabled: true, scanIgnoredTools: true },
|
|
589
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
575
590
|
},
|
|
576
591
|
environments: {}
|
|
577
592
|
};
|
|
@@ -693,7 +708,8 @@ function getConfig(cwd) {
|
|
|
693
708
|
onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
|
|
694
709
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
695
710
|
},
|
|
696
|
-
dlp: { ...DEFAULT_CONFIG.policy.dlp }
|
|
711
|
+
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
712
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
697
713
|
};
|
|
698
714
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
699
715
|
const applyLayer = (source) => {
|
|
@@ -732,6 +748,13 @@ function getConfig(cwd) {
|
|
|
732
748
|
if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
|
|
733
749
|
if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
|
|
734
750
|
}
|
|
751
|
+
if (p.loopDetection) {
|
|
752
|
+
const ld = p.loopDetection;
|
|
753
|
+
if (ld.enabled !== void 0) mergedPolicy.loopDetection.enabled = ld.enabled;
|
|
754
|
+
if (ld.threshold !== void 0) mergedPolicy.loopDetection.threshold = ld.threshold;
|
|
755
|
+
if (ld.windowSeconds !== void 0)
|
|
756
|
+
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
757
|
+
}
|
|
735
758
|
const envs = source.environments || {};
|
|
736
759
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
737
760
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -1506,9 +1529,9 @@ function matchesPattern(text, patterns) {
|
|
|
1506
1529
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1507
1530
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1508
1531
|
}
|
|
1509
|
-
function getNestedValue(obj,
|
|
1532
|
+
function getNestedValue(obj, path15) {
|
|
1510
1533
|
if (!obj || typeof obj !== "object") return null;
|
|
1511
|
-
return
|
|
1534
|
+
return path15.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1512
1535
|
}
|
|
1513
1536
|
function evaluateSmartConditions(args, rule) {
|
|
1514
1537
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
@@ -2269,11 +2292,12 @@ ${smartTruncate(str, 500)}`
|
|
|
2269
2292
|
function escapePango(text) {
|
|
2270
2293
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2271
2294
|
}
|
|
2272
|
-
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2295
|
+
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2273
2296
|
const lines = [];
|
|
2274
2297
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2275
2298
|
lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
|
|
2276
2299
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2300
|
+
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2277
2301
|
lines.push("");
|
|
2278
2302
|
lines.push(formattedArgs);
|
|
2279
2303
|
if (allowCount >= 3) {
|
|
@@ -2286,7 +2310,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2286
2310
|
}
|
|
2287
2311
|
return lines.join("\n");
|
|
2288
2312
|
}
|
|
2289
|
-
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2313
|
+
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2290
2314
|
const lines = [];
|
|
2291
2315
|
if (locked) {
|
|
2292
2316
|
lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
|
|
@@ -2296,6 +2320,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2296
2320
|
`<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
|
|
2297
2321
|
);
|
|
2298
2322
|
lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
|
|
2323
|
+
if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
|
|
2299
2324
|
lines.push("");
|
|
2300
2325
|
lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
|
|
2301
2326
|
if (allowCount >= 3) {
|
|
@@ -2312,7 +2337,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2312
2337
|
}
|
|
2313
2338
|
return lines.join("\n");
|
|
2314
2339
|
}
|
|
2315
|
-
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
|
|
2340
|
+
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
|
|
2316
2341
|
if (isTestEnv()) return "deny";
|
|
2317
2342
|
const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
|
|
2318
2343
|
const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
|
|
@@ -2323,7 +2348,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
|
|
|
2323
2348
|
agent,
|
|
2324
2349
|
explainableLabel,
|
|
2325
2350
|
locked,
|
|
2326
|
-
allowCount
|
|
2351
|
+
allowCount,
|
|
2352
|
+
ruleDescription
|
|
2327
2353
|
);
|
|
2328
2354
|
return new Promise((resolve) => {
|
|
2329
2355
|
let childProcess = null;
|
|
@@ -2357,7 +2383,8 @@ end run`;
|
|
|
2357
2383
|
agent,
|
|
2358
2384
|
explainableLabel,
|
|
2359
2385
|
locked,
|
|
2360
|
-
allowCount
|
|
2386
|
+
allowCount,
|
|
2387
|
+
ruleDescription
|
|
2361
2388
|
);
|
|
2362
2389
|
const argsList = [
|
|
2363
2390
|
locked ? "--info" : "--question",
|
|
@@ -2425,7 +2452,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2425
2452
|
}).catch(() => {
|
|
2426
2453
|
});
|
|
2427
2454
|
}
|
|
2428
|
-
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
2455
|
+
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
2429
2456
|
const controller = new AbortController();
|
|
2430
2457
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2431
2458
|
if (!creds.apiKey) throw new Error("Node9 API Key is missing");
|
|
@@ -2470,7 +2497,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2470
2497
|
platform: os8.platform()
|
|
2471
2498
|
},
|
|
2472
2499
|
...riskMetadata && { riskMetadata },
|
|
2473
|
-
...ciContext && { ciContext }
|
|
2500
|
+
...ciContext && { ciContext },
|
|
2501
|
+
...agentPolicy && { policy: agentPolicy },
|
|
2502
|
+
...forceReview && { forceReview: true }
|
|
2474
2503
|
}),
|
|
2475
2504
|
signal: controller.signal
|
|
2476
2505
|
});
|
|
@@ -2544,6 +2573,52 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
2544
2573
|
}
|
|
2545
2574
|
}
|
|
2546
2575
|
|
|
2576
|
+
// src/loop-detector.ts
|
|
2577
|
+
import fs10 from "fs";
|
|
2578
|
+
import path14 from "path";
|
|
2579
|
+
import os9 from "os";
|
|
2580
|
+
import crypto from "crypto";
|
|
2581
|
+
function loopStateFile() {
|
|
2582
|
+
return path14.join(os9.homedir(), ".node9", "loop-state.json");
|
|
2583
|
+
}
|
|
2584
|
+
var MAX_RECORDS = 500;
|
|
2585
|
+
function computeArgsHash(args) {
|
|
2586
|
+
const str = JSON.stringify(args ?? "");
|
|
2587
|
+
return crypto.createHash("sha256").update(str).digest("hex").slice(0, 16);
|
|
2588
|
+
}
|
|
2589
|
+
function readState() {
|
|
2590
|
+
try {
|
|
2591
|
+
if (!fs10.existsSync(loopStateFile())) return [];
|
|
2592
|
+
const raw = fs10.readFileSync(loopStateFile(), "utf-8");
|
|
2593
|
+
const parsed = JSON.parse(raw);
|
|
2594
|
+
if (!Array.isArray(parsed)) return [];
|
|
2595
|
+
return parsed;
|
|
2596
|
+
} catch {
|
|
2597
|
+
return [];
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
function writeState(records) {
|
|
2601
|
+
const dir = path14.dirname(loopStateFile());
|
|
2602
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
2603
|
+
const tmpPath = `${loopStateFile()}.${os9.hostname()}.${process.pid}.tmp`;
|
|
2604
|
+
fs10.writeFileSync(tmpPath, JSON.stringify(records));
|
|
2605
|
+
fs10.renameSync(tmpPath, loopStateFile());
|
|
2606
|
+
}
|
|
2607
|
+
function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
2608
|
+
try {
|
|
2609
|
+
const hash = computeArgsHash(args);
|
|
2610
|
+
const now = Date.now();
|
|
2611
|
+
const cutoff = now - windowMs;
|
|
2612
|
+
const records = readState().filter((r) => r.ts >= cutoff);
|
|
2613
|
+
records.push({ t: tool, h: hash, ts: now });
|
|
2614
|
+
const count = records.filter((r) => r.t === tool && r.h === hash).length;
|
|
2615
|
+
writeState(records.slice(-MAX_RECORDS));
|
|
2616
|
+
return { looping: count >= threshold, count };
|
|
2617
|
+
} catch {
|
|
2618
|
+
return { looping: false, count: 0 };
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2547
2622
|
// src/auth/orchestrator.ts
|
|
2548
2623
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
2549
2624
|
"write",
|
|
@@ -2642,6 +2717,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2642
2717
|
let explainableLabel = "Local Config";
|
|
2643
2718
|
let policyMatchedField;
|
|
2644
2719
|
let policyMatchedWord;
|
|
2720
|
+
let policyRuleDescription;
|
|
2645
2721
|
let riskMetadata;
|
|
2646
2722
|
let statefulRecoveryCommand;
|
|
2647
2723
|
let localSmartRuleMatched = false;
|
|
@@ -2735,6 +2811,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2735
2811
|
return { approved: true, checkedBy: "audit" };
|
|
2736
2812
|
}
|
|
2737
2813
|
if (!taintWarning && !isIgnoredTool(toolName)) {
|
|
2814
|
+
const ld = config.policy.loopDetection;
|
|
2815
|
+
if (ld.enabled) {
|
|
2816
|
+
const loopResult = recordAndCheck(toolName, args, ld.threshold, ld.windowSeconds * 1e3);
|
|
2817
|
+
if (loopResult.looping) {
|
|
2818
|
+
const reason = `It looks like you've called "${toolName}" ${loopResult.count} times with identical arguments in the last ${ld.windowSeconds}s. Are you stuck? Step back and reconsider your approach \u2014 what are you actually trying to accomplish, and is there a different way to get there?`;
|
|
2819
|
+
if (!isManual)
|
|
2820
|
+
appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
|
|
2821
|
+
return {
|
|
2822
|
+
approved: false,
|
|
2823
|
+
reason,
|
|
2824
|
+
blockedBy: "loop-detection",
|
|
2825
|
+
blockedByLabel: "\u{1F504} Loop Detected"
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2738
2829
|
if (getActiveTrustSession(toolName)) {
|
|
2739
2830
|
if (approvers.cloud && creds?.apiKey)
|
|
2740
2831
|
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
@@ -2790,6 +2881,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2790
2881
|
policyMatchedField = policyResult.matchedField;
|
|
2791
2882
|
policyMatchedWord = policyResult.matchedWord;
|
|
2792
2883
|
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
2884
|
+
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
2885
|
+
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
2793
2886
|
riskMetadata = computeRiskMetadata(
|
|
2794
2887
|
args,
|
|
2795
2888
|
policyResult.tier ?? 6,
|
|
@@ -2798,6 +2891,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2798
2891
|
policyMatchedWord,
|
|
2799
2892
|
policyResult.ruleName
|
|
2800
2893
|
);
|
|
2894
|
+
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
2801
2895
|
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
2802
2896
|
if (persistent === "allow") {
|
|
2803
2897
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -2832,9 +2926,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2832
2926
|
}
|
|
2833
2927
|
let cloudRequestId = null;
|
|
2834
2928
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
2835
|
-
|
|
2929
|
+
const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
|
|
2930
|
+
if (cloudEnforced) {
|
|
2836
2931
|
try {
|
|
2837
|
-
const initResult = await initNode9SaaS(
|
|
2932
|
+
const initResult = await initNode9SaaS(
|
|
2933
|
+
toolName,
|
|
2934
|
+
args,
|
|
2935
|
+
creds,
|
|
2936
|
+
meta,
|
|
2937
|
+
riskMetadata,
|
|
2938
|
+
config.settings.agentPolicy,
|
|
2939
|
+
forceReview
|
|
2940
|
+
);
|
|
2838
2941
|
if (!initResult.pending) {
|
|
2839
2942
|
if (initResult.shadowMode) {
|
|
2840
2943
|
return { approved: true, checkedBy: "cloud" };
|
|
@@ -2849,9 +2952,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2849
2952
|
};
|
|
2850
2953
|
}
|
|
2851
2954
|
}
|
|
2852
|
-
if (
|
|
2853
|
-
cloudRequestId = initResult.requestId || null;
|
|
2854
|
-
}
|
|
2955
|
+
if (initResult.pending) cloudRequestId = initResult.requestId || null;
|
|
2855
2956
|
if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
|
|
2856
2957
|
} catch {
|
|
2857
2958
|
}
|
|
@@ -2908,7 +3009,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2908
3009
|
}
|
|
2909
3010
|
}
|
|
2910
3011
|
}
|
|
2911
|
-
if (cloudEnforced && cloudRequestId
|
|
3012
|
+
if (cloudEnforced && cloudRequestId) {
|
|
2912
3013
|
racePromises.push(
|
|
2913
3014
|
(async () => {
|
|
2914
3015
|
try {
|
|
@@ -2940,7 +3041,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2940
3041
|
signal,
|
|
2941
3042
|
policyMatchedField,
|
|
2942
3043
|
policyMatchedWord,
|
|
2943
|
-
daemonAllowCount
|
|
3044
|
+
daemonAllowCount,
|
|
3045
|
+
riskMetadata?.ruleDescription
|
|
2944
3046
|
);
|
|
2945
3047
|
if (decision === "always_allow") {
|
|
2946
3048
|
writeTrustSession(toolName, 36e5);
|
|
@@ -3053,7 +3155,8 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3053
3155
|
hashAuditArgs
|
|
3054
3156
|
);
|
|
3055
3157
|
}
|
|
3056
|
-
|
|
3158
|
+
const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
|
|
3159
|
+
return enrichedResult;
|
|
3057
3160
|
}
|
|
3058
3161
|
async function authorizeAction(toolName, args) {
|
|
3059
3162
|
const result = await authorizeHeadless(toolName, args);
|