@node9/proxy 1.4.0 → 1.5.0
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 +36 -7
- package/dist/cli.js +1110 -374
- package/dist/cli.mjs +1105 -368
- package/dist/index.js +72 -21
- package/dist/index.mjs +72 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1492,16 +1492,35 @@ function readTrustedHosts() {
|
|
|
1492
1492
|
return [];
|
|
1493
1493
|
}
|
|
1494
1494
|
}
|
|
1495
|
+
var _cache = null;
|
|
1496
|
+
var CACHE_TTL_MS = 5e3;
|
|
1497
|
+
function getFileMtime() {
|
|
1498
|
+
try {
|
|
1499
|
+
return import_fs6.default.statSync(getTrustedHostsPath()).mtimeMs;
|
|
1500
|
+
} catch {
|
|
1501
|
+
return 0;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
function getCachedHosts() {
|
|
1505
|
+
const now = Date.now();
|
|
1506
|
+
if (_cache && now < _cache.expiry) {
|
|
1507
|
+
const mtime = getFileMtime();
|
|
1508
|
+
if (mtime === _cache.mtime) return _cache.hosts;
|
|
1509
|
+
}
|
|
1510
|
+
const hosts = readTrustedHosts();
|
|
1511
|
+
_cache = { hosts, expiry: now + CACHE_TTL_MS, mtime: getFileMtime() };
|
|
1512
|
+
return hosts;
|
|
1513
|
+
}
|
|
1495
1514
|
function normalizeHost(raw) {
|
|
1496
1515
|
return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
|
|
1497
1516
|
}
|
|
1498
1517
|
function isTrustedHost(host) {
|
|
1499
1518
|
const normalized = normalizeHost(host);
|
|
1500
|
-
return
|
|
1519
|
+
return getCachedHosts().some((entry) => {
|
|
1501
1520
|
const entryHost = entry.host.toLowerCase();
|
|
1502
1521
|
if (entryHost.startsWith("*.")) {
|
|
1503
1522
|
const domain = entryHost.slice(2);
|
|
1504
|
-
return normalized
|
|
1523
|
+
return normalized.endsWith("." + domain);
|
|
1505
1524
|
}
|
|
1506
1525
|
return normalized === entryHost;
|
|
1507
1526
|
});
|
|
@@ -1698,7 +1717,12 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
1698
1717
|
};
|
|
1699
1718
|
}
|
|
1700
1719
|
if (allTrusted) {
|
|
1701
|
-
return {
|
|
1720
|
+
return {
|
|
1721
|
+
decision: "allow",
|
|
1722
|
+
blockedByLabel: "Node9: Pipe-Chain to Trusted Host",
|
|
1723
|
+
reason: `Sensitive file piped to trusted host(s): ${sinks.join(", ")}`,
|
|
1724
|
+
tier: 3
|
|
1725
|
+
};
|
|
1702
1726
|
}
|
|
1703
1727
|
return {
|
|
1704
1728
|
decision: "review",
|
|
@@ -1950,8 +1974,8 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
1950
1974
|
signal: ctrl.signal
|
|
1951
1975
|
});
|
|
1952
1976
|
if (!res.ok) throw new Error("Daemon fail");
|
|
1953
|
-
const { id } = await res.json();
|
|
1954
|
-
return id;
|
|
1977
|
+
const { id, allowCount } = await res.json();
|
|
1978
|
+
return { id, allowCount: allowCount ?? 1 };
|
|
1955
1979
|
} finally {
|
|
1956
1980
|
clearTimeout(timer);
|
|
1957
1981
|
}
|
|
@@ -1990,15 +2014,15 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
1990
2014
|
signal: AbortSignal.timeout(3e3)
|
|
1991
2015
|
});
|
|
1992
2016
|
if (!res.ok) throw new Error("Daemon unreachable");
|
|
1993
|
-
const { id } = await res.json();
|
|
1994
|
-
return id;
|
|
2017
|
+
const { id, allowCount } = await res.json();
|
|
2018
|
+
return { id, allowCount: allowCount ?? 1 };
|
|
1995
2019
|
}
|
|
1996
|
-
async function resolveViaDaemon(id, decision, internalToken) {
|
|
2020
|
+
async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
1997
2021
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
1998
2022
|
await fetch(`${base}/resolve/${id}`, {
|
|
1999
2023
|
method: "POST",
|
|
2000
2024
|
headers: { "Content-Type": "application/json", "X-Node9-Internal": internalToken },
|
|
2001
|
-
body: JSON.stringify({ decision }),
|
|
2025
|
+
body: JSON.stringify({ decision, ...source && { source } }),
|
|
2002
2026
|
signal: AbortSignal.timeout(3e3)
|
|
2003
2027
|
});
|
|
2004
2028
|
}
|
|
@@ -2200,20 +2224,24 @@ ${smartTruncate(str, 500)}`
|
|
|
2200
2224
|
function escapePango(text) {
|
|
2201
2225
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2202
2226
|
}
|
|
2203
|
-
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
|
|
2227
|
+
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2204
2228
|
const lines = [];
|
|
2205
2229
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2206
2230
|
lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
|
|
2207
2231
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2208
2232
|
lines.push("");
|
|
2209
2233
|
lines.push(formattedArgs);
|
|
2234
|
+
if (allowCount >= 3) {
|
|
2235
|
+
lines.push("");
|
|
2236
|
+
lines.push(`\u{1F4A1} Approved ${allowCount - 1}\xD7 before \u2014 "Always Allow" creates a permanent rule`);
|
|
2237
|
+
}
|
|
2210
2238
|
if (!locked) {
|
|
2211
2239
|
lines.push("");
|
|
2212
2240
|
lines.push('\u21B5 Enter = Allow \u21B5 | \u238B Esc = Block \u238B | "Always Allow" = never ask again');
|
|
2213
2241
|
}
|
|
2214
2242
|
return lines.join("\n");
|
|
2215
2243
|
}
|
|
2216
|
-
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
|
|
2244
|
+
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2217
2245
|
const lines = [];
|
|
2218
2246
|
if (locked) {
|
|
2219
2247
|
lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
|
|
@@ -2225,6 +2253,12 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2225
2253
|
lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
|
|
2226
2254
|
lines.push("");
|
|
2227
2255
|
lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
|
|
2256
|
+
if (allowCount >= 3) {
|
|
2257
|
+
lines.push("");
|
|
2258
|
+
lines.push(
|
|
2259
|
+
`<span foreground="#f0c040">\u{1F4A1} Approved ${allowCount - 1}\xD7 before \u2014 "Always Allow" creates a permanent rule</span>`
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2228
2262
|
if (!locked) {
|
|
2229
2263
|
lines.push("");
|
|
2230
2264
|
lines.push(
|
|
@@ -2233,12 +2267,19 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2233
2267
|
}
|
|
2234
2268
|
return lines.join("\n");
|
|
2235
2269
|
}
|
|
2236
|
-
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord) {
|
|
2270
|
+
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
|
|
2237
2271
|
if (isTestEnv()) return "deny";
|
|
2238
2272
|
const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
|
|
2239
2273
|
const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
|
|
2240
2274
|
const title = locked ? `\u26A1 Node9 \u2014 Locked` : `\u{1F6E1}\uFE0F Node9 \u2014 ${intentLabel}`;
|
|
2241
|
-
const message = buildPlainMessage(
|
|
2275
|
+
const message = buildPlainMessage(
|
|
2276
|
+
toolName,
|
|
2277
|
+
formattedArgs,
|
|
2278
|
+
agent,
|
|
2279
|
+
explainableLabel,
|
|
2280
|
+
locked,
|
|
2281
|
+
allowCount
|
|
2282
|
+
);
|
|
2242
2283
|
return new Promise((resolve) => {
|
|
2243
2284
|
let childProcess = null;
|
|
2244
2285
|
const onAbort = () => {
|
|
@@ -2270,7 +2311,8 @@ end run`;
|
|
|
2270
2311
|
formattedArgs,
|
|
2271
2312
|
agent,
|
|
2272
2313
|
explainableLabel,
|
|
2273
|
-
locked
|
|
2314
|
+
locked,
|
|
2315
|
+
allowCount
|
|
2274
2316
|
);
|
|
2275
2317
|
const argsList = [
|
|
2276
2318
|
locked ? "--info" : "--question",
|
|
@@ -2615,13 +2657,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2615
2657
|
let viewerId = null;
|
|
2616
2658
|
const internalToken = getInternalToken();
|
|
2617
2659
|
let daemonEntryId = null;
|
|
2660
|
+
let daemonAllowCount = 1;
|
|
2618
2661
|
if ((approvers.browser || approvers.terminal) && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
2619
2662
|
if (cloudEnforced && cloudRequestId) {
|
|
2620
|
-
|
|
2663
|
+
const viewer = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
|
|
2664
|
+
viewerId = viewer?.id ?? null;
|
|
2621
2665
|
daemonEntryId = viewerId;
|
|
2666
|
+
if (viewer) daemonAllowCount = viewer.allowCount;
|
|
2622
2667
|
} else {
|
|
2623
2668
|
try {
|
|
2624
|
-
|
|
2669
|
+
const entry = await registerDaemonEntry(
|
|
2625
2670
|
toolName,
|
|
2626
2671
|
args,
|
|
2627
2672
|
meta,
|
|
@@ -2629,6 +2674,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2629
2674
|
options?.activityId,
|
|
2630
2675
|
options?.cwd
|
|
2631
2676
|
);
|
|
2677
|
+
daemonEntryId = entry.id;
|
|
2678
|
+
daemonAllowCount = entry.allowCount;
|
|
2632
2679
|
} catch {
|
|
2633
2680
|
}
|
|
2634
2681
|
}
|
|
@@ -2664,7 +2711,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2664
2711
|
false,
|
|
2665
2712
|
signal,
|
|
2666
2713
|
policyMatchedField,
|
|
2667
|
-
policyMatchedWord
|
|
2714
|
+
policyMatchedWord,
|
|
2715
|
+
daemonAllowCount
|
|
2668
2716
|
);
|
|
2669
2717
|
if (decision === "always_allow") {
|
|
2670
2718
|
writeTrustSession(toolName, 36e5);
|
|
@@ -2722,10 +2770,13 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
2722
2770
|
if (!resolved) {
|
|
2723
2771
|
resolved = true;
|
|
2724
2772
|
abortController.abort();
|
|
2725
|
-
if (
|
|
2726
|
-
resolveViaDaemon(
|
|
2727
|
-
|
|
2728
|
-
|
|
2773
|
+
if (daemonEntryId && internalToken) {
|
|
2774
|
+
resolveViaDaemon(
|
|
2775
|
+
daemonEntryId,
|
|
2776
|
+
res.approved ? "allow" : "deny",
|
|
2777
|
+
internalToken,
|
|
2778
|
+
res.decisionSource
|
|
2779
|
+
).catch(() => null);
|
|
2729
2780
|
}
|
|
2730
2781
|
resolve(res);
|
|
2731
2782
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1456,16 +1456,35 @@ function readTrustedHosts() {
|
|
|
1456
1456
|
return [];
|
|
1457
1457
|
}
|
|
1458
1458
|
}
|
|
1459
|
+
var _cache = null;
|
|
1460
|
+
var CACHE_TTL_MS = 5e3;
|
|
1461
|
+
function getFileMtime() {
|
|
1462
|
+
try {
|
|
1463
|
+
return fs6.statSync(getTrustedHostsPath()).mtimeMs;
|
|
1464
|
+
} catch {
|
|
1465
|
+
return 0;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function getCachedHosts() {
|
|
1469
|
+
const now = Date.now();
|
|
1470
|
+
if (_cache && now < _cache.expiry) {
|
|
1471
|
+
const mtime = getFileMtime();
|
|
1472
|
+
if (mtime === _cache.mtime) return _cache.hosts;
|
|
1473
|
+
}
|
|
1474
|
+
const hosts = readTrustedHosts();
|
|
1475
|
+
_cache = { hosts, expiry: now + CACHE_TTL_MS, mtime: getFileMtime() };
|
|
1476
|
+
return hosts;
|
|
1477
|
+
}
|
|
1459
1478
|
function normalizeHost(raw) {
|
|
1460
1479
|
return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
|
|
1461
1480
|
}
|
|
1462
1481
|
function isTrustedHost(host) {
|
|
1463
1482
|
const normalized = normalizeHost(host);
|
|
1464
|
-
return
|
|
1483
|
+
return getCachedHosts().some((entry) => {
|
|
1465
1484
|
const entryHost = entry.host.toLowerCase();
|
|
1466
1485
|
if (entryHost.startsWith("*.")) {
|
|
1467
1486
|
const domain = entryHost.slice(2);
|
|
1468
|
-
return normalized
|
|
1487
|
+
return normalized.endsWith("." + domain);
|
|
1469
1488
|
}
|
|
1470
1489
|
return normalized === entryHost;
|
|
1471
1490
|
});
|
|
@@ -1662,7 +1681,12 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
1662
1681
|
};
|
|
1663
1682
|
}
|
|
1664
1683
|
if (allTrusted) {
|
|
1665
|
-
return {
|
|
1684
|
+
return {
|
|
1685
|
+
decision: "allow",
|
|
1686
|
+
blockedByLabel: "Node9: Pipe-Chain to Trusted Host",
|
|
1687
|
+
reason: `Sensitive file piped to trusted host(s): ${sinks.join(", ")}`,
|
|
1688
|
+
tier: 3
|
|
1689
|
+
};
|
|
1666
1690
|
}
|
|
1667
1691
|
return {
|
|
1668
1692
|
decision: "review",
|
|
@@ -1914,8 +1938,8 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
1914
1938
|
signal: ctrl.signal
|
|
1915
1939
|
});
|
|
1916
1940
|
if (!res.ok) throw new Error("Daemon fail");
|
|
1917
|
-
const { id } = await res.json();
|
|
1918
|
-
return id;
|
|
1941
|
+
const { id, allowCount } = await res.json();
|
|
1942
|
+
return { id, allowCount: allowCount ?? 1 };
|
|
1919
1943
|
} finally {
|
|
1920
1944
|
clearTimeout(timer);
|
|
1921
1945
|
}
|
|
@@ -1954,15 +1978,15 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
1954
1978
|
signal: AbortSignal.timeout(3e3)
|
|
1955
1979
|
});
|
|
1956
1980
|
if (!res.ok) throw new Error("Daemon unreachable");
|
|
1957
|
-
const { id } = await res.json();
|
|
1958
|
-
return id;
|
|
1981
|
+
const { id, allowCount } = await res.json();
|
|
1982
|
+
return { id, allowCount: allowCount ?? 1 };
|
|
1959
1983
|
}
|
|
1960
|
-
async function resolveViaDaemon(id, decision, internalToken) {
|
|
1984
|
+
async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
1961
1985
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
1962
1986
|
await fetch(`${base}/resolve/${id}`, {
|
|
1963
1987
|
method: "POST",
|
|
1964
1988
|
headers: { "Content-Type": "application/json", "X-Node9-Internal": internalToken },
|
|
1965
|
-
body: JSON.stringify({ decision }),
|
|
1989
|
+
body: JSON.stringify({ decision, ...source && { source } }),
|
|
1966
1990
|
signal: AbortSignal.timeout(3e3)
|
|
1967
1991
|
});
|
|
1968
1992
|
}
|
|
@@ -2164,20 +2188,24 @@ ${smartTruncate(str, 500)}`
|
|
|
2164
2188
|
function escapePango(text) {
|
|
2165
2189
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2166
2190
|
}
|
|
2167
|
-
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
|
|
2191
|
+
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2168
2192
|
const lines = [];
|
|
2169
2193
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2170
2194
|
lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
|
|
2171
2195
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2172
2196
|
lines.push("");
|
|
2173
2197
|
lines.push(formattedArgs);
|
|
2198
|
+
if (allowCount >= 3) {
|
|
2199
|
+
lines.push("");
|
|
2200
|
+
lines.push(`\u{1F4A1} Approved ${allowCount - 1}\xD7 before \u2014 "Always Allow" creates a permanent rule`);
|
|
2201
|
+
}
|
|
2174
2202
|
if (!locked) {
|
|
2175
2203
|
lines.push("");
|
|
2176
2204
|
lines.push('\u21B5 Enter = Allow \u21B5 | \u238B Esc = Block \u238B | "Always Allow" = never ask again');
|
|
2177
2205
|
}
|
|
2178
2206
|
return lines.join("\n");
|
|
2179
2207
|
}
|
|
2180
|
-
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
|
|
2208
|
+
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2181
2209
|
const lines = [];
|
|
2182
2210
|
if (locked) {
|
|
2183
2211
|
lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
|
|
@@ -2189,6 +2217,12 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2189
2217
|
lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
|
|
2190
2218
|
lines.push("");
|
|
2191
2219
|
lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
|
|
2220
|
+
if (allowCount >= 3) {
|
|
2221
|
+
lines.push("");
|
|
2222
|
+
lines.push(
|
|
2223
|
+
`<span foreground="#f0c040">\u{1F4A1} Approved ${allowCount - 1}\xD7 before \u2014 "Always Allow" creates a permanent rule</span>`
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2192
2226
|
if (!locked) {
|
|
2193
2227
|
lines.push("");
|
|
2194
2228
|
lines.push(
|
|
@@ -2197,12 +2231,19 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2197
2231
|
}
|
|
2198
2232
|
return lines.join("\n");
|
|
2199
2233
|
}
|
|
2200
|
-
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord) {
|
|
2234
|
+
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
|
|
2201
2235
|
if (isTestEnv()) return "deny";
|
|
2202
2236
|
const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
|
|
2203
2237
|
const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
|
|
2204
2238
|
const title = locked ? `\u26A1 Node9 \u2014 Locked` : `\u{1F6E1}\uFE0F Node9 \u2014 ${intentLabel}`;
|
|
2205
|
-
const message = buildPlainMessage(
|
|
2239
|
+
const message = buildPlainMessage(
|
|
2240
|
+
toolName,
|
|
2241
|
+
formattedArgs,
|
|
2242
|
+
agent,
|
|
2243
|
+
explainableLabel,
|
|
2244
|
+
locked,
|
|
2245
|
+
allowCount
|
|
2246
|
+
);
|
|
2206
2247
|
return new Promise((resolve) => {
|
|
2207
2248
|
let childProcess = null;
|
|
2208
2249
|
const onAbort = () => {
|
|
@@ -2234,7 +2275,8 @@ end run`;
|
|
|
2234
2275
|
formattedArgs,
|
|
2235
2276
|
agent,
|
|
2236
2277
|
explainableLabel,
|
|
2237
|
-
locked
|
|
2278
|
+
locked,
|
|
2279
|
+
allowCount
|
|
2238
2280
|
);
|
|
2239
2281
|
const argsList = [
|
|
2240
2282
|
locked ? "--info" : "--question",
|
|
@@ -2579,13 +2621,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2579
2621
|
let viewerId = null;
|
|
2580
2622
|
const internalToken = getInternalToken();
|
|
2581
2623
|
let daemonEntryId = null;
|
|
2624
|
+
let daemonAllowCount = 1;
|
|
2582
2625
|
if ((approvers.browser || approvers.terminal) && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
2583
2626
|
if (cloudEnforced && cloudRequestId) {
|
|
2584
|
-
|
|
2627
|
+
const viewer = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
|
|
2628
|
+
viewerId = viewer?.id ?? null;
|
|
2585
2629
|
daemonEntryId = viewerId;
|
|
2630
|
+
if (viewer) daemonAllowCount = viewer.allowCount;
|
|
2586
2631
|
} else {
|
|
2587
2632
|
try {
|
|
2588
|
-
|
|
2633
|
+
const entry = await registerDaemonEntry(
|
|
2589
2634
|
toolName,
|
|
2590
2635
|
args,
|
|
2591
2636
|
meta,
|
|
@@ -2593,6 +2638,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2593
2638
|
options?.activityId,
|
|
2594
2639
|
options?.cwd
|
|
2595
2640
|
);
|
|
2641
|
+
daemonEntryId = entry.id;
|
|
2642
|
+
daemonAllowCount = entry.allowCount;
|
|
2596
2643
|
} catch {
|
|
2597
2644
|
}
|
|
2598
2645
|
}
|
|
@@ -2628,7 +2675,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2628
2675
|
false,
|
|
2629
2676
|
signal,
|
|
2630
2677
|
policyMatchedField,
|
|
2631
|
-
policyMatchedWord
|
|
2678
|
+
policyMatchedWord,
|
|
2679
|
+
daemonAllowCount
|
|
2632
2680
|
);
|
|
2633
2681
|
if (decision === "always_allow") {
|
|
2634
2682
|
writeTrustSession(toolName, 36e5);
|
|
@@ -2686,10 +2734,13 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
2686
2734
|
if (!resolved) {
|
|
2687
2735
|
resolved = true;
|
|
2688
2736
|
abortController.abort();
|
|
2689
|
-
if (
|
|
2690
|
-
resolveViaDaemon(
|
|
2691
|
-
|
|
2692
|
-
|
|
2737
|
+
if (daemonEntryId && internalToken) {
|
|
2738
|
+
resolveViaDaemon(
|
|
2739
|
+
daemonEntryId,
|
|
2740
|
+
res.approved ? "allow" : "deny",
|
|
2741
|
+
internalToken,
|
|
2742
|
+
res.decisionSource
|
|
2743
|
+
).catch(() => null);
|
|
2693
2744
|
}
|
|
2694
2745
|
resolve(res);
|
|
2695
2746
|
}
|