@node9/proxy 1.3.2 → 1.4.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,37 @@ 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
+ function normalizeHost(raw) {
1496
+ return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
1497
+ }
1498
+ function isTrustedHost(host) {
1499
+ const normalized = normalizeHost(host);
1500
+ return readTrustedHosts().some((entry) => {
1501
+ const entryHost = entry.host.toLowerCase();
1502
+ if (entryHost.startsWith("*.")) {
1503
+ const domain = entryHost.slice(2);
1504
+ return normalized === domain || normalized.endsWith("." + domain);
1505
+ }
1506
+ return normalized === entryHost;
1507
+ });
1508
+ }
1509
+
1479
1510
  // src/policy/index.ts
1480
1511
  function tokenize2(toolName) {
1481
1512
  return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
@@ -1490,9 +1521,9 @@ function matchesPattern(text, patterns) {
1490
1521
  const withoutDotSlash = text.replace(/^\.\//, "");
1491
1522
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1492
1523
  }
1493
- function getNestedValue(obj, path13) {
1524
+ function getNestedValue(obj, path14) {
1494
1525
  if (!obj || typeof obj !== "object") return null;
1495
- return path13.split(".").reduce((prev, curr) => prev?.[curr], obj);
1526
+ return path14.split(".").reduce((prev, curr) => prev?.[curr], obj);
1496
1527
  }
1497
1528
  function evaluateSmartConditions(args, rule) {
1498
1529
  if (!rule.conditions || rule.conditions.length === 0) return true;
@@ -1647,23 +1678,34 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1647
1678
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
1648
1679
  }
1649
1680
  const pipeAnalysis = analyzePipeChain(shellCommand);
1650
- if (pipeAnalysis.isPipeline) {
1681
+ if (pipeAnalysis.isPipeline && (pipeAnalysis.risk === "critical" || pipeAnalysis.risk === "high")) {
1682
+ const sinks = pipeAnalysis.sinkTargets;
1683
+ const allTrusted = sinks.length > 0 && sinks.every(isTrustedHost);
1651
1684
  if (pipeAnalysis.risk === "critical") {
1685
+ if (allTrusted) {
1686
+ return {
1687
+ decision: "review",
1688
+ blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
1689
+ reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
1690
+ tier: 3
1691
+ };
1692
+ }
1652
1693
  return {
1653
1694
  decision: "block",
1654
1695
  blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
1655
- reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1696
+ reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1656
1697
  tier: 3
1657
1698
  };
1658
1699
  }
1659
- if (pipeAnalysis.risk === "high") {
1660
- 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(", ")}`,
1664
- tier: 3
1665
- };
1700
+ if (allTrusted) {
1701
+ return { decision: "allow" };
1666
1702
  }
1703
+ return {
1704
+ decision: "review",
1705
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1706
+ reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1707
+ tier: 3
1708
+ };
1667
1709
  }
1668
1710
  const firstToken = analyzed.actions[0] ?? "";
1669
1711
  if (["ssh", "scp", "rsync"].includes(firstToken)) {
@@ -1671,7 +1713,7 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1671
1713
  const sshHosts = extractAllSshHosts(rawTokens.slice(1));
1672
1714
  allTokens.push(...sshHosts);
1673
1715
  }
1674
- if (firstToken && import_path7.default.posix.isAbsolute(firstToken)) {
1716
+ if (firstToken && import_path8.default.posix.isAbsolute(firstToken)) {
1675
1717
  const prov = checkProvenance(firstToken, cwd);
1676
1718
  if (prov.trustLevel === "suspect") {
1677
1719
  return {
@@ -1772,18 +1814,18 @@ function isIgnoredTool(toolName) {
1772
1814
  }
1773
1815
 
1774
1816
  // 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");
1817
+ var import_fs7 = __toESM(require("fs"));
1818
+ var import_path9 = __toESM(require("path"));
1819
+ var import_os6 = __toESM(require("os"));
1820
+ var PAUSED_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "PAUSED");
1821
+ var TRUST_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "trust.json");
1780
1822
  function checkPause() {
1781
1823
  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"));
1824
+ if (!import_fs7.default.existsSync(PAUSED_FILE)) return { paused: false };
1825
+ const state = JSON.parse(import_fs7.default.readFileSync(PAUSED_FILE, "utf-8"));
1784
1826
  if (state.expiry > 0 && Date.now() >= state.expiry) {
1785
1827
  try {
1786
- import_fs6.default.unlinkSync(PAUSED_FILE);
1828
+ import_fs7.default.unlinkSync(PAUSED_FILE);
1787
1829
  } catch {
1788
1830
  }
1789
1831
  return { paused: false };
@@ -1794,20 +1836,20 @@ function checkPause() {
1794
1836
  }
1795
1837
  }
1796
1838
  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);
1839
+ const dir = import_path9.default.dirname(filePath);
1840
+ if (!import_fs7.default.existsSync(dir)) import_fs7.default.mkdirSync(dir, { recursive: true });
1841
+ const tmpPath = `${filePath}.${import_os6.default.hostname()}.${process.pid}.tmp`;
1842
+ import_fs7.default.writeFileSync(tmpPath, data, options);
1843
+ import_fs7.default.renameSync(tmpPath, filePath);
1802
1844
  }
1803
1845
  function getActiveTrustSession(toolName) {
1804
1846
  try {
1805
- if (!import_fs6.default.existsSync(TRUST_FILE)) return false;
1806
- const trust = JSON.parse(import_fs6.default.readFileSync(TRUST_FILE, "utf-8"));
1847
+ if (!import_fs7.default.existsSync(TRUST_FILE)) return false;
1848
+ const trust = JSON.parse(import_fs7.default.readFileSync(TRUST_FILE, "utf-8"));
1807
1849
  const now = Date.now();
1808
1850
  const active = trust.entries.filter((e) => e.expiry > now);
1809
1851
  if (active.length !== trust.entries.length) {
1810
- import_fs6.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1852
+ import_fs7.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1811
1853
  }
1812
1854
  return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
1813
1855
  } catch {
@@ -1818,8 +1860,8 @@ function writeTrustSession(toolName, durationMs) {
1818
1860
  try {
1819
1861
  let trust = { entries: [] };
1820
1862
  try {
1821
- if (import_fs6.default.existsSync(TRUST_FILE)) {
1822
- trust = JSON.parse(import_fs6.default.readFileSync(TRUST_FILE, "utf-8"));
1863
+ if (import_fs7.default.existsSync(TRUST_FILE)) {
1864
+ trust = JSON.parse(import_fs7.default.readFileSync(TRUST_FILE, "utf-8"));
1823
1865
  }
1824
1866
  } catch {
1825
1867
  }
@@ -1835,9 +1877,9 @@ function writeTrustSession(toolName, durationMs) {
1835
1877
  }
1836
1878
  function getPersistentDecision(toolName) {
1837
1879
  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"));
1880
+ const file = import_path9.default.join(import_os6.default.homedir(), ".node9", "decisions.json");
1881
+ if (!import_fs7.default.existsSync(file)) return null;
1882
+ const decisions = JSON.parse(import_fs7.default.readFileSync(file, "utf-8"));
1841
1883
  const d = decisions[toolName];
1842
1884
  if (d === "allow" || d === "deny") return d;
1843
1885
  } catch {
@@ -1846,17 +1888,17 @@ function getPersistentDecision(toolName) {
1846
1888
  }
1847
1889
 
1848
1890
  // 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"));
1891
+ var import_fs8 = __toESM(require("fs"));
1892
+ var import_path10 = __toESM(require("path"));
1893
+ var import_os7 = __toESM(require("os"));
1852
1894
  var import_child_process = require("child_process");
1853
1895
  var DAEMON_PORT = 7391;
1854
1896
  var DAEMON_HOST = "127.0.0.1";
1855
1897
  function getInternalToken() {
1856
1898
  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"));
1899
+ const pidFile = import_path10.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
1900
+ if (!import_fs8.default.existsSync(pidFile)) return null;
1901
+ const data = JSON.parse(import_fs8.default.readFileSync(pidFile, "utf-8"));
1860
1902
  process.kill(data.pid, 0);
1861
1903
  return data.internalToken ?? null;
1862
1904
  } catch {
@@ -1864,10 +1906,10 @@ function getInternalToken() {
1864
1906
  }
1865
1907
  }
1866
1908
  function isDaemonRunning() {
1867
- const pidFile = import_path9.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
1868
- if (import_fs7.default.existsSync(pidFile)) {
1909
+ const pidFile = import_path10.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
1910
+ if (import_fs8.default.existsSync(pidFile)) {
1869
1911
  try {
1870
- const { pid, port } = JSON.parse(import_fs7.default.readFileSync(pidFile, "utf-8"));
1912
+ const { pid, port } = JSON.parse(import_fs8.default.readFileSync(pidFile, "utf-8"));
1871
1913
  if (port !== DAEMON_PORT) return false;
1872
1914
  process.kill(pid, 0);
1873
1915
  return true;
@@ -1963,16 +2005,16 @@ async function resolveViaDaemon(id, decision, internalToken) {
1963
2005
 
1964
2006
  // src/auth/orchestrator.ts
1965
2007
  var import_net = __toESM(require("net"));
1966
- var import_path12 = __toESM(require("path"));
1967
- var import_os8 = __toESM(require("os"));
2008
+ var import_path13 = __toESM(require("path"));
2009
+ var import_os9 = __toESM(require("os"));
1968
2010
  var import_crypto = require("crypto");
1969
2011
 
1970
2012
  // src/ui/native.ts
1971
2013
  var import_child_process2 = require("child_process");
1972
- var import_path11 = __toESM(require("path"));
2014
+ var import_path12 = __toESM(require("path"));
1973
2015
 
1974
2016
  // src/context-sniper.ts
1975
- var import_path10 = __toESM(require("path"));
2017
+ var import_path11 = __toESM(require("path"));
1976
2018
  function smartTruncate(str, maxLen = 500) {
1977
2019
  if (str.length <= maxLen) return str;
1978
2020
  const edge = Math.floor(maxLen / 2) - 3;
@@ -2040,7 +2082,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
2040
2082
  intent = "EDIT";
2041
2083
  if (obj.file_path) {
2042
2084
  editFilePath = String(obj.file_path);
2043
- editFileName = import_path10.default.basename(editFilePath);
2085
+ editFileName = import_path11.default.basename(editFilePath);
2044
2086
  }
2045
2087
  const result = extractContext(String(obj.new_string), matchedWord);
2046
2088
  contextSnippet = result.snippet;
@@ -2095,7 +2137,7 @@ function formatArgs(args, matchedField, matchedWord) {
2095
2137
  if (typeof parsed === "object" && !Array.isArray(parsed)) {
2096
2138
  const obj = parsed;
2097
2139
  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";
2140
+ const file = obj.file_path ? import_path12.default.basename(String(obj.file_path)) : "file";
2099
2141
  const oldPreview = smartTruncate(String(obj.old_string), 120);
2100
2142
  const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
2101
2143
  return {
@@ -2270,8 +2312,8 @@ end run`;
2270
2312
  }
2271
2313
 
2272
2314
  // src/auth/cloud.ts
2273
- var import_fs8 = __toESM(require("fs"));
2274
- var import_os7 = __toESM(require("os"));
2315
+ var import_fs9 = __toESM(require("fs"));
2316
+ var import_os8 = __toESM(require("os"));
2275
2317
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2276
2318
  return fetch(`${creds.apiUrl}/audit`, {
2277
2319
  method: "POST",
@@ -2283,9 +2325,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2283
2325
  context: {
2284
2326
  agent: meta?.agent,
2285
2327
  mcpServer: meta?.mcpServer,
2286
- hostname: import_os7.default.hostname(),
2328
+ hostname: import_os8.default.hostname(),
2287
2329
  cwd: process.cwd(),
2288
- platform: import_os7.default.platform()
2330
+ platform: import_os8.default.platform()
2289
2331
  }
2290
2332
  }),
2291
2333
  signal: AbortSignal.timeout(5e3)
@@ -2306,9 +2348,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2306
2348
  context: {
2307
2349
  agent: meta?.agent,
2308
2350
  mcpServer: meta?.mcpServer,
2309
- hostname: import_os7.default.hostname(),
2351
+ hostname: import_os8.default.hostname(),
2310
2352
  cwd: process.cwd(),
2311
- platform: import_os7.default.platform()
2353
+ platform: import_os8.default.platform()
2312
2354
  },
2313
2355
  ...riskMetadata && { riskMetadata }
2314
2356
  }),
@@ -2364,14 +2406,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2364
2406
  });
2365
2407
  clearTimeout(timer);
2366
2408
  if (!res.ok) {
2367
- import_fs8.default.appendFileSync(
2409
+ import_fs9.default.appendFileSync(
2368
2410
  HOOK_DEBUG_LOG,
2369
2411
  `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
2370
2412
  `
2371
2413
  );
2372
2414
  }
2373
2415
  } catch (err) {
2374
- import_fs8.default.appendFileSync(
2416
+ import_fs9.default.appendFileSync(
2375
2417
  HOOK_DEBUG_LOG,
2376
2418
  `[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
2377
2419
  `
@@ -2380,7 +2422,7 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2380
2422
  }
2381
2423
 
2382
2424
  // 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");
2425
+ var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path13.default.join(import_os9.default.tmpdir(), "node9-activity.sock");
2384
2426
  function notifyActivity(data) {
2385
2427
  return new Promise((resolve) => {
2386
2428
  try {
package/dist/index.mjs CHANGED
@@ -155,8 +155,8 @@ function sanitizeConfig(raw) {
155
155
  }
156
156
  }
157
157
  const lines = result.error.issues.map((issue) => {
158
- const path13 = issue.path.length > 0 ? issue.path.join(".") : "root";
159
- return ` \u2022 ${path13}: ${issue.message}`;
158
+ const path14 = issue.path.length > 0 ? issue.path.join(".") : "root";
159
+ return ` \u2022 ${path14}: ${issue.message}`;
160
160
  });
161
161
  return {
162
162
  sanitized,
@@ -886,7 +886,7 @@ function getCompiledRegex(pattern, flags = "") {
886
886
  }
887
887
 
888
888
  // src/policy/index.ts
889
- import path7 from "path";
889
+ import path8 from "path";
890
890
  import pm from "picomatch";
891
891
  import { parse } from "sh-syntax";
892
892
 
@@ -1440,6 +1440,37 @@ function extractAllSshHosts(tokens) {
1440
1440
  return [...hosts].filter(Boolean);
1441
1441
  }
1442
1442
 
1443
+ // src/auth/trusted-hosts.ts
1444
+ import fs6 from "fs";
1445
+ import path7 from "path";
1446
+ import os5 from "os";
1447
+ function getTrustedHostsPath() {
1448
+ return path7.join(os5.homedir(), ".node9", "trusted-hosts.json");
1449
+ }
1450
+ function readTrustedHosts() {
1451
+ try {
1452
+ const raw = fs6.readFileSync(getTrustedHostsPath(), "utf8");
1453
+ const parsed = JSON.parse(raw);
1454
+ return Array.isArray(parsed.hosts) ? parsed.hosts : [];
1455
+ } catch {
1456
+ return [];
1457
+ }
1458
+ }
1459
+ function normalizeHost(raw) {
1460
+ return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
1461
+ }
1462
+ function isTrustedHost(host) {
1463
+ const normalized = normalizeHost(host);
1464
+ return readTrustedHosts().some((entry) => {
1465
+ const entryHost = entry.host.toLowerCase();
1466
+ if (entryHost.startsWith("*.")) {
1467
+ const domain = entryHost.slice(2);
1468
+ return normalized === domain || normalized.endsWith("." + domain);
1469
+ }
1470
+ return normalized === entryHost;
1471
+ });
1472
+ }
1473
+
1443
1474
  // src/policy/index.ts
1444
1475
  function tokenize2(toolName) {
1445
1476
  return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
@@ -1454,9 +1485,9 @@ function matchesPattern(text, patterns) {
1454
1485
  const withoutDotSlash = text.replace(/^\.\//, "");
1455
1486
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1456
1487
  }
1457
- function getNestedValue(obj, path13) {
1488
+ function getNestedValue(obj, path14) {
1458
1489
  if (!obj || typeof obj !== "object") return null;
1459
- return path13.split(".").reduce((prev, curr) => prev?.[curr], obj);
1490
+ return path14.split(".").reduce((prev, curr) => prev?.[curr], obj);
1460
1491
  }
1461
1492
  function evaluateSmartConditions(args, rule) {
1462
1493
  if (!rule.conditions || rule.conditions.length === 0) return true;
@@ -1611,23 +1642,34 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1611
1642
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
1612
1643
  }
1613
1644
  const pipeAnalysis = analyzePipeChain(shellCommand);
1614
- if (pipeAnalysis.isPipeline) {
1645
+ if (pipeAnalysis.isPipeline && (pipeAnalysis.risk === "critical" || pipeAnalysis.risk === "high")) {
1646
+ const sinks = pipeAnalysis.sinkTargets;
1647
+ const allTrusted = sinks.length > 0 && sinks.every(isTrustedHost);
1615
1648
  if (pipeAnalysis.risk === "critical") {
1649
+ if (allTrusted) {
1650
+ return {
1651
+ decision: "review",
1652
+ blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
1653
+ reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
1654
+ tier: 3
1655
+ };
1656
+ }
1616
1657
  return {
1617
1658
  decision: "block",
1618
1659
  blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
1619
- reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1660
+ reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1620
1661
  tier: 3
1621
1662
  };
1622
1663
  }
1623
- if (pipeAnalysis.risk === "high") {
1624
- return {
1625
- decision: "review",
1626
- blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1627
- reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1628
- tier: 3
1629
- };
1664
+ if (allTrusted) {
1665
+ return { decision: "allow" };
1630
1666
  }
1667
+ return {
1668
+ decision: "review",
1669
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1670
+ reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1671
+ tier: 3
1672
+ };
1631
1673
  }
1632
1674
  const firstToken = analyzed.actions[0] ?? "";
1633
1675
  if (["ssh", "scp", "rsync"].includes(firstToken)) {
@@ -1635,7 +1677,7 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1635
1677
  const sshHosts = extractAllSshHosts(rawTokens.slice(1));
1636
1678
  allTokens.push(...sshHosts);
1637
1679
  }
1638
- if (firstToken && path7.posix.isAbsolute(firstToken)) {
1680
+ if (firstToken && path8.posix.isAbsolute(firstToken)) {
1639
1681
  const prov = checkProvenance(firstToken, cwd);
1640
1682
  if (prov.trustLevel === "suspect") {
1641
1683
  return {
@@ -1736,18 +1778,18 @@ function isIgnoredTool(toolName) {
1736
1778
  }
1737
1779
 
1738
1780
  // src/auth/state.ts
1739
- import fs6 from "fs";
1740
- import path8 from "path";
1741
- import os5 from "os";
1742
- var PAUSED_FILE = path8.join(os5.homedir(), ".node9", "PAUSED");
1743
- var TRUST_FILE = path8.join(os5.homedir(), ".node9", "trust.json");
1781
+ import fs7 from "fs";
1782
+ import path9 from "path";
1783
+ import os6 from "os";
1784
+ var PAUSED_FILE = path9.join(os6.homedir(), ".node9", "PAUSED");
1785
+ var TRUST_FILE = path9.join(os6.homedir(), ".node9", "trust.json");
1744
1786
  function checkPause() {
1745
1787
  try {
1746
- if (!fs6.existsSync(PAUSED_FILE)) return { paused: false };
1747
- const state = JSON.parse(fs6.readFileSync(PAUSED_FILE, "utf-8"));
1788
+ if (!fs7.existsSync(PAUSED_FILE)) return { paused: false };
1789
+ const state = JSON.parse(fs7.readFileSync(PAUSED_FILE, "utf-8"));
1748
1790
  if (state.expiry > 0 && Date.now() >= state.expiry) {
1749
1791
  try {
1750
- fs6.unlinkSync(PAUSED_FILE);
1792
+ fs7.unlinkSync(PAUSED_FILE);
1751
1793
  } catch {
1752
1794
  }
1753
1795
  return { paused: false };
@@ -1758,20 +1800,20 @@ function checkPause() {
1758
1800
  }
1759
1801
  }
1760
1802
  function atomicWriteSync(filePath, data, options) {
1761
- const dir = path8.dirname(filePath);
1762
- if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
1763
- const tmpPath = `${filePath}.${os5.hostname()}.${process.pid}.tmp`;
1764
- fs6.writeFileSync(tmpPath, data, options);
1765
- fs6.renameSync(tmpPath, filePath);
1803
+ const dir = path9.dirname(filePath);
1804
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
1805
+ const tmpPath = `${filePath}.${os6.hostname()}.${process.pid}.tmp`;
1806
+ fs7.writeFileSync(tmpPath, data, options);
1807
+ fs7.renameSync(tmpPath, filePath);
1766
1808
  }
1767
1809
  function getActiveTrustSession(toolName) {
1768
1810
  try {
1769
- if (!fs6.existsSync(TRUST_FILE)) return false;
1770
- const trust = JSON.parse(fs6.readFileSync(TRUST_FILE, "utf-8"));
1811
+ if (!fs7.existsSync(TRUST_FILE)) return false;
1812
+ const trust = JSON.parse(fs7.readFileSync(TRUST_FILE, "utf-8"));
1771
1813
  const now = Date.now();
1772
1814
  const active = trust.entries.filter((e) => e.expiry > now);
1773
1815
  if (active.length !== trust.entries.length) {
1774
- fs6.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1816
+ fs7.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1775
1817
  }
1776
1818
  return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
1777
1819
  } catch {
@@ -1782,8 +1824,8 @@ function writeTrustSession(toolName, durationMs) {
1782
1824
  try {
1783
1825
  let trust = { entries: [] };
1784
1826
  try {
1785
- if (fs6.existsSync(TRUST_FILE)) {
1786
- trust = JSON.parse(fs6.readFileSync(TRUST_FILE, "utf-8"));
1827
+ if (fs7.existsSync(TRUST_FILE)) {
1828
+ trust = JSON.parse(fs7.readFileSync(TRUST_FILE, "utf-8"));
1787
1829
  }
1788
1830
  } catch {
1789
1831
  }
@@ -1799,9 +1841,9 @@ function writeTrustSession(toolName, durationMs) {
1799
1841
  }
1800
1842
  function getPersistentDecision(toolName) {
1801
1843
  try {
1802
- const file = path8.join(os5.homedir(), ".node9", "decisions.json");
1803
- if (!fs6.existsSync(file)) return null;
1804
- const decisions = JSON.parse(fs6.readFileSync(file, "utf-8"));
1844
+ const file = path9.join(os6.homedir(), ".node9", "decisions.json");
1845
+ if (!fs7.existsSync(file)) return null;
1846
+ const decisions = JSON.parse(fs7.readFileSync(file, "utf-8"));
1805
1847
  const d = decisions[toolName];
1806
1848
  if (d === "allow" || d === "deny") return d;
1807
1849
  } catch {
@@ -1810,17 +1852,17 @@ function getPersistentDecision(toolName) {
1810
1852
  }
1811
1853
 
1812
1854
  // src/auth/daemon.ts
1813
- import fs7 from "fs";
1814
- import path9 from "path";
1815
- import os6 from "os";
1855
+ import fs8 from "fs";
1856
+ import path10 from "path";
1857
+ import os7 from "os";
1816
1858
  import { spawnSync } from "child_process";
1817
1859
  var DAEMON_PORT = 7391;
1818
1860
  var DAEMON_HOST = "127.0.0.1";
1819
1861
  function getInternalToken() {
1820
1862
  try {
1821
- const pidFile = path9.join(os6.homedir(), ".node9", "daemon.pid");
1822
- if (!fs7.existsSync(pidFile)) return null;
1823
- const data = JSON.parse(fs7.readFileSync(pidFile, "utf-8"));
1863
+ const pidFile = path10.join(os7.homedir(), ".node9", "daemon.pid");
1864
+ if (!fs8.existsSync(pidFile)) return null;
1865
+ const data = JSON.parse(fs8.readFileSync(pidFile, "utf-8"));
1824
1866
  process.kill(data.pid, 0);
1825
1867
  return data.internalToken ?? null;
1826
1868
  } catch {
@@ -1828,10 +1870,10 @@ function getInternalToken() {
1828
1870
  }
1829
1871
  }
1830
1872
  function isDaemonRunning() {
1831
- const pidFile = path9.join(os6.homedir(), ".node9", "daemon.pid");
1832
- if (fs7.existsSync(pidFile)) {
1873
+ const pidFile = path10.join(os7.homedir(), ".node9", "daemon.pid");
1874
+ if (fs8.existsSync(pidFile)) {
1833
1875
  try {
1834
- const { pid, port } = JSON.parse(fs7.readFileSync(pidFile, "utf-8"));
1876
+ const { pid, port } = JSON.parse(fs8.readFileSync(pidFile, "utf-8"));
1835
1877
  if (port !== DAEMON_PORT) return false;
1836
1878
  process.kill(pid, 0);
1837
1879
  return true;
@@ -1927,16 +1969,16 @@ async function resolveViaDaemon(id, decision, internalToken) {
1927
1969
 
1928
1970
  // src/auth/orchestrator.ts
1929
1971
  import net from "net";
1930
- import path12 from "path";
1931
- import os8 from "os";
1972
+ import path13 from "path";
1973
+ import os9 from "os";
1932
1974
  import { randomUUID } from "crypto";
1933
1975
 
1934
1976
  // src/ui/native.ts
1935
1977
  import { spawn } from "child_process";
1936
- import path11 from "path";
1978
+ import path12 from "path";
1937
1979
 
1938
1980
  // src/context-sniper.ts
1939
- import path10 from "path";
1981
+ import path11 from "path";
1940
1982
  function smartTruncate(str, maxLen = 500) {
1941
1983
  if (str.length <= maxLen) return str;
1942
1984
  const edge = Math.floor(maxLen / 2) - 3;
@@ -2004,7 +2046,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
2004
2046
  intent = "EDIT";
2005
2047
  if (obj.file_path) {
2006
2048
  editFilePath = String(obj.file_path);
2007
- editFileName = path10.basename(editFilePath);
2049
+ editFileName = path11.basename(editFilePath);
2008
2050
  }
2009
2051
  const result = extractContext(String(obj.new_string), matchedWord);
2010
2052
  contextSnippet = result.snippet;
@@ -2059,7 +2101,7 @@ function formatArgs(args, matchedField, matchedWord) {
2059
2101
  if (typeof parsed === "object" && !Array.isArray(parsed)) {
2060
2102
  const obj = parsed;
2061
2103
  if (obj.old_string !== void 0 && obj.new_string !== void 0) {
2062
- const file = obj.file_path ? path11.basename(String(obj.file_path)) : "file";
2104
+ const file = obj.file_path ? path12.basename(String(obj.file_path)) : "file";
2063
2105
  const oldPreview = smartTruncate(String(obj.old_string), 120);
2064
2106
  const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
2065
2107
  return {
@@ -2234,8 +2276,8 @@ end run`;
2234
2276
  }
2235
2277
 
2236
2278
  // src/auth/cloud.ts
2237
- import fs8 from "fs";
2238
- import os7 from "os";
2279
+ import fs9 from "fs";
2280
+ import os8 from "os";
2239
2281
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2240
2282
  return fetch(`${creds.apiUrl}/audit`, {
2241
2283
  method: "POST",
@@ -2247,9 +2289,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2247
2289
  context: {
2248
2290
  agent: meta?.agent,
2249
2291
  mcpServer: meta?.mcpServer,
2250
- hostname: os7.hostname(),
2292
+ hostname: os8.hostname(),
2251
2293
  cwd: process.cwd(),
2252
- platform: os7.platform()
2294
+ platform: os8.platform()
2253
2295
  }
2254
2296
  }),
2255
2297
  signal: AbortSignal.timeout(5e3)
@@ -2270,9 +2312,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2270
2312
  context: {
2271
2313
  agent: meta?.agent,
2272
2314
  mcpServer: meta?.mcpServer,
2273
- hostname: os7.hostname(),
2315
+ hostname: os8.hostname(),
2274
2316
  cwd: process.cwd(),
2275
- platform: os7.platform()
2317
+ platform: os8.platform()
2276
2318
  },
2277
2319
  ...riskMetadata && { riskMetadata }
2278
2320
  }),
@@ -2328,14 +2370,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2328
2370
  });
2329
2371
  clearTimeout(timer);
2330
2372
  if (!res.ok) {
2331
- fs8.appendFileSync(
2373
+ fs9.appendFileSync(
2332
2374
  HOOK_DEBUG_LOG,
2333
2375
  `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
2334
2376
  `
2335
2377
  );
2336
2378
  }
2337
2379
  } catch (err) {
2338
- fs8.appendFileSync(
2380
+ fs9.appendFileSync(
2339
2381
  HOOK_DEBUG_LOG,
2340
2382
  `[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
2341
2383
  `
@@ -2344,7 +2386,7 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2344
2386
  }
2345
2387
 
2346
2388
  // src/auth/orchestrator.ts
2347
- var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path12.join(os8.tmpdir(), "node9-activity.sock");
2389
+ var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path13.join(os9.tmpdir(), "node9-activity.sock");
2348
2390
  function notifyActivity(data) {
2349
2391
  return new Promise((resolve) => {
2350
2392
  try {