@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/cli.js +493 -368
- package/dist/cli.mjs +479 -355
- package/dist/index.js +102 -60
- package/dist/index.mjs +102 -60
- package/package.json +1 -1
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
|
|
195
|
-
return ` \u2022 ${
|
|
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
|
|
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,
|
|
1524
|
+
function getNestedValue(obj, path14) {
|
|
1494
1525
|
if (!obj || typeof obj !== "object") return null;
|
|
1495
|
-
return
|
|
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 ${
|
|
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 (
|
|
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 &&
|
|
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
|
|
1776
|
-
var
|
|
1777
|
-
var
|
|
1778
|
-
var PAUSED_FILE =
|
|
1779
|
-
var TRUST_FILE =
|
|
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 (!
|
|
1783
|
-
const state = JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
1798
|
-
if (!
|
|
1799
|
-
const tmpPath = `${filePath}.${
|
|
1800
|
-
|
|
1801
|
-
|
|
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 (!
|
|
1806
|
-
const trust = JSON.parse(
|
|
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
|
-
|
|
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 (
|
|
1822
|
-
trust = JSON.parse(
|
|
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 =
|
|
1839
|
-
if (!
|
|
1840
|
-
const decisions = JSON.parse(
|
|
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
|
|
1850
|
-
var
|
|
1851
|
-
var
|
|
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 =
|
|
1858
|
-
if (!
|
|
1859
|
-
const data = JSON.parse(
|
|
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 =
|
|
1868
|
-
if (
|
|
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(
|
|
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
|
|
1967
|
-
var
|
|
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
|
|
2014
|
+
var import_path12 = __toESM(require("path"));
|
|
1973
2015
|
|
|
1974
2016
|
// src/context-sniper.ts
|
|
1975
|
-
var
|
|
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 =
|
|
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 ?
|
|
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
|
|
2274
|
-
var
|
|
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:
|
|
2328
|
+
hostname: import_os8.default.hostname(),
|
|
2287
2329
|
cwd: process.cwd(),
|
|
2288
|
-
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:
|
|
2351
|
+
hostname: import_os8.default.hostname(),
|
|
2310
2352
|
cwd: process.cwd(),
|
|
2311
|
-
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
|
-
|
|
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
|
-
|
|
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" :
|
|
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
|
|
159
|
-
return ` \u2022 ${
|
|
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
|
|
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,
|
|
1488
|
+
function getNestedValue(obj, path14) {
|
|
1458
1489
|
if (!obj || typeof obj !== "object") return null;
|
|
1459
|
-
return
|
|
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 ${
|
|
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 (
|
|
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 &&
|
|
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
|
|
1740
|
-
import
|
|
1741
|
-
import
|
|
1742
|
-
var PAUSED_FILE =
|
|
1743
|
-
var TRUST_FILE =
|
|
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 (!
|
|
1747
|
-
const state = JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
1762
|
-
if (!
|
|
1763
|
-
const tmpPath = `${filePath}.${
|
|
1764
|
-
|
|
1765
|
-
|
|
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 (!
|
|
1770
|
-
const trust = JSON.parse(
|
|
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
|
-
|
|
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 (
|
|
1786
|
-
trust = JSON.parse(
|
|
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 =
|
|
1803
|
-
if (!
|
|
1804
|
-
const decisions = JSON.parse(
|
|
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
|
|
1814
|
-
import
|
|
1815
|
-
import
|
|
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 =
|
|
1822
|
-
if (!
|
|
1823
|
-
const data = JSON.parse(
|
|
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 =
|
|
1832
|
-
if (
|
|
1873
|
+
const pidFile = path10.join(os7.homedir(), ".node9", "daemon.pid");
|
|
1874
|
+
if (fs8.existsSync(pidFile)) {
|
|
1833
1875
|
try {
|
|
1834
|
-
const { pid, port } = JSON.parse(
|
|
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
|
|
1931
|
-
import
|
|
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
|
|
1978
|
+
import path12 from "path";
|
|
1937
1979
|
|
|
1938
1980
|
// src/context-sniper.ts
|
|
1939
|
-
import
|
|
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 =
|
|
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 ?
|
|
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
|
|
2238
|
-
import
|
|
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:
|
|
2292
|
+
hostname: os8.hostname(),
|
|
2251
2293
|
cwd: process.cwd(),
|
|
2252
|
-
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:
|
|
2315
|
+
hostname: os8.hostname(),
|
|
2274
2316
|
cwd: process.cwd(),
|
|
2275
|
-
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
|
-
|
|
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
|
-
|
|
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" :
|
|
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 {
|