@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.js
CHANGED
|
@@ -76,6 +76,11 @@ __export(audit_exports, {
|
|
|
76
76
|
appendToLog: () => appendToLog,
|
|
77
77
|
redactSecrets: () => redactSecrets
|
|
78
78
|
});
|
|
79
|
+
function isTestCall(toolName, args) {
|
|
80
|
+
if (toolName !== "Bash" && toolName !== "bash") return false;
|
|
81
|
+
const cmd = args?.command;
|
|
82
|
+
return typeof cmd === "string" && TEST_COMMAND_RE.test(cmd);
|
|
83
|
+
}
|
|
79
84
|
function redactSecrets(text) {
|
|
80
85
|
if (!text) return text;
|
|
81
86
|
let redacted = text;
|
|
@@ -111,12 +116,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
111
116
|
}
|
|
112
117
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
113
118
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
119
|
+
const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
|
|
114
120
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
115
121
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
116
122
|
tool: toolName,
|
|
117
123
|
...argsField,
|
|
118
124
|
decision,
|
|
119
125
|
checkedBy,
|
|
126
|
+
...testRun,
|
|
120
127
|
agent: meta?.agent,
|
|
121
128
|
mcpServer: meta?.mcpServer,
|
|
122
129
|
hostname: import_os.default.hostname()
|
|
@@ -129,7 +136,7 @@ function appendConfigAudit(entry) {
|
|
|
129
136
|
hostname: import_os.default.hostname()
|
|
130
137
|
});
|
|
131
138
|
}
|
|
132
|
-
var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG;
|
|
139
|
+
var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, TEST_COMMAND_RE;
|
|
133
140
|
var init_audit = __esm({
|
|
134
141
|
"src/audit/index.ts"() {
|
|
135
142
|
"use strict";
|
|
@@ -139,6 +146,7 @@ var init_audit = __esm({
|
|
|
139
146
|
init_hasher();
|
|
140
147
|
LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
|
|
141
148
|
HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
|
|
149
|
+
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;
|
|
142
150
|
}
|
|
143
151
|
});
|
|
144
152
|
|
|
@@ -230,7 +238,8 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
230
238
|
slackEnabled: import_zod.z.boolean().optional(),
|
|
231
239
|
enableTrustSessions: import_zod.z.boolean().optional(),
|
|
232
240
|
allowGlobalPause: import_zod.z.boolean().optional(),
|
|
233
|
-
auditHashArgs: import_zod.z.boolean().optional()
|
|
241
|
+
auditHashArgs: import_zod.z.boolean().optional(),
|
|
242
|
+
agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
|
|
234
243
|
}).optional(),
|
|
235
244
|
policy: import_zod.z.object({
|
|
236
245
|
sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
|
|
@@ -246,6 +255,11 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
246
255
|
dlp: import_zod.z.object({
|
|
247
256
|
enabled: import_zod.z.boolean().optional(),
|
|
248
257
|
scanIgnoredTools: import_zod.z.boolean().optional()
|
|
258
|
+
}).optional(),
|
|
259
|
+
loopDetection: import_zod.z.object({
|
|
260
|
+
enabled: import_zod.z.boolean().optional(),
|
|
261
|
+
threshold: import_zod.z.number().min(2).optional(),
|
|
262
|
+
windowSeconds: import_zod.z.number().min(10).optional()
|
|
249
263
|
}).optional()
|
|
250
264
|
}).optional(),
|
|
251
265
|
environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
|
|
@@ -267,8 +281,8 @@ function sanitizeConfig(raw) {
|
|
|
267
281
|
}
|
|
268
282
|
}
|
|
269
283
|
const lines = result.error.issues.map((issue) => {
|
|
270
|
-
const
|
|
271
|
-
return ` \u2022 ${
|
|
284
|
+
const path15 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
285
|
+
return ` \u2022 ${path15}: ${issue.message}`;
|
|
272
286
|
});
|
|
273
287
|
return {
|
|
274
288
|
sanitized,
|
|
@@ -601,7 +615,8 @@ var DEFAULT_CONFIG = {
|
|
|
601
615
|
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."
|
|
602
616
|
}
|
|
603
617
|
],
|
|
604
|
-
dlp: { enabled: true, scanIgnoredTools: true }
|
|
618
|
+
dlp: { enabled: true, scanIgnoredTools: true },
|
|
619
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
605
620
|
},
|
|
606
621
|
environments: {}
|
|
607
622
|
};
|
|
@@ -723,7 +738,8 @@ function getConfig(cwd) {
|
|
|
723
738
|
onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
|
|
724
739
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
725
740
|
},
|
|
726
|
-
dlp: { ...DEFAULT_CONFIG.policy.dlp }
|
|
741
|
+
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
742
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
727
743
|
};
|
|
728
744
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
729
745
|
const applyLayer = (source) => {
|
|
@@ -762,6 +778,13 @@ function getConfig(cwd) {
|
|
|
762
778
|
if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
|
|
763
779
|
if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
|
|
764
780
|
}
|
|
781
|
+
if (p.loopDetection) {
|
|
782
|
+
const ld = p.loopDetection;
|
|
783
|
+
if (ld.enabled !== void 0) mergedPolicy.loopDetection.enabled = ld.enabled;
|
|
784
|
+
if (ld.threshold !== void 0) mergedPolicy.loopDetection.threshold = ld.threshold;
|
|
785
|
+
if (ld.windowSeconds !== void 0)
|
|
786
|
+
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
787
|
+
}
|
|
765
788
|
const envs = source.environments || {};
|
|
766
789
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
767
790
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -1536,9 +1559,9 @@ function matchesPattern(text, patterns) {
|
|
|
1536
1559
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1537
1560
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1538
1561
|
}
|
|
1539
|
-
function getNestedValue(obj,
|
|
1562
|
+
function getNestedValue(obj, path15) {
|
|
1540
1563
|
if (!obj || typeof obj !== "object") return null;
|
|
1541
|
-
return
|
|
1564
|
+
return path15.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1542
1565
|
}
|
|
1543
1566
|
function evaluateSmartConditions(args, rule) {
|
|
1544
1567
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
@@ -2106,7 +2129,7 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
|
2106
2129
|
}
|
|
2107
2130
|
|
|
2108
2131
|
// src/auth/orchestrator.ts
|
|
2109
|
-
var
|
|
2132
|
+
var import_crypto3 = require("crypto");
|
|
2110
2133
|
|
|
2111
2134
|
// src/ui/native.ts
|
|
2112
2135
|
var import_child_process2 = require("child_process");
|
|
@@ -2299,11 +2322,12 @@ ${smartTruncate(str, 500)}`
|
|
|
2299
2322
|
function escapePango(text) {
|
|
2300
2323
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2301
2324
|
}
|
|
2302
|
-
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2325
|
+
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2303
2326
|
const lines = [];
|
|
2304
2327
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2305
2328
|
lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
|
|
2306
2329
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2330
|
+
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2307
2331
|
lines.push("");
|
|
2308
2332
|
lines.push(formattedArgs);
|
|
2309
2333
|
if (allowCount >= 3) {
|
|
@@ -2316,7 +2340,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2316
2340
|
}
|
|
2317
2341
|
return lines.join("\n");
|
|
2318
2342
|
}
|
|
2319
|
-
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2343
|
+
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2320
2344
|
const lines = [];
|
|
2321
2345
|
if (locked) {
|
|
2322
2346
|
lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
|
|
@@ -2326,6 +2350,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2326
2350
|
`<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
|
|
2327
2351
|
);
|
|
2328
2352
|
lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
|
|
2353
|
+
if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
|
|
2329
2354
|
lines.push("");
|
|
2330
2355
|
lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
|
|
2331
2356
|
if (allowCount >= 3) {
|
|
@@ -2342,7 +2367,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2342
2367
|
}
|
|
2343
2368
|
return lines.join("\n");
|
|
2344
2369
|
}
|
|
2345
|
-
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
|
|
2370
|
+
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
|
|
2346
2371
|
if (isTestEnv()) return "deny";
|
|
2347
2372
|
const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
|
|
2348
2373
|
const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
|
|
@@ -2353,7 +2378,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
|
|
|
2353
2378
|
agent,
|
|
2354
2379
|
explainableLabel,
|
|
2355
2380
|
locked,
|
|
2356
|
-
allowCount
|
|
2381
|
+
allowCount,
|
|
2382
|
+
ruleDescription
|
|
2357
2383
|
);
|
|
2358
2384
|
return new Promise((resolve) => {
|
|
2359
2385
|
let childProcess = null;
|
|
@@ -2387,7 +2413,8 @@ end run`;
|
|
|
2387
2413
|
agent,
|
|
2388
2414
|
explainableLabel,
|
|
2389
2415
|
locked,
|
|
2390
|
-
allowCount
|
|
2416
|
+
allowCount,
|
|
2417
|
+
ruleDescription
|
|
2391
2418
|
);
|
|
2392
2419
|
const argsList = [
|
|
2393
2420
|
locked ? "--info" : "--question",
|
|
@@ -2455,7 +2482,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2455
2482
|
}).catch(() => {
|
|
2456
2483
|
});
|
|
2457
2484
|
}
|
|
2458
|
-
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
2485
|
+
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
2459
2486
|
const controller = new AbortController();
|
|
2460
2487
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2461
2488
|
if (!creds.apiKey) throw new Error("Node9 API Key is missing");
|
|
@@ -2500,7 +2527,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2500
2527
|
platform: import_os8.default.platform()
|
|
2501
2528
|
},
|
|
2502
2529
|
...riskMetadata && { riskMetadata },
|
|
2503
|
-
...ciContext && { ciContext }
|
|
2530
|
+
...ciContext && { ciContext },
|
|
2531
|
+
...agentPolicy && { policy: agentPolicy },
|
|
2532
|
+
...forceReview && { forceReview: true }
|
|
2504
2533
|
}),
|
|
2505
2534
|
signal: controller.signal
|
|
2506
2535
|
});
|
|
@@ -2574,6 +2603,52 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
2574
2603
|
}
|
|
2575
2604
|
}
|
|
2576
2605
|
|
|
2606
|
+
// src/loop-detector.ts
|
|
2607
|
+
var import_fs10 = __toESM(require("fs"));
|
|
2608
|
+
var import_path14 = __toESM(require("path"));
|
|
2609
|
+
var import_os9 = __toESM(require("os"));
|
|
2610
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
2611
|
+
function loopStateFile() {
|
|
2612
|
+
return import_path14.default.join(import_os9.default.homedir(), ".node9", "loop-state.json");
|
|
2613
|
+
}
|
|
2614
|
+
var MAX_RECORDS = 500;
|
|
2615
|
+
function computeArgsHash(args) {
|
|
2616
|
+
const str = JSON.stringify(args ?? "");
|
|
2617
|
+
return import_crypto2.default.createHash("sha256").update(str).digest("hex").slice(0, 16);
|
|
2618
|
+
}
|
|
2619
|
+
function readState() {
|
|
2620
|
+
try {
|
|
2621
|
+
if (!import_fs10.default.existsSync(loopStateFile())) return [];
|
|
2622
|
+
const raw = import_fs10.default.readFileSync(loopStateFile(), "utf-8");
|
|
2623
|
+
const parsed = JSON.parse(raw);
|
|
2624
|
+
if (!Array.isArray(parsed)) return [];
|
|
2625
|
+
return parsed;
|
|
2626
|
+
} catch {
|
|
2627
|
+
return [];
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
function writeState(records) {
|
|
2631
|
+
const dir = import_path14.default.dirname(loopStateFile());
|
|
2632
|
+
if (!import_fs10.default.existsSync(dir)) import_fs10.default.mkdirSync(dir, { recursive: true });
|
|
2633
|
+
const tmpPath = `${loopStateFile()}.${import_os9.default.hostname()}.${process.pid}.tmp`;
|
|
2634
|
+
import_fs10.default.writeFileSync(tmpPath, JSON.stringify(records));
|
|
2635
|
+
import_fs10.default.renameSync(tmpPath, loopStateFile());
|
|
2636
|
+
}
|
|
2637
|
+
function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
2638
|
+
try {
|
|
2639
|
+
const hash = computeArgsHash(args);
|
|
2640
|
+
const now = Date.now();
|
|
2641
|
+
const cutoff = now - windowMs;
|
|
2642
|
+
const records = readState().filter((r) => r.ts >= cutoff);
|
|
2643
|
+
records.push({ t: tool, h: hash, ts: now });
|
|
2644
|
+
const count = records.filter((r) => r.t === tool && r.h === hash).length;
|
|
2645
|
+
writeState(records.slice(-MAX_RECORDS));
|
|
2646
|
+
return { looping: count >= threshold, count };
|
|
2647
|
+
} catch {
|
|
2648
|
+
return { looping: false, count: 0 };
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2577
2652
|
// src/auth/orchestrator.ts
|
|
2578
2653
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
2579
2654
|
"write",
|
|
@@ -2625,7 +2700,7 @@ function notifyActivity(data) {
|
|
|
2625
2700
|
}
|
|
2626
2701
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
2627
2702
|
if (!options?.calledFromDaemon) {
|
|
2628
|
-
const actId = (0,
|
|
2703
|
+
const actId = (0, import_crypto3.randomUUID)();
|
|
2629
2704
|
const actTs = Date.now();
|
|
2630
2705
|
await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
|
|
2631
2706
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
@@ -2672,6 +2747,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2672
2747
|
let explainableLabel = "Local Config";
|
|
2673
2748
|
let policyMatchedField;
|
|
2674
2749
|
let policyMatchedWord;
|
|
2750
|
+
let policyRuleDescription;
|
|
2675
2751
|
let riskMetadata;
|
|
2676
2752
|
let statefulRecoveryCommand;
|
|
2677
2753
|
let localSmartRuleMatched = false;
|
|
@@ -2765,6 +2841,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2765
2841
|
return { approved: true, checkedBy: "audit" };
|
|
2766
2842
|
}
|
|
2767
2843
|
if (!taintWarning && !isIgnoredTool(toolName)) {
|
|
2844
|
+
const ld = config.policy.loopDetection;
|
|
2845
|
+
if (ld.enabled) {
|
|
2846
|
+
const loopResult = recordAndCheck(toolName, args, ld.threshold, ld.windowSeconds * 1e3);
|
|
2847
|
+
if (loopResult.looping) {
|
|
2848
|
+
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?`;
|
|
2849
|
+
if (!isManual)
|
|
2850
|
+
appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
|
|
2851
|
+
return {
|
|
2852
|
+
approved: false,
|
|
2853
|
+
reason,
|
|
2854
|
+
blockedBy: "loop-detection",
|
|
2855
|
+
blockedByLabel: "\u{1F504} Loop Detected"
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2768
2859
|
if (getActiveTrustSession(toolName)) {
|
|
2769
2860
|
if (approvers.cloud && creds?.apiKey)
|
|
2770
2861
|
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
@@ -2820,6 +2911,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2820
2911
|
policyMatchedField = policyResult.matchedField;
|
|
2821
2912
|
policyMatchedWord = policyResult.matchedWord;
|
|
2822
2913
|
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
2914
|
+
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
2915
|
+
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
2823
2916
|
riskMetadata = computeRiskMetadata(
|
|
2824
2917
|
args,
|
|
2825
2918
|
policyResult.tier ?? 6,
|
|
@@ -2828,6 +2921,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2828
2921
|
policyMatchedWord,
|
|
2829
2922
|
policyResult.ruleName
|
|
2830
2923
|
);
|
|
2924
|
+
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
2831
2925
|
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
2832
2926
|
if (persistent === "allow") {
|
|
2833
2927
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -2862,9 +2956,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2862
2956
|
}
|
|
2863
2957
|
let cloudRequestId = null;
|
|
2864
2958
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
2865
|
-
|
|
2959
|
+
const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
|
|
2960
|
+
if (cloudEnforced) {
|
|
2866
2961
|
try {
|
|
2867
|
-
const initResult = await initNode9SaaS(
|
|
2962
|
+
const initResult = await initNode9SaaS(
|
|
2963
|
+
toolName,
|
|
2964
|
+
args,
|
|
2965
|
+
creds,
|
|
2966
|
+
meta,
|
|
2967
|
+
riskMetadata,
|
|
2968
|
+
config.settings.agentPolicy,
|
|
2969
|
+
forceReview
|
|
2970
|
+
);
|
|
2868
2971
|
if (!initResult.pending) {
|
|
2869
2972
|
if (initResult.shadowMode) {
|
|
2870
2973
|
return { approved: true, checkedBy: "cloud" };
|
|
@@ -2879,9 +2982,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2879
2982
|
};
|
|
2880
2983
|
}
|
|
2881
2984
|
}
|
|
2882
|
-
if (
|
|
2883
|
-
cloudRequestId = initResult.requestId || null;
|
|
2884
|
-
}
|
|
2985
|
+
if (initResult.pending) cloudRequestId = initResult.requestId || null;
|
|
2885
2986
|
if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
|
|
2886
2987
|
} catch {
|
|
2887
2988
|
}
|
|
@@ -2938,7 +3039,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2938
3039
|
}
|
|
2939
3040
|
}
|
|
2940
3041
|
}
|
|
2941
|
-
if (cloudEnforced && cloudRequestId
|
|
3042
|
+
if (cloudEnforced && cloudRequestId) {
|
|
2942
3043
|
racePromises.push(
|
|
2943
3044
|
(async () => {
|
|
2944
3045
|
try {
|
|
@@ -2970,7 +3071,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2970
3071
|
signal,
|
|
2971
3072
|
policyMatchedField,
|
|
2972
3073
|
policyMatchedWord,
|
|
2973
|
-
daemonAllowCount
|
|
3074
|
+
daemonAllowCount,
|
|
3075
|
+
riskMetadata?.ruleDescription
|
|
2974
3076
|
);
|
|
2975
3077
|
if (decision === "always_allow") {
|
|
2976
3078
|
writeTrustSession(toolName, 36e5);
|
|
@@ -3083,7 +3185,8 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3083
3185
|
hashAuditArgs
|
|
3084
3186
|
);
|
|
3085
3187
|
}
|
|
3086
|
-
|
|
3188
|
+
const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
|
|
3189
|
+
return enrichedResult;
|
|
3087
3190
|
}
|
|
3088
3191
|
async function authorizeAction(toolName, args) {
|
|
3089
3192
|
const result = await authorizeHeadless(toolName, args);
|