@node9/proxy 2.14.2 → 2.15.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 +1 -1
- package/dist/cli.js +1476 -1337
- package/dist/cli.mjs +1406 -1269
- package/dist/dashboard.mjs +82 -7
- package/dist/index.js +223 -88
- package/dist/index.mjs +223 -88
- package/package.json +5 -4
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __esm = (fn, res) => function __init() {
|
|
4
|
-
|
|
3
|
+
var __esm = (fn, res, err) => function __init() {
|
|
4
|
+
if (err) throw err[0];
|
|
5
|
+
try {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw err = [e], e;
|
|
9
|
+
}
|
|
5
10
|
};
|
|
6
11
|
var __export = (target, all) => {
|
|
7
12
|
for (var name in all)
|
|
@@ -377,8 +382,8 @@ var ConfigFileSchema = z.object({
|
|
|
377
382
|
}).strict();
|
|
378
383
|
function formatIssues(issues) {
|
|
379
384
|
const lines = issues.map((issue) => {
|
|
380
|
-
const
|
|
381
|
-
return ` \u2022 ${
|
|
385
|
+
const path16 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
|
|
386
|
+
return ` \u2022 ${path16}: ${issue.message}`;
|
|
382
387
|
});
|
|
383
388
|
return `Invalid config:
|
|
384
389
|
${lines.join("\n")}`;
|
|
@@ -391,9 +396,9 @@ function prunePaths(root, paths) {
|
|
|
391
396
|
const yi = y[y.length - 1];
|
|
392
397
|
return typeof xi === "number" && typeof yi === "number" ? yi - xi : 0;
|
|
393
398
|
});
|
|
394
|
-
for (const
|
|
399
|
+
for (const path16 of ordered) {
|
|
395
400
|
let cur = root;
|
|
396
|
-
for (const key of
|
|
401
|
+
for (const key of path16.slice(0, -1)) {
|
|
397
402
|
if (cur === null || typeof cur !== "object") {
|
|
398
403
|
cur = void 0;
|
|
399
404
|
break;
|
|
@@ -401,7 +406,7 @@ function prunePaths(root, paths) {
|
|
|
401
406
|
cur = cur[key];
|
|
402
407
|
}
|
|
403
408
|
if (cur === null || typeof cur !== "object") continue;
|
|
404
|
-
const last =
|
|
409
|
+
const last = path16[path16.length - 1];
|
|
405
410
|
if (Array.isArray(cur)) {
|
|
406
411
|
const i = Number(last);
|
|
407
412
|
if (Number.isInteger(i) && i >= 0 && i < cur.length) {
|
|
@@ -437,7 +442,7 @@ function sanitizeConfig(raw) {
|
|
|
437
442
|
return issue.keys.map((k) => [...at, k]);
|
|
438
443
|
}
|
|
439
444
|
return [at.slice(0, -level || void 0)];
|
|
440
|
-
}).filter((
|
|
445
|
+
}).filter((path16) => path16.length > 0);
|
|
441
446
|
if (paths.length === 0 || !prunePaths(working, paths)) break;
|
|
442
447
|
}
|
|
443
448
|
if (ConfigFileSchema.safeParse(working).success) break;
|
|
@@ -1349,9 +1354,9 @@ function matchesPattern(text, patterns) {
|
|
|
1349
1354
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1350
1355
|
}
|
|
1351
1356
|
var FORBIDDEN_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1352
|
-
function getNestedValue(obj,
|
|
1357
|
+
function getNestedValue(obj, path16) {
|
|
1353
1358
|
if (!obj || typeof obj !== "object") return null;
|
|
1354
|
-
const segments2 =
|
|
1359
|
+
const segments2 = path16.split(".");
|
|
1355
1360
|
for (const seg of segments2) {
|
|
1356
1361
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
1357
1362
|
}
|
|
@@ -4016,9 +4021,9 @@ function bareToolName(toolName) {
|
|
|
4016
4021
|
const parts = toolName.split("__");
|
|
4017
4022
|
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
4018
4023
|
}
|
|
4019
|
-
function valuesAt(args,
|
|
4024
|
+
function valuesAt(args, path16) {
|
|
4020
4025
|
let cursors = [args];
|
|
4021
|
-
for (const rawSegment of
|
|
4026
|
+
for (const rawSegment of path16.split(".")) {
|
|
4022
4027
|
const isArray = rawSegment.endsWith("[]");
|
|
4023
4028
|
const key = isArray ? rawSegment.slice(0, -2) : rawSegment;
|
|
4024
4029
|
const next = [];
|
|
@@ -4050,15 +4055,15 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
4050
4055
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
4051
4056
|
if (!paths) return null;
|
|
4052
4057
|
const exempt = new Set((opts.ssrfAllow ?? []).map((a) => classifySsrf(a)?.normalized ?? a));
|
|
4053
|
-
for (const
|
|
4054
|
-
for (const value of valuesAt(args,
|
|
4058
|
+
for (const path16 of paths) {
|
|
4059
|
+
for (const value of valuesAt(args, path16)) {
|
|
4055
4060
|
const host = hostOf(value);
|
|
4056
4061
|
if (!host) continue;
|
|
4057
4062
|
const m = classifySsrf(host);
|
|
4058
4063
|
if (!m) continue;
|
|
4059
4064
|
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
4060
4065
|
if (m.overridable && m.normalized && exempt.has(m.normalized)) continue;
|
|
4061
|
-
return { ...m, argPath:
|
|
4066
|
+
return { ...m, argPath: path16, host, reason: ssrfReason(m, host) };
|
|
4062
4067
|
}
|
|
4063
4068
|
}
|
|
4064
4069
|
return null;
|
|
@@ -4181,7 +4186,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4181
4186
|
reason: dest.reason,
|
|
4182
4187
|
ruleName: `ssrf:${dest.tier}:${toolName}:${dest.host}`,
|
|
4183
4188
|
ruleDescription: dest.reason,
|
|
4184
|
-
tier: 3
|
|
4189
|
+
tier: 3,
|
|
4190
|
+
overridable: dest.overridable
|
|
4185
4191
|
};
|
|
4186
4192
|
}
|
|
4187
4193
|
}
|
|
@@ -4330,7 +4336,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4330
4336
|
reason: ssrf.reason,
|
|
4331
4337
|
ruleName: `ssrf:${ssrf.tier}:${ssrf.binary}:${ssrf.host}`,
|
|
4332
4338
|
ruleDescription: ssrf.reason,
|
|
4333
|
-
tier: 3
|
|
4339
|
+
tier: 3,
|
|
4340
|
+
overridable: ssrf.overridable
|
|
4334
4341
|
};
|
|
4335
4342
|
}
|
|
4336
4343
|
}
|
|
@@ -5836,6 +5843,45 @@ function isTrustedHost(host) {
|
|
|
5836
5843
|
);
|
|
5837
5844
|
}
|
|
5838
5845
|
|
|
5846
|
+
// src/auth/api-url.ts
|
|
5847
|
+
var DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
5848
|
+
var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]", "0.0.0.0"]);
|
|
5849
|
+
var HOST_ALLOW_ENV = "NODE9_API_HOST_ALLOW";
|
|
5850
|
+
function defaultHostSuffixes() {
|
|
5851
|
+
const host = new URL(DEFAULT_API_URL).hostname.toLowerCase();
|
|
5852
|
+
const parent = host.split(".").slice(1).join(".");
|
|
5853
|
+
return parent.includes(".") ? [host, parent] : [host];
|
|
5854
|
+
}
|
|
5855
|
+
function allowedSuffixes() {
|
|
5856
|
+
const extra = (process.env[HOST_ALLOW_ENV] ?? "").split(",").map(
|
|
5857
|
+
(s) => s.trim().toLowerCase().replace(/^\*?\./, "")
|
|
5858
|
+
).filter(Boolean);
|
|
5859
|
+
return [...defaultHostSuffixes(), ...extra];
|
|
5860
|
+
}
|
|
5861
|
+
function validateApiUrl(raw) {
|
|
5862
|
+
if (typeof raw !== "string" || raw.length === 0 || raw.length > 2048) return null;
|
|
5863
|
+
let u;
|
|
5864
|
+
try {
|
|
5865
|
+
u = new URL(raw);
|
|
5866
|
+
} catch {
|
|
5867
|
+
return null;
|
|
5868
|
+
}
|
|
5869
|
+
if (u.username || u.password) return null;
|
|
5870
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
|
|
5871
|
+
const host = u.hostname.toLowerCase();
|
|
5872
|
+
if (LOOPBACK.has(host)) return u;
|
|
5873
|
+
if (u.protocol !== "https:") return null;
|
|
5874
|
+
const suffixes = allowedSuffixes();
|
|
5875
|
+
const ok = suffixes.some((s) => host === s || host.endsWith("." + s));
|
|
5876
|
+
return ok ? u : null;
|
|
5877
|
+
}
|
|
5878
|
+
function safeApiUrl(raw, onReject) {
|
|
5879
|
+
const ok = validateApiUrl(raw);
|
|
5880
|
+
if (ok) return typeof raw === "string" ? raw : ok.toString();
|
|
5881
|
+
onReject?.(raw);
|
|
5882
|
+
return DEFAULT_API_URL;
|
|
5883
|
+
}
|
|
5884
|
+
|
|
5839
5885
|
// src/config/index.ts
|
|
5840
5886
|
function sanitizeSsrfAllow(entries, source) {
|
|
5841
5887
|
const kept = [];
|
|
@@ -6124,12 +6170,27 @@ var ADVISORY_SMART_RULES = [
|
|
|
6124
6170
|
}
|
|
6125
6171
|
];
|
|
6126
6172
|
var cachedConfig = null;
|
|
6173
|
+
var rejectedApiUrlsSeen = /* @__PURE__ */ new Set();
|
|
6174
|
+
function noteRejectedApiUrl(raw) {
|
|
6175
|
+
const key = String(raw).slice(0, 200);
|
|
6176
|
+
if (rejectedApiUrlsSeen.has(key)) return;
|
|
6177
|
+
rejectedApiUrlsSeen.add(key);
|
|
6178
|
+
try {
|
|
6179
|
+
fs4.appendFileSync(
|
|
6180
|
+
path4.join(os4.homedir(), ".node9", "hook-debug.log"),
|
|
6181
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] REFUSED apiUrl, using the default instead: ${String(raw).slice(0, 200).replace(/[\r\n]+/g, " ")}
|
|
6182
|
+
`
|
|
6183
|
+
);
|
|
6184
|
+
} catch {
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6127
6187
|
function getCredentials() {
|
|
6128
|
-
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
6129
6188
|
if (process.env.NODE9_API_KEY) {
|
|
6130
6189
|
return {
|
|
6131
6190
|
apiKey: process.env.NODE9_API_KEY,
|
|
6132
|
-
|
|
6191
|
+
// NODE9_API_URL is env, and a hook inherits the agent's env, so it is
|
|
6192
|
+
// no more trusted than the file below.
|
|
6193
|
+
apiUrl: safeApiUrl(process.env.NODE9_API_URL || DEFAULT_API_URL, noteRejectedApiUrl)
|
|
6133
6194
|
};
|
|
6134
6195
|
}
|
|
6135
6196
|
try {
|
|
@@ -6141,14 +6202,14 @@ function getCredentials() {
|
|
|
6141
6202
|
if (profile?.apiKey) {
|
|
6142
6203
|
return {
|
|
6143
6204
|
apiKey: profile.apiKey,
|
|
6144
|
-
apiUrl: profile.apiUrl || DEFAULT_API_URL,
|
|
6205
|
+
apiUrl: safeApiUrl(profile.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6145
6206
|
localOnly: profile.localOnly === true || profileName !== "default"
|
|
6146
6207
|
};
|
|
6147
6208
|
}
|
|
6148
6209
|
if (creds.apiKey) {
|
|
6149
6210
|
return {
|
|
6150
6211
|
apiKey: creds.apiKey,
|
|
6151
|
-
apiUrl: creds.apiUrl || DEFAULT_API_URL,
|
|
6212
|
+
apiUrl: safeApiUrl(creds.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6152
6213
|
localOnly: creds.localOnly === true
|
|
6153
6214
|
};
|
|
6154
6215
|
}
|
|
@@ -6581,13 +6642,13 @@ function getConfig(cwd) {
|
|
|
6581
6642
|
}
|
|
6582
6643
|
if (Array.isArray(mc.jailPaths)) {
|
|
6583
6644
|
for (const jp of mc.jailPaths) {
|
|
6584
|
-
const
|
|
6585
|
-
if (!
|
|
6645
|
+
const path16 = typeof jp?.path === "string" ? jp.path.trim() : "";
|
|
6646
|
+
if (!path16) continue;
|
|
6586
6647
|
const verdict = jp?.verdict === "review" ? "review" : "block";
|
|
6587
|
-
for (const r of pathRules(
|
|
6648
|
+
for (const r of pathRules(path16, verdict, "org-managed jail")) {
|
|
6588
6649
|
mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
|
|
6589
6650
|
}
|
|
6590
|
-
mergedPolicy.managedJailPaths.push({ path:
|
|
6651
|
+
mergedPolicy.managedJailPaths.push({ path: path16, verdict });
|
|
6591
6652
|
}
|
|
6592
6653
|
}
|
|
6593
6654
|
if (Array.isArray(mc.trustedHosts)) {
|
|
@@ -6924,11 +6985,41 @@ function isIgnoredTool2(toolName) {
|
|
|
6924
6985
|
}
|
|
6925
6986
|
|
|
6926
6987
|
// src/auth/state.ts
|
|
6988
|
+
import fs8 from "fs";
|
|
6989
|
+
import path8 from "path";
|
|
6990
|
+
import os6 from "os";
|
|
6991
|
+
|
|
6992
|
+
// src/utils/atomic-write.ts
|
|
6927
6993
|
import fs7 from "fs";
|
|
6928
6994
|
import path7 from "path";
|
|
6929
|
-
import
|
|
6930
|
-
|
|
6931
|
-
|
|
6995
|
+
import { randomUUID } from "crypto";
|
|
6996
|
+
function atomicWriteSync(filePath, data, options) {
|
|
6997
|
+
const dir = path7.dirname(filePath);
|
|
6998
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
6999
|
+
const tmpPath = `${filePath}.${randomUUID()}.tmp`;
|
|
7000
|
+
try {
|
|
7001
|
+
fs7.writeFileSync(tmpPath, data, options);
|
|
7002
|
+
} catch (err) {
|
|
7003
|
+
try {
|
|
7004
|
+
fs7.unlinkSync(tmpPath);
|
|
7005
|
+
} catch {
|
|
7006
|
+
}
|
|
7007
|
+
throw err;
|
|
7008
|
+
}
|
|
7009
|
+
try {
|
|
7010
|
+
fs7.renameSync(tmpPath, filePath);
|
|
7011
|
+
} catch (err) {
|
|
7012
|
+
try {
|
|
7013
|
+
fs7.unlinkSync(tmpPath);
|
|
7014
|
+
} catch {
|
|
7015
|
+
}
|
|
7016
|
+
throw err;
|
|
7017
|
+
}
|
|
7018
|
+
}
|
|
7019
|
+
|
|
7020
|
+
// src/auth/state.ts
|
|
7021
|
+
var PAUSED_FILE = path8.join(os6.homedir(), ".node9", "PAUSED");
|
|
7022
|
+
var TRUST_FILE = path8.join(os6.homedir(), ".node9", "trust.json");
|
|
6932
7023
|
function extractCommandPattern(toolName, args) {
|
|
6933
7024
|
const lower = toolName.toLowerCase();
|
|
6934
7025
|
if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
|
|
@@ -6940,11 +7031,11 @@ function extractCommandPattern(toolName, args) {
|
|
|
6940
7031
|
}
|
|
6941
7032
|
function checkPause() {
|
|
6942
7033
|
try {
|
|
6943
|
-
if (!
|
|
6944
|
-
const state = JSON.parse(
|
|
7034
|
+
if (!fs8.existsSync(PAUSED_FILE)) return { paused: false };
|
|
7035
|
+
const state = JSON.parse(fs8.readFileSync(PAUSED_FILE, "utf-8"));
|
|
6945
7036
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
6946
7037
|
try {
|
|
6947
|
-
|
|
7038
|
+
fs8.unlinkSync(PAUSED_FILE);
|
|
6948
7039
|
} catch {
|
|
6949
7040
|
}
|
|
6950
7041
|
return { paused: false };
|
|
@@ -6954,21 +7045,14 @@ function checkPause() {
|
|
|
6954
7045
|
return { paused: false };
|
|
6955
7046
|
}
|
|
6956
7047
|
}
|
|
6957
|
-
function atomicWriteSync(filePath, data, options) {
|
|
6958
|
-
const dir = path7.dirname(filePath);
|
|
6959
|
-
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
6960
|
-
const tmpPath = `${filePath}.${os6.hostname()}.${process.pid}.tmp`;
|
|
6961
|
-
fs7.writeFileSync(tmpPath, data, options);
|
|
6962
|
-
fs7.renameSync(tmpPath, filePath);
|
|
6963
|
-
}
|
|
6964
7048
|
function getActiveTrustSession(toolName, args) {
|
|
6965
7049
|
try {
|
|
6966
|
-
if (!
|
|
6967
|
-
const trust = JSON.parse(
|
|
7050
|
+
if (!fs8.existsSync(TRUST_FILE)) return false;
|
|
7051
|
+
const trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
|
|
6968
7052
|
const now = Date.now();
|
|
6969
7053
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
6970
7054
|
if (active.length !== trust.entries.length) {
|
|
6971
|
-
|
|
7055
|
+
atomicWriteSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
6972
7056
|
}
|
|
6973
7057
|
return active.some((e) => {
|
|
6974
7058
|
if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
|
|
@@ -6987,8 +7071,8 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
6987
7071
|
try {
|
|
6988
7072
|
let trust = { entries: [] };
|
|
6989
7073
|
try {
|
|
6990
|
-
if (
|
|
6991
|
-
trust = JSON.parse(
|
|
7074
|
+
if (fs8.existsSync(TRUST_FILE)) {
|
|
7075
|
+
trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
|
|
6992
7076
|
}
|
|
6993
7077
|
} catch {
|
|
6994
7078
|
}
|
|
@@ -7010,9 +7094,9 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
7010
7094
|
}
|
|
7011
7095
|
function getPersistentDecision(toolName) {
|
|
7012
7096
|
try {
|
|
7013
|
-
const file =
|
|
7014
|
-
if (!
|
|
7015
|
-
const decisions = JSON.parse(
|
|
7097
|
+
const file = path8.join(os6.homedir(), ".node9", "decisions.json");
|
|
7098
|
+
if (!fs8.existsSync(file)) return null;
|
|
7099
|
+
const decisions = JSON.parse(fs8.readFileSync(file, "utf-8"));
|
|
7016
7100
|
const d = decisions[toolName];
|
|
7017
7101
|
if (d === "allow" || d === "deny") return d;
|
|
7018
7102
|
} catch {
|
|
@@ -7021,11 +7105,11 @@ function getPersistentDecision(toolName) {
|
|
|
7021
7105
|
}
|
|
7022
7106
|
|
|
7023
7107
|
// src/auth/daemon.ts
|
|
7024
|
-
import
|
|
7108
|
+
import fs9 from "fs";
|
|
7025
7109
|
import net from "net";
|
|
7026
|
-
import
|
|
7110
|
+
import path9 from "path";
|
|
7027
7111
|
import os7 from "os";
|
|
7028
|
-
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
7112
|
+
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path9.join(os7.tmpdir(), "node9-activity.sock");
|
|
7029
7113
|
function notifyActivitySocket(data) {
|
|
7030
7114
|
return new Promise((resolve) => {
|
|
7031
7115
|
try {
|
|
@@ -7058,9 +7142,9 @@ var DAEMON_PORT = 7391;
|
|
|
7058
7142
|
var DAEMON_HOST = "127.0.0.1";
|
|
7059
7143
|
function getInternalToken() {
|
|
7060
7144
|
try {
|
|
7061
|
-
const pidFile =
|
|
7062
|
-
if (!
|
|
7063
|
-
const data = JSON.parse(
|
|
7145
|
+
const pidFile = path9.join(os7.homedir(), ".node9", "daemon.pid");
|
|
7146
|
+
if (!fs9.existsSync(pidFile)) return null;
|
|
7147
|
+
const data = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
|
|
7064
7148
|
process.kill(data.pid, 0);
|
|
7065
7149
|
return data.internalToken ?? null;
|
|
7066
7150
|
} catch {
|
|
@@ -7068,12 +7152,12 @@ function getInternalToken() {
|
|
|
7068
7152
|
}
|
|
7069
7153
|
}
|
|
7070
7154
|
function isDaemonRunning() {
|
|
7071
|
-
const pidFile =
|
|
7072
|
-
if (
|
|
7155
|
+
const pidFile = path9.join(os7.homedir(), ".node9", "daemon.pid");
|
|
7156
|
+
if (fs9.existsSync(pidFile)) {
|
|
7073
7157
|
let pid;
|
|
7074
7158
|
let port;
|
|
7075
7159
|
try {
|
|
7076
|
-
const data = JSON.parse(
|
|
7160
|
+
const data = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
|
|
7077
7161
|
pid = data.pid;
|
|
7078
7162
|
port = data.port;
|
|
7079
7163
|
} catch {
|
|
@@ -7091,7 +7175,7 @@ function isDaemonRunning() {
|
|
|
7091
7175
|
} catch (err) {
|
|
7092
7176
|
if (err instanceof Error && "code" in err && err.code === "ESRCH") {
|
|
7093
7177
|
try {
|
|
7094
|
-
|
|
7178
|
+
fs9.unlinkSync(pidFile);
|
|
7095
7179
|
} catch {
|
|
7096
7180
|
}
|
|
7097
7181
|
}
|
|
@@ -7257,14 +7341,14 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
|
7257
7341
|
}
|
|
7258
7342
|
|
|
7259
7343
|
// src/auth/orchestrator.ts
|
|
7260
|
-
import { randomUUID } from "crypto";
|
|
7344
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
7261
7345
|
|
|
7262
7346
|
// src/ui/native.ts
|
|
7263
7347
|
import { spawn } from "child_process";
|
|
7264
|
-
import
|
|
7348
|
+
import path11 from "path";
|
|
7265
7349
|
|
|
7266
7350
|
// src/context-sniper.ts
|
|
7267
|
-
import
|
|
7351
|
+
import path10 from "path";
|
|
7268
7352
|
function smartTruncate(str, maxLen = 500) {
|
|
7269
7353
|
if (str.length <= maxLen) return str;
|
|
7270
7354
|
const edge = Math.floor(maxLen / 2) - 3;
|
|
@@ -7332,7 +7416,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
7332
7416
|
intent = "EDIT";
|
|
7333
7417
|
if (obj.file_path) {
|
|
7334
7418
|
editFilePath = String(obj.file_path);
|
|
7335
|
-
editFileName =
|
|
7419
|
+
editFileName = path10.basename(editFilePath);
|
|
7336
7420
|
}
|
|
7337
7421
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
7338
7422
|
contextSnippet = result.snippet;
|
|
@@ -7396,7 +7480,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
7396
7480
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7397
7481
|
const obj = parsed;
|
|
7398
7482
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
7399
|
-
const file = obj.file_path ?
|
|
7483
|
+
const file = obj.file_path ? path11.basename(String(obj.file_path)) : "file";
|
|
7400
7484
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
7401
7485
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
7402
7486
|
return {
|
|
@@ -7594,22 +7678,22 @@ end run`;
|
|
|
7594
7678
|
}
|
|
7595
7679
|
|
|
7596
7680
|
// src/canary/registry.ts
|
|
7597
|
-
import
|
|
7681
|
+
import fs11 from "fs";
|
|
7598
7682
|
import os9 from "os";
|
|
7599
|
-
import
|
|
7683
|
+
import path13 from "path";
|
|
7600
7684
|
|
|
7601
7685
|
// src/shields/jail.ts
|
|
7602
|
-
import
|
|
7686
|
+
import fs10 from "fs";
|
|
7603
7687
|
import os8 from "os";
|
|
7604
|
-
import
|
|
7688
|
+
import path12 from "path";
|
|
7605
7689
|
var USER_JAIL_SHIELD = "user-jail";
|
|
7606
7690
|
function jailStorePath() {
|
|
7607
|
-
return
|
|
7691
|
+
return path12.join(os8.homedir(), ".node9", "jail-paths.json");
|
|
7608
7692
|
}
|
|
7609
7693
|
function readJailPaths() {
|
|
7610
7694
|
let text;
|
|
7611
7695
|
try {
|
|
7612
|
-
text =
|
|
7696
|
+
text = fs10.readFileSync(jailStorePath(), "utf8");
|
|
7613
7697
|
} catch (err) {
|
|
7614
7698
|
if (err.code === "ENOENT") return [];
|
|
7615
7699
|
throw err;
|
|
@@ -7638,7 +7722,7 @@ function findJailedPathIn(candidate, paths) {
|
|
|
7638
7722
|
|
|
7639
7723
|
// src/canary/registry.ts
|
|
7640
7724
|
function canaryStorePath() {
|
|
7641
|
-
return
|
|
7725
|
+
return path13.join(os9.homedir(), ".node9", "canaries.json");
|
|
7642
7726
|
}
|
|
7643
7727
|
function isRecord(x) {
|
|
7644
7728
|
if (!x || typeof x !== "object") return false;
|
|
@@ -7649,7 +7733,7 @@ function loadCanaries(opts) {
|
|
|
7649
7733
|
const p = canaryStorePath();
|
|
7650
7734
|
let raw;
|
|
7651
7735
|
try {
|
|
7652
|
-
raw =
|
|
7736
|
+
raw = fs11.readFileSync(p, "utf-8");
|
|
7653
7737
|
} catch (e) {
|
|
7654
7738
|
if (e.code === "ENOENT") return [];
|
|
7655
7739
|
throw e;
|
|
@@ -7676,9 +7760,50 @@ init_audit();
|
|
|
7676
7760
|
|
|
7677
7761
|
// src/auth/cloud.ts
|
|
7678
7762
|
init_audit();
|
|
7679
|
-
import
|
|
7763
|
+
import fs13 from "fs";
|
|
7680
7764
|
import os10 from "os";
|
|
7681
|
-
import
|
|
7765
|
+
import path14 from "path";
|
|
7766
|
+
|
|
7767
|
+
// src/utils/read-capped.ts
|
|
7768
|
+
import fs12 from "fs";
|
|
7769
|
+
function readCapped(file, maxBytes) {
|
|
7770
|
+
if (maxBytes < 0) return null;
|
|
7771
|
+
let fd;
|
|
7772
|
+
try {
|
|
7773
|
+
fd = fs12.openSync(file, "r");
|
|
7774
|
+
} catch {
|
|
7775
|
+
return null;
|
|
7776
|
+
}
|
|
7777
|
+
try {
|
|
7778
|
+
const st = fs12.fstatSync(fd);
|
|
7779
|
+
if (!st.isFile()) return null;
|
|
7780
|
+
const want = Math.min(st.size, maxBytes + 1);
|
|
7781
|
+
const buf = Buffer.alloc(want);
|
|
7782
|
+
let read = 0;
|
|
7783
|
+
while (read < want) {
|
|
7784
|
+
const n = fs12.readSync(fd, buf, read, want - read, read);
|
|
7785
|
+
if (n <= 0) break;
|
|
7786
|
+
read += n;
|
|
7787
|
+
}
|
|
7788
|
+
return {
|
|
7789
|
+
bytes: buf.subarray(0, Math.min(read, maxBytes)),
|
|
7790
|
+
truncated: read > maxBytes
|
|
7791
|
+
};
|
|
7792
|
+
} catch {
|
|
7793
|
+
return null;
|
|
7794
|
+
} finally {
|
|
7795
|
+
try {
|
|
7796
|
+
fs12.closeSync(fd);
|
|
7797
|
+
} catch {
|
|
7798
|
+
}
|
|
7799
|
+
}
|
|
7800
|
+
}
|
|
7801
|
+
function readCappedText(file, maxBytes) {
|
|
7802
|
+
const r = readCapped(file, maxBytes);
|
|
7803
|
+
return r ? { text: r.bytes.toString("utf8"), truncated: r.truncated } : null;
|
|
7804
|
+
}
|
|
7805
|
+
|
|
7806
|
+
// src/auth/cloud.ts
|
|
7682
7807
|
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
7683
7808
|
const controller = new AbortController();
|
|
7684
7809
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
@@ -7686,10 +7811,11 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
|
|
|
7686
7811
|
let ciContext;
|
|
7687
7812
|
if (process.env.CI) {
|
|
7688
7813
|
try {
|
|
7689
|
-
const ciContextPath =
|
|
7690
|
-
const
|
|
7691
|
-
if (
|
|
7692
|
-
|
|
7814
|
+
const ciContextPath = path14.join(os10.homedir(), ".node9", "ci-context.json");
|
|
7815
|
+
const ci = readCappedText(ciContextPath, 1e4);
|
|
7816
|
+
if (!ci) throw new Error("ci-context.json unreadable");
|
|
7817
|
+
if (ci.truncated) throw new Error("ci-context.json exceeds 10 KB");
|
|
7818
|
+
const raw = ci.text;
|
|
7693
7819
|
const parsed = JSON.parse(raw);
|
|
7694
7820
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
7695
7821
|
throw new Error("ci-context.json is not a plain object");
|
|
@@ -7788,14 +7914,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
7788
7914
|
});
|
|
7789
7915
|
clearTimeout(timer);
|
|
7790
7916
|
if (!res.ok) {
|
|
7791
|
-
|
|
7917
|
+
fs13.appendFileSync(
|
|
7792
7918
|
HOOK_DEBUG_LOG,
|
|
7793
7919
|
`[resolve-cloud] PATCH ${safeMessage(resolveUrl, 200)} \u2192 HTTP ${res.status}
|
|
7794
7920
|
`
|
|
7795
7921
|
);
|
|
7796
7922
|
}
|
|
7797
7923
|
} catch (err) {
|
|
7798
|
-
|
|
7924
|
+
fs13.appendFileSync(
|
|
7799
7925
|
HOOK_DEBUG_LOG,
|
|
7800
7926
|
`[resolve-cloud] PATCH failed for ${safeMessage(requestId, 64)}: ${safeMessage(err)}
|
|
7801
7927
|
`
|
|
@@ -7804,16 +7930,16 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
7804
7930
|
}
|
|
7805
7931
|
|
|
7806
7932
|
// src/loop-detector.ts
|
|
7807
|
-
import
|
|
7808
|
-
import
|
|
7933
|
+
import fs14 from "fs";
|
|
7934
|
+
import path15 from "path";
|
|
7809
7935
|
import os11 from "os";
|
|
7810
7936
|
function loopStateFile() {
|
|
7811
|
-
return
|
|
7937
|
+
return path15.join(os11.homedir(), ".node9", "loop-state.json");
|
|
7812
7938
|
}
|
|
7813
7939
|
function readState() {
|
|
7814
7940
|
try {
|
|
7815
|
-
if (!
|
|
7816
|
-
const raw =
|
|
7941
|
+
if (!fs14.existsSync(loopStateFile())) return [];
|
|
7942
|
+
const raw = fs14.readFileSync(loopStateFile(), "utf-8");
|
|
7817
7943
|
const parsed = JSON.parse(raw);
|
|
7818
7944
|
if (!Array.isArray(parsed)) return [];
|
|
7819
7945
|
return parsed;
|
|
@@ -7822,11 +7948,11 @@ function readState() {
|
|
|
7822
7948
|
}
|
|
7823
7949
|
}
|
|
7824
7950
|
function writeState(records) {
|
|
7825
|
-
const dir =
|
|
7826
|
-
if (!
|
|
7951
|
+
const dir = path15.dirname(loopStateFile());
|
|
7952
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
7827
7953
|
const tmpPath = `${loopStateFile()}.${os11.hostname()}.${process.pid}.tmp`;
|
|
7828
|
-
|
|
7829
|
-
|
|
7954
|
+
fs14.writeFileSync(tmpPath, JSON.stringify(records));
|
|
7955
|
+
fs14.renameSync(tmpPath, loopStateFile());
|
|
7830
7956
|
}
|
|
7831
7957
|
function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
7832
7958
|
try {
|
|
@@ -7894,9 +8020,13 @@ async function hasReachableHumanApprover(opts) {
|
|
|
7894
8020
|
if (nativeReachable) return true;
|
|
7895
8021
|
return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
|
|
7896
8022
|
}
|
|
8023
|
+
function mayDowngradeHardBlock(opts) {
|
|
8024
|
+
if (opts.overridable === false) return false;
|
|
8025
|
+
return opts.daemonUp && !opts.isTestEnv && opts.humanApproverReachable;
|
|
8026
|
+
}
|
|
7897
8027
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
7898
8028
|
if (!options?.calledFromDaemon) {
|
|
7899
|
-
const actId =
|
|
8029
|
+
const actId = randomUUID2();
|
|
7900
8030
|
const actTs = Date.now();
|
|
7901
8031
|
const sanitizedAgent = meta?.agent ? safeMessage(meta.agent, 80) : void 0;
|
|
7902
8032
|
const sanitizedMcpServer = meta?.mcpServer ? safeMessage(meta.mcpServer, 40) : void 0;
|
|
@@ -8284,7 +8414,12 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
8284
8414
|
calledFromDaemon: options?.calledFromDaemon
|
|
8285
8415
|
});
|
|
8286
8416
|
}
|
|
8287
|
-
const mayDowngrade =
|
|
8417
|
+
const mayDowngrade = mayDowngradeHardBlock({
|
|
8418
|
+
daemonUp,
|
|
8419
|
+
isTestEnv: isTestEnv2,
|
|
8420
|
+
humanApproverReachable,
|
|
8421
|
+
overridable: policyResult.overridable
|
|
8422
|
+
});
|
|
8288
8423
|
const hardBlock = () => {
|
|
8289
8424
|
if (!isManual)
|
|
8290
8425
|
appendLocalAudit(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"node9": "./dist/cli.js"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
19
|
-
"node": ">=
|
|
19
|
+
"node": ">=22"
|
|
20
20
|
},
|
|
21
21
|
"workspaces": [
|
|
22
22
|
"packages/*"
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"mvdan-sh"
|
|
101
101
|
],
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@anthropic-ai/sdk": "^0.
|
|
103
|
+
"@anthropic-ai/sdk": "^0.125.0",
|
|
104
104
|
"@octokit/rest": "^22.0.1",
|
|
105
105
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
106
106
|
"@semantic-release/git": "^10.0.1",
|
|
@@ -123,7 +123,8 @@
|
|
|
123
123
|
},
|
|
124
124
|
"overrides": {
|
|
125
125
|
"undici": "^7.24.0",
|
|
126
|
-
"ws": "^8.21.0"
|
|
126
|
+
"ws": "^8.21.0",
|
|
127
|
+
"esbuild": "^0.28.1"
|
|
127
128
|
},
|
|
128
129
|
"publishConfig": {
|
|
129
130
|
"access": "public"
|