@node9/proxy 1.3.2 → 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/dist/index.js CHANGED
@@ -191,8 +191,8 @@ function sanitizeConfig(raw) {
191
191
  }
192
192
  }
193
193
  const lines = result.error.issues.map((issue) => {
194
- const path13 = issue.path.length > 0 ? issue.path.join(".") : "root";
195
- return ` \u2022 ${path13}: ${issue.message}`;
194
+ const path14 = issue.path.length > 0 ? issue.path.join(".") : "root";
195
+ return ` \u2022 ${path14}: ${issue.message}`;
196
196
  });
197
197
  return {
198
198
  sanitized,
@@ -922,7 +922,7 @@ function getCompiledRegex(pattern, flags = "") {
922
922
  }
923
923
 
924
924
  // src/policy/index.ts
925
- var import_path7 = __toESM(require("path"));
925
+ var import_path8 = __toESM(require("path"));
926
926
  var import_picomatch = __toESM(require("picomatch"));
927
927
  var import_sh_syntax = require("sh-syntax");
928
928
 
@@ -1476,6 +1476,56 @@ function extractAllSshHosts(tokens) {
1476
1476
  return [...hosts].filter(Boolean);
1477
1477
  }
1478
1478
 
1479
+ // src/auth/trusted-hosts.ts
1480
+ var import_fs6 = __toESM(require("fs"));
1481
+ var import_path7 = __toESM(require("path"));
1482
+ var import_os5 = __toESM(require("os"));
1483
+ function getTrustedHostsPath() {
1484
+ return import_path7.default.join(import_os5.default.homedir(), ".node9", "trusted-hosts.json");
1485
+ }
1486
+ function readTrustedHosts() {
1487
+ try {
1488
+ const raw = import_fs6.default.readFileSync(getTrustedHostsPath(), "utf8");
1489
+ const parsed = JSON.parse(raw);
1490
+ return Array.isArray(parsed.hosts) ? parsed.hosts : [];
1491
+ } catch {
1492
+ return [];
1493
+ }
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
+ }
1514
+ function normalizeHost(raw) {
1515
+ return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
1516
+ }
1517
+ function isTrustedHost(host) {
1518
+ const normalized = normalizeHost(host);
1519
+ return getCachedHosts().some((entry) => {
1520
+ const entryHost = entry.host.toLowerCase();
1521
+ if (entryHost.startsWith("*.")) {
1522
+ const domain = entryHost.slice(2);
1523
+ return normalized.endsWith("." + domain);
1524
+ }
1525
+ return normalized === entryHost;
1526
+ });
1527
+ }
1528
+
1479
1529
  // src/policy/index.ts
1480
1530
  function tokenize2(toolName) {
1481
1531
  return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
@@ -1490,9 +1540,9 @@ function matchesPattern(text, patterns) {
1490
1540
  const withoutDotSlash = text.replace(/^\.\//, "");
1491
1541
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1492
1542
  }
1493
- function getNestedValue(obj, path13) {
1543
+ function getNestedValue(obj, path14) {
1494
1544
  if (!obj || typeof obj !== "object") return null;
1495
- return path13.split(".").reduce((prev, curr) => prev?.[curr], obj);
1545
+ return path14.split(".").reduce((prev, curr) => prev?.[curr], obj);
1496
1546
  }
1497
1547
  function evaluateSmartConditions(args, rule) {
1498
1548
  if (!rule.conditions || rule.conditions.length === 0) return true;
@@ -1647,23 +1697,39 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1647
1697
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
1648
1698
  }
1649
1699
  const pipeAnalysis = analyzePipeChain(shellCommand);
1650
- if (pipeAnalysis.isPipeline) {
1700
+ if (pipeAnalysis.isPipeline && (pipeAnalysis.risk === "critical" || pipeAnalysis.risk === "high")) {
1701
+ const sinks = pipeAnalysis.sinkTargets;
1702
+ const allTrusted = sinks.length > 0 && sinks.every(isTrustedHost);
1651
1703
  if (pipeAnalysis.risk === "critical") {
1704
+ if (allTrusted) {
1705
+ return {
1706
+ decision: "review",
1707
+ blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
1708
+ reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
1709
+ tier: 3
1710
+ };
1711
+ }
1652
1712
  return {
1653
1713
  decision: "block",
1654
1714
  blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
1655
- reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1715
+ reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1656
1716
  tier: 3
1657
1717
  };
1658
1718
  }
1659
- if (pipeAnalysis.risk === "high") {
1719
+ if (allTrusted) {
1660
1720
  return {
1661
- decision: "review",
1662
- blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1663
- reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1721
+ decision: "allow",
1722
+ blockedByLabel: "Node9: Pipe-Chain to Trusted Host",
1723
+ reason: `Sensitive file piped to trusted host(s): ${sinks.join(", ")}`,
1664
1724
  tier: 3
1665
1725
  };
1666
1726
  }
1727
+ return {
1728
+ decision: "review",
1729
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1730
+ reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1731
+ tier: 3
1732
+ };
1667
1733
  }
1668
1734
  const firstToken = analyzed.actions[0] ?? "";
1669
1735
  if (["ssh", "scp", "rsync"].includes(firstToken)) {
@@ -1671,7 +1737,7 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1671
1737
  const sshHosts = extractAllSshHosts(rawTokens.slice(1));
1672
1738
  allTokens.push(...sshHosts);
1673
1739
  }
1674
- if (firstToken && import_path7.default.posix.isAbsolute(firstToken)) {
1740
+ if (firstToken && import_path8.default.posix.isAbsolute(firstToken)) {
1675
1741
  const prov = checkProvenance(firstToken, cwd);
1676
1742
  if (prov.trustLevel === "suspect") {
1677
1743
  return {
@@ -1772,18 +1838,18 @@ function isIgnoredTool(toolName) {
1772
1838
  }
1773
1839
 
1774
1840
  // src/auth/state.ts
1775
- var import_fs6 = __toESM(require("fs"));
1776
- var import_path8 = __toESM(require("path"));
1777
- var import_os5 = __toESM(require("os"));
1778
- var PAUSED_FILE = import_path8.default.join(import_os5.default.homedir(), ".node9", "PAUSED");
1779
- var TRUST_FILE = import_path8.default.join(import_os5.default.homedir(), ".node9", "trust.json");
1841
+ var import_fs7 = __toESM(require("fs"));
1842
+ var import_path9 = __toESM(require("path"));
1843
+ var import_os6 = __toESM(require("os"));
1844
+ var PAUSED_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "PAUSED");
1845
+ var TRUST_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "trust.json");
1780
1846
  function checkPause() {
1781
1847
  try {
1782
- if (!import_fs6.default.existsSync(PAUSED_FILE)) return { paused: false };
1783
- const state = JSON.parse(import_fs6.default.readFileSync(PAUSED_FILE, "utf-8"));
1848
+ if (!import_fs7.default.existsSync(PAUSED_FILE)) return { paused: false };
1849
+ const state = JSON.parse(import_fs7.default.readFileSync(PAUSED_FILE, "utf-8"));
1784
1850
  if (state.expiry > 0 && Date.now() >= state.expiry) {
1785
1851
  try {
1786
- import_fs6.default.unlinkSync(PAUSED_FILE);
1852
+ import_fs7.default.unlinkSync(PAUSED_FILE);
1787
1853
  } catch {
1788
1854
  }
1789
1855
  return { paused: false };
@@ -1794,20 +1860,20 @@ function checkPause() {
1794
1860
  }
1795
1861
  }
1796
1862
  function atomicWriteSync(filePath, data, options) {
1797
- const dir = import_path8.default.dirname(filePath);
1798
- if (!import_fs6.default.existsSync(dir)) import_fs6.default.mkdirSync(dir, { recursive: true });
1799
- const tmpPath = `${filePath}.${import_os5.default.hostname()}.${process.pid}.tmp`;
1800
- import_fs6.default.writeFileSync(tmpPath, data, options);
1801
- import_fs6.default.renameSync(tmpPath, filePath);
1863
+ const dir = import_path9.default.dirname(filePath);
1864
+ if (!import_fs7.default.existsSync(dir)) import_fs7.default.mkdirSync(dir, { recursive: true });
1865
+ const tmpPath = `${filePath}.${import_os6.default.hostname()}.${process.pid}.tmp`;
1866
+ import_fs7.default.writeFileSync(tmpPath, data, options);
1867
+ import_fs7.default.renameSync(tmpPath, filePath);
1802
1868
  }
1803
1869
  function getActiveTrustSession(toolName) {
1804
1870
  try {
1805
- if (!import_fs6.default.existsSync(TRUST_FILE)) return false;
1806
- const trust = JSON.parse(import_fs6.default.readFileSync(TRUST_FILE, "utf-8"));
1871
+ if (!import_fs7.default.existsSync(TRUST_FILE)) return false;
1872
+ const trust = JSON.parse(import_fs7.default.readFileSync(TRUST_FILE, "utf-8"));
1807
1873
  const now = Date.now();
1808
1874
  const active = trust.entries.filter((e) => e.expiry > now);
1809
1875
  if (active.length !== trust.entries.length) {
1810
- import_fs6.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1876
+ import_fs7.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1811
1877
  }
1812
1878
  return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
1813
1879
  } catch {
@@ -1818,8 +1884,8 @@ function writeTrustSession(toolName, durationMs) {
1818
1884
  try {
1819
1885
  let trust = { entries: [] };
1820
1886
  try {
1821
- if (import_fs6.default.existsSync(TRUST_FILE)) {
1822
- trust = JSON.parse(import_fs6.default.readFileSync(TRUST_FILE, "utf-8"));
1887
+ if (import_fs7.default.existsSync(TRUST_FILE)) {
1888
+ trust = JSON.parse(import_fs7.default.readFileSync(TRUST_FILE, "utf-8"));
1823
1889
  }
1824
1890
  } catch {
1825
1891
  }
@@ -1835,9 +1901,9 @@ function writeTrustSession(toolName, durationMs) {
1835
1901
  }
1836
1902
  function getPersistentDecision(toolName) {
1837
1903
  try {
1838
- const file = import_path8.default.join(import_os5.default.homedir(), ".node9", "decisions.json");
1839
- if (!import_fs6.default.existsSync(file)) return null;
1840
- const decisions = JSON.parse(import_fs6.default.readFileSync(file, "utf-8"));
1904
+ const file = import_path9.default.join(import_os6.default.homedir(), ".node9", "decisions.json");
1905
+ if (!import_fs7.default.existsSync(file)) return null;
1906
+ const decisions = JSON.parse(import_fs7.default.readFileSync(file, "utf-8"));
1841
1907
  const d = decisions[toolName];
1842
1908
  if (d === "allow" || d === "deny") return d;
1843
1909
  } catch {
@@ -1846,17 +1912,17 @@ function getPersistentDecision(toolName) {
1846
1912
  }
1847
1913
 
1848
1914
  // src/auth/daemon.ts
1849
- var import_fs7 = __toESM(require("fs"));
1850
- var import_path9 = __toESM(require("path"));
1851
- var import_os6 = __toESM(require("os"));
1915
+ var import_fs8 = __toESM(require("fs"));
1916
+ var import_path10 = __toESM(require("path"));
1917
+ var import_os7 = __toESM(require("os"));
1852
1918
  var import_child_process = require("child_process");
1853
1919
  var DAEMON_PORT = 7391;
1854
1920
  var DAEMON_HOST = "127.0.0.1";
1855
1921
  function getInternalToken() {
1856
1922
  try {
1857
- const pidFile = import_path9.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
1858
- if (!import_fs7.default.existsSync(pidFile)) return null;
1859
- const data = JSON.parse(import_fs7.default.readFileSync(pidFile, "utf-8"));
1923
+ const pidFile = import_path10.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
1924
+ if (!import_fs8.default.existsSync(pidFile)) return null;
1925
+ const data = JSON.parse(import_fs8.default.readFileSync(pidFile, "utf-8"));
1860
1926
  process.kill(data.pid, 0);
1861
1927
  return data.internalToken ?? null;
1862
1928
  } catch {
@@ -1864,10 +1930,10 @@ function getInternalToken() {
1864
1930
  }
1865
1931
  }
1866
1932
  function isDaemonRunning() {
1867
- const pidFile = import_path9.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
1868
- if (import_fs7.default.existsSync(pidFile)) {
1933
+ const pidFile = import_path10.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
1934
+ if (import_fs8.default.existsSync(pidFile)) {
1869
1935
  try {
1870
- const { pid, port } = JSON.parse(import_fs7.default.readFileSync(pidFile, "utf-8"));
1936
+ const { pid, port } = JSON.parse(import_fs8.default.readFileSync(pidFile, "utf-8"));
1871
1937
  if (port !== DAEMON_PORT) return false;
1872
1938
  process.kill(pid, 0);
1873
1939
  return true;
@@ -1908,8 +1974,8 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
1908
1974
  signal: ctrl.signal
1909
1975
  });
1910
1976
  if (!res.ok) throw new Error("Daemon fail");
1911
- const { id } = await res.json();
1912
- return id;
1977
+ const { id, allowCount } = await res.json();
1978
+ return { id, allowCount: allowCount ?? 1 };
1913
1979
  } finally {
1914
1980
  clearTimeout(timer);
1915
1981
  }
@@ -1948,31 +2014,31 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
1948
2014
  signal: AbortSignal.timeout(3e3)
1949
2015
  });
1950
2016
  if (!res.ok) throw new Error("Daemon unreachable");
1951
- const { id } = await res.json();
1952
- return id;
2017
+ const { id, allowCount } = await res.json();
2018
+ return { id, allowCount: allowCount ?? 1 };
1953
2019
  }
1954
- async function resolveViaDaemon(id, decision, internalToken) {
2020
+ async function resolveViaDaemon(id, decision, internalToken, source) {
1955
2021
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
1956
2022
  await fetch(`${base}/resolve/${id}`, {
1957
2023
  method: "POST",
1958
2024
  headers: { "Content-Type": "application/json", "X-Node9-Internal": internalToken },
1959
- body: JSON.stringify({ decision }),
2025
+ body: JSON.stringify({ decision, ...source && { source } }),
1960
2026
  signal: AbortSignal.timeout(3e3)
1961
2027
  });
1962
2028
  }
1963
2029
 
1964
2030
  // src/auth/orchestrator.ts
1965
2031
  var import_net = __toESM(require("net"));
1966
- var import_path12 = __toESM(require("path"));
1967
- var import_os8 = __toESM(require("os"));
2032
+ var import_path13 = __toESM(require("path"));
2033
+ var import_os9 = __toESM(require("os"));
1968
2034
  var import_crypto = require("crypto");
1969
2035
 
1970
2036
  // src/ui/native.ts
1971
2037
  var import_child_process2 = require("child_process");
1972
- var import_path11 = __toESM(require("path"));
2038
+ var import_path12 = __toESM(require("path"));
1973
2039
 
1974
2040
  // src/context-sniper.ts
1975
- var import_path10 = __toESM(require("path"));
2041
+ var import_path11 = __toESM(require("path"));
1976
2042
  function smartTruncate(str, maxLen = 500) {
1977
2043
  if (str.length <= maxLen) return str;
1978
2044
  const edge = Math.floor(maxLen / 2) - 3;
@@ -2040,7 +2106,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
2040
2106
  intent = "EDIT";
2041
2107
  if (obj.file_path) {
2042
2108
  editFilePath = String(obj.file_path);
2043
- editFileName = import_path10.default.basename(editFilePath);
2109
+ editFileName = import_path11.default.basename(editFilePath);
2044
2110
  }
2045
2111
  const result = extractContext(String(obj.new_string), matchedWord);
2046
2112
  contextSnippet = result.snippet;
@@ -2095,7 +2161,7 @@ function formatArgs(args, matchedField, matchedWord) {
2095
2161
  if (typeof parsed === "object" && !Array.isArray(parsed)) {
2096
2162
  const obj = parsed;
2097
2163
  if (obj.old_string !== void 0 && obj.new_string !== void 0) {
2098
- const file = obj.file_path ? import_path11.default.basename(String(obj.file_path)) : "file";
2164
+ const file = obj.file_path ? import_path12.default.basename(String(obj.file_path)) : "file";
2099
2165
  const oldPreview = smartTruncate(String(obj.old_string), 120);
2100
2166
  const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
2101
2167
  return {
@@ -2158,20 +2224,24 @@ ${smartTruncate(str, 500)}`
2158
2224
  function escapePango(text) {
2159
2225
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2160
2226
  }
2161
- function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
2227
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2162
2228
  const lines = [];
2163
2229
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2164
2230
  lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2165
2231
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2166
2232
  lines.push("");
2167
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
+ }
2168
2238
  if (!locked) {
2169
2239
  lines.push("");
2170
2240
  lines.push('\u21B5 Enter = Allow \u21B5 | \u238B Esc = Block \u238B | "Always Allow" = never ask again');
2171
2241
  }
2172
2242
  return lines.join("\n");
2173
2243
  }
2174
- function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
2244
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2175
2245
  const lines = [];
2176
2246
  if (locked) {
2177
2247
  lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
@@ -2183,6 +2253,12 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2183
2253
  lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
2184
2254
  lines.push("");
2185
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
+ }
2186
2262
  if (!locked) {
2187
2263
  lines.push("");
2188
2264
  lines.push(
@@ -2191,12 +2267,19 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2191
2267
  }
2192
2268
  return lines.join("\n");
2193
2269
  }
2194
- 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) {
2195
2271
  if (isTestEnv()) return "deny";
2196
2272
  const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
2197
2273
  const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
2198
2274
  const title = locked ? `\u26A1 Node9 \u2014 Locked` : `\u{1F6E1}\uFE0F Node9 \u2014 ${intentLabel}`;
2199
- const message = buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked);
2275
+ const message = buildPlainMessage(
2276
+ toolName,
2277
+ formattedArgs,
2278
+ agent,
2279
+ explainableLabel,
2280
+ locked,
2281
+ allowCount
2282
+ );
2200
2283
  return new Promise((resolve) => {
2201
2284
  let childProcess = null;
2202
2285
  const onAbort = () => {
@@ -2228,7 +2311,8 @@ end run`;
2228
2311
  formattedArgs,
2229
2312
  agent,
2230
2313
  explainableLabel,
2231
- locked
2314
+ locked,
2315
+ allowCount
2232
2316
  );
2233
2317
  const argsList = [
2234
2318
  locked ? "--info" : "--question",
@@ -2270,8 +2354,8 @@ end run`;
2270
2354
  }
2271
2355
 
2272
2356
  // src/auth/cloud.ts
2273
- var import_fs8 = __toESM(require("fs"));
2274
- var import_os7 = __toESM(require("os"));
2357
+ var import_fs9 = __toESM(require("fs"));
2358
+ var import_os8 = __toESM(require("os"));
2275
2359
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2276
2360
  return fetch(`${creds.apiUrl}/audit`, {
2277
2361
  method: "POST",
@@ -2283,9 +2367,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2283
2367
  context: {
2284
2368
  agent: meta?.agent,
2285
2369
  mcpServer: meta?.mcpServer,
2286
- hostname: import_os7.default.hostname(),
2370
+ hostname: import_os8.default.hostname(),
2287
2371
  cwd: process.cwd(),
2288
- platform: import_os7.default.platform()
2372
+ platform: import_os8.default.platform()
2289
2373
  }
2290
2374
  }),
2291
2375
  signal: AbortSignal.timeout(5e3)
@@ -2306,9 +2390,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2306
2390
  context: {
2307
2391
  agent: meta?.agent,
2308
2392
  mcpServer: meta?.mcpServer,
2309
- hostname: import_os7.default.hostname(),
2393
+ hostname: import_os8.default.hostname(),
2310
2394
  cwd: process.cwd(),
2311
- platform: import_os7.default.platform()
2395
+ platform: import_os8.default.platform()
2312
2396
  },
2313
2397
  ...riskMetadata && { riskMetadata }
2314
2398
  }),
@@ -2364,14 +2448,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2364
2448
  });
2365
2449
  clearTimeout(timer);
2366
2450
  if (!res.ok) {
2367
- import_fs8.default.appendFileSync(
2451
+ import_fs9.default.appendFileSync(
2368
2452
  HOOK_DEBUG_LOG,
2369
2453
  `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
2370
2454
  `
2371
2455
  );
2372
2456
  }
2373
2457
  } catch (err) {
2374
- import_fs8.default.appendFileSync(
2458
+ import_fs9.default.appendFileSync(
2375
2459
  HOOK_DEBUG_LOG,
2376
2460
  `[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
2377
2461
  `
@@ -2380,7 +2464,7 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2380
2464
  }
2381
2465
 
2382
2466
  // src/auth/orchestrator.ts
2383
- var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path12.default.join(import_os8.default.tmpdir(), "node9-activity.sock");
2467
+ var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path13.default.join(import_os9.default.tmpdir(), "node9-activity.sock");
2384
2468
  function notifyActivity(data) {
2385
2469
  return new Promise((resolve) => {
2386
2470
  try {
@@ -2573,13 +2657,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2573
2657
  let viewerId = null;
2574
2658
  const internalToken = getInternalToken();
2575
2659
  let daemonEntryId = null;
2660
+ let daemonAllowCount = 1;
2576
2661
  if ((approvers.browser || approvers.terminal) && isDaemonRunning() && !options?.calledFromDaemon) {
2577
2662
  if (cloudEnforced && cloudRequestId) {
2578
- viewerId = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
2663
+ const viewer = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
2664
+ viewerId = viewer?.id ?? null;
2579
2665
  daemonEntryId = viewerId;
2666
+ if (viewer) daemonAllowCount = viewer.allowCount;
2580
2667
  } else {
2581
2668
  try {
2582
- daemonEntryId = await registerDaemonEntry(
2669
+ const entry = await registerDaemonEntry(
2583
2670
  toolName,
2584
2671
  args,
2585
2672
  meta,
@@ -2587,6 +2674,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2587
2674
  options?.activityId,
2588
2675
  options?.cwd
2589
2676
  );
2677
+ daemonEntryId = entry.id;
2678
+ daemonAllowCount = entry.allowCount;
2590
2679
  } catch {
2591
2680
  }
2592
2681
  }
@@ -2622,7 +2711,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2622
2711
  false,
2623
2712
  signal,
2624
2713
  policyMatchedField,
2625
- policyMatchedWord
2714
+ policyMatchedWord,
2715
+ daemonAllowCount
2626
2716
  );
2627
2717
  if (decision === "always_allow") {
2628
2718
  writeTrustSession(toolName, 36e5);
@@ -2680,10 +2770,13 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
2680
2770
  if (!resolved) {
2681
2771
  resolved = true;
2682
2772
  abortController.abort();
2683
- if (viewerId && internalToken) {
2684
- resolveViaDaemon(viewerId, res.approved ? "allow" : "deny", internalToken).catch(
2685
- () => null
2686
- );
2773
+ if (daemonEntryId && internalToken) {
2774
+ resolveViaDaemon(
2775
+ daemonEntryId,
2776
+ res.approved ? "allow" : "deny",
2777
+ internalToken,
2778
+ res.decisionSource
2779
+ ).catch(() => null);
2687
2780
  }
2688
2781
  resolve(res);
2689
2782
  }