@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.js
CHANGED
|
@@ -5,8 +5,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
|
|
8
|
+
var __esm = (fn, res, err) => function __init() {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
10
15
|
};
|
|
11
16
|
var __export = (target, all) => {
|
|
12
17
|
for (var name in all)
|
|
@@ -407,8 +412,8 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
407
412
|
}).strict();
|
|
408
413
|
function formatIssues(issues) {
|
|
409
414
|
const lines = issues.map((issue) => {
|
|
410
|
-
const
|
|
411
|
-
return ` \u2022 ${
|
|
415
|
+
const path16 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
|
|
416
|
+
return ` \u2022 ${path16}: ${issue.message}`;
|
|
412
417
|
});
|
|
413
418
|
return `Invalid config:
|
|
414
419
|
${lines.join("\n")}`;
|
|
@@ -421,9 +426,9 @@ function prunePaths(root, paths) {
|
|
|
421
426
|
const yi = y[y.length - 1];
|
|
422
427
|
return typeof xi === "number" && typeof yi === "number" ? yi - xi : 0;
|
|
423
428
|
});
|
|
424
|
-
for (const
|
|
429
|
+
for (const path16 of ordered) {
|
|
425
430
|
let cur = root;
|
|
426
|
-
for (const key of
|
|
431
|
+
for (const key of path16.slice(0, -1)) {
|
|
427
432
|
if (cur === null || typeof cur !== "object") {
|
|
428
433
|
cur = void 0;
|
|
429
434
|
break;
|
|
@@ -431,7 +436,7 @@ function prunePaths(root, paths) {
|
|
|
431
436
|
cur = cur[key];
|
|
432
437
|
}
|
|
433
438
|
if (cur === null || typeof cur !== "object") continue;
|
|
434
|
-
const last =
|
|
439
|
+
const last = path16[path16.length - 1];
|
|
435
440
|
if (Array.isArray(cur)) {
|
|
436
441
|
const i = Number(last);
|
|
437
442
|
if (Number.isInteger(i) && i >= 0 && i < cur.length) {
|
|
@@ -467,7 +472,7 @@ function sanitizeConfig(raw) {
|
|
|
467
472
|
return issue.keys.map((k) => [...at, k]);
|
|
468
473
|
}
|
|
469
474
|
return [at.slice(0, -level || void 0)];
|
|
470
|
-
}).filter((
|
|
475
|
+
}).filter((path16) => path16.length > 0);
|
|
471
476
|
if (paths.length === 0 || !prunePaths(working, paths)) break;
|
|
472
477
|
}
|
|
473
478
|
if (ConfigFileSchema.safeParse(working).success) break;
|
|
@@ -1379,9 +1384,9 @@ function matchesPattern(text, patterns) {
|
|
|
1379
1384
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1380
1385
|
}
|
|
1381
1386
|
var FORBIDDEN_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1382
|
-
function getNestedValue(obj,
|
|
1387
|
+
function getNestedValue(obj, path16) {
|
|
1383
1388
|
if (!obj || typeof obj !== "object") return null;
|
|
1384
|
-
const segments2 =
|
|
1389
|
+
const segments2 = path16.split(".");
|
|
1385
1390
|
for (const seg of segments2) {
|
|
1386
1391
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
1387
1392
|
}
|
|
@@ -4046,9 +4051,9 @@ function bareToolName(toolName) {
|
|
|
4046
4051
|
const parts = toolName.split("__");
|
|
4047
4052
|
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
4048
4053
|
}
|
|
4049
|
-
function valuesAt(args,
|
|
4054
|
+
function valuesAt(args, path16) {
|
|
4050
4055
|
let cursors = [args];
|
|
4051
|
-
for (const rawSegment of
|
|
4056
|
+
for (const rawSegment of path16.split(".")) {
|
|
4052
4057
|
const isArray = rawSegment.endsWith("[]");
|
|
4053
4058
|
const key = isArray ? rawSegment.slice(0, -2) : rawSegment;
|
|
4054
4059
|
const next = [];
|
|
@@ -4080,15 +4085,15 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
4080
4085
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
4081
4086
|
if (!paths) return null;
|
|
4082
4087
|
const exempt = new Set((opts.ssrfAllow ?? []).map((a) => classifySsrf(a)?.normalized ?? a));
|
|
4083
|
-
for (const
|
|
4084
|
-
for (const value of valuesAt(args,
|
|
4088
|
+
for (const path16 of paths) {
|
|
4089
|
+
for (const value of valuesAt(args, path16)) {
|
|
4085
4090
|
const host = hostOf(value);
|
|
4086
4091
|
if (!host) continue;
|
|
4087
4092
|
const m = classifySsrf(host);
|
|
4088
4093
|
if (!m) continue;
|
|
4089
4094
|
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
4090
4095
|
if (m.overridable && m.normalized && exempt.has(m.normalized)) continue;
|
|
4091
|
-
return { ...m, argPath:
|
|
4096
|
+
return { ...m, argPath: path16, host, reason: ssrfReason(m, host) };
|
|
4092
4097
|
}
|
|
4093
4098
|
}
|
|
4094
4099
|
return null;
|
|
@@ -4211,7 +4216,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4211
4216
|
reason: dest.reason,
|
|
4212
4217
|
ruleName: `ssrf:${dest.tier}:${toolName}:${dest.host}`,
|
|
4213
4218
|
ruleDescription: dest.reason,
|
|
4214
|
-
tier: 3
|
|
4219
|
+
tier: 3,
|
|
4220
|
+
overridable: dest.overridable
|
|
4215
4221
|
};
|
|
4216
4222
|
}
|
|
4217
4223
|
}
|
|
@@ -4360,7 +4366,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4360
4366
|
reason: ssrf.reason,
|
|
4361
4367
|
ruleName: `ssrf:${ssrf.tier}:${ssrf.binary}:${ssrf.host}`,
|
|
4362
4368
|
ruleDescription: ssrf.reason,
|
|
4363
|
-
tier: 3
|
|
4369
|
+
tier: 3,
|
|
4370
|
+
overridable: ssrf.overridable
|
|
4364
4371
|
};
|
|
4365
4372
|
}
|
|
4366
4373
|
}
|
|
@@ -5866,6 +5873,45 @@ function isTrustedHost(host) {
|
|
|
5866
5873
|
);
|
|
5867
5874
|
}
|
|
5868
5875
|
|
|
5876
|
+
// src/auth/api-url.ts
|
|
5877
|
+
var DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
5878
|
+
var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]", "0.0.0.0"]);
|
|
5879
|
+
var HOST_ALLOW_ENV = "NODE9_API_HOST_ALLOW";
|
|
5880
|
+
function defaultHostSuffixes() {
|
|
5881
|
+
const host = new URL(DEFAULT_API_URL).hostname.toLowerCase();
|
|
5882
|
+
const parent = host.split(".").slice(1).join(".");
|
|
5883
|
+
return parent.includes(".") ? [host, parent] : [host];
|
|
5884
|
+
}
|
|
5885
|
+
function allowedSuffixes() {
|
|
5886
|
+
const extra = (process.env[HOST_ALLOW_ENV] ?? "").split(",").map(
|
|
5887
|
+
(s) => s.trim().toLowerCase().replace(/^\*?\./, "")
|
|
5888
|
+
).filter(Boolean);
|
|
5889
|
+
return [...defaultHostSuffixes(), ...extra];
|
|
5890
|
+
}
|
|
5891
|
+
function validateApiUrl(raw) {
|
|
5892
|
+
if (typeof raw !== "string" || raw.length === 0 || raw.length > 2048) return null;
|
|
5893
|
+
let u;
|
|
5894
|
+
try {
|
|
5895
|
+
u = new URL(raw);
|
|
5896
|
+
} catch {
|
|
5897
|
+
return null;
|
|
5898
|
+
}
|
|
5899
|
+
if (u.username || u.password) return null;
|
|
5900
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
|
|
5901
|
+
const host = u.hostname.toLowerCase();
|
|
5902
|
+
if (LOOPBACK.has(host)) return u;
|
|
5903
|
+
if (u.protocol !== "https:") return null;
|
|
5904
|
+
const suffixes = allowedSuffixes();
|
|
5905
|
+
const ok = suffixes.some((s) => host === s || host.endsWith("." + s));
|
|
5906
|
+
return ok ? u : null;
|
|
5907
|
+
}
|
|
5908
|
+
function safeApiUrl(raw, onReject) {
|
|
5909
|
+
const ok = validateApiUrl(raw);
|
|
5910
|
+
if (ok) return typeof raw === "string" ? raw : ok.toString();
|
|
5911
|
+
onReject?.(raw);
|
|
5912
|
+
return DEFAULT_API_URL;
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5869
5915
|
// src/config/index.ts
|
|
5870
5916
|
function sanitizeSsrfAllow(entries, source) {
|
|
5871
5917
|
const kept = [];
|
|
@@ -6154,12 +6200,27 @@ var ADVISORY_SMART_RULES = [
|
|
|
6154
6200
|
}
|
|
6155
6201
|
];
|
|
6156
6202
|
var cachedConfig = null;
|
|
6203
|
+
var rejectedApiUrlsSeen = /* @__PURE__ */ new Set();
|
|
6204
|
+
function noteRejectedApiUrl(raw) {
|
|
6205
|
+
const key = String(raw).slice(0, 200);
|
|
6206
|
+
if (rejectedApiUrlsSeen.has(key)) return;
|
|
6207
|
+
rejectedApiUrlsSeen.add(key);
|
|
6208
|
+
try {
|
|
6209
|
+
import_fs4.default.appendFileSync(
|
|
6210
|
+
import_path4.default.join(import_os4.default.homedir(), ".node9", "hook-debug.log"),
|
|
6211
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] REFUSED apiUrl, using the default instead: ${String(raw).slice(0, 200).replace(/[\r\n]+/g, " ")}
|
|
6212
|
+
`
|
|
6213
|
+
);
|
|
6214
|
+
} catch {
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6157
6217
|
function getCredentials() {
|
|
6158
|
-
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
6159
6218
|
if (process.env.NODE9_API_KEY) {
|
|
6160
6219
|
return {
|
|
6161
6220
|
apiKey: process.env.NODE9_API_KEY,
|
|
6162
|
-
|
|
6221
|
+
// NODE9_API_URL is env, and a hook inherits the agent's env, so it is
|
|
6222
|
+
// no more trusted than the file below.
|
|
6223
|
+
apiUrl: safeApiUrl(process.env.NODE9_API_URL || DEFAULT_API_URL, noteRejectedApiUrl)
|
|
6163
6224
|
};
|
|
6164
6225
|
}
|
|
6165
6226
|
try {
|
|
@@ -6171,14 +6232,14 @@ function getCredentials() {
|
|
|
6171
6232
|
if (profile?.apiKey) {
|
|
6172
6233
|
return {
|
|
6173
6234
|
apiKey: profile.apiKey,
|
|
6174
|
-
apiUrl: profile.apiUrl || DEFAULT_API_URL,
|
|
6235
|
+
apiUrl: safeApiUrl(profile.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6175
6236
|
localOnly: profile.localOnly === true || profileName !== "default"
|
|
6176
6237
|
};
|
|
6177
6238
|
}
|
|
6178
6239
|
if (creds.apiKey) {
|
|
6179
6240
|
return {
|
|
6180
6241
|
apiKey: creds.apiKey,
|
|
6181
|
-
apiUrl: creds.apiUrl || DEFAULT_API_URL,
|
|
6242
|
+
apiUrl: safeApiUrl(creds.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6182
6243
|
localOnly: creds.localOnly === true
|
|
6183
6244
|
};
|
|
6184
6245
|
}
|
|
@@ -6611,13 +6672,13 @@ function getConfig(cwd) {
|
|
|
6611
6672
|
}
|
|
6612
6673
|
if (Array.isArray(mc.jailPaths)) {
|
|
6613
6674
|
for (const jp of mc.jailPaths) {
|
|
6614
|
-
const
|
|
6615
|
-
if (!
|
|
6675
|
+
const path16 = typeof jp?.path === "string" ? jp.path.trim() : "";
|
|
6676
|
+
if (!path16) continue;
|
|
6616
6677
|
const verdict = jp?.verdict === "review" ? "review" : "block";
|
|
6617
|
-
for (const r of pathRules(
|
|
6678
|
+
for (const r of pathRules(path16, verdict, "org-managed jail")) {
|
|
6618
6679
|
mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
|
|
6619
6680
|
}
|
|
6620
|
-
mergedPolicy.managedJailPaths.push({ path:
|
|
6681
|
+
mergedPolicy.managedJailPaths.push({ path: path16, verdict });
|
|
6621
6682
|
}
|
|
6622
6683
|
}
|
|
6623
6684
|
if (Array.isArray(mc.trustedHosts)) {
|
|
@@ -6954,11 +7015,41 @@ function isIgnoredTool2(toolName) {
|
|
|
6954
7015
|
}
|
|
6955
7016
|
|
|
6956
7017
|
// src/auth/state.ts
|
|
7018
|
+
var import_fs8 = __toESM(require("fs"));
|
|
7019
|
+
var import_path8 = __toESM(require("path"));
|
|
7020
|
+
var import_os6 = __toESM(require("os"));
|
|
7021
|
+
|
|
7022
|
+
// src/utils/atomic-write.ts
|
|
6957
7023
|
var import_fs7 = __toESM(require("fs"));
|
|
6958
7024
|
var import_path7 = __toESM(require("path"));
|
|
6959
|
-
var
|
|
6960
|
-
|
|
6961
|
-
|
|
7025
|
+
var import_crypto5 = require("crypto");
|
|
7026
|
+
function atomicWriteSync(filePath, data, options) {
|
|
7027
|
+
const dir = import_path7.default.dirname(filePath);
|
|
7028
|
+
if (!import_fs7.default.existsSync(dir)) import_fs7.default.mkdirSync(dir, { recursive: true });
|
|
7029
|
+
const tmpPath = `${filePath}.${(0, import_crypto5.randomUUID)()}.tmp`;
|
|
7030
|
+
try {
|
|
7031
|
+
import_fs7.default.writeFileSync(tmpPath, data, options);
|
|
7032
|
+
} catch (err) {
|
|
7033
|
+
try {
|
|
7034
|
+
import_fs7.default.unlinkSync(tmpPath);
|
|
7035
|
+
} catch {
|
|
7036
|
+
}
|
|
7037
|
+
throw err;
|
|
7038
|
+
}
|
|
7039
|
+
try {
|
|
7040
|
+
import_fs7.default.renameSync(tmpPath, filePath);
|
|
7041
|
+
} catch (err) {
|
|
7042
|
+
try {
|
|
7043
|
+
import_fs7.default.unlinkSync(tmpPath);
|
|
7044
|
+
} catch {
|
|
7045
|
+
}
|
|
7046
|
+
throw err;
|
|
7047
|
+
}
|
|
7048
|
+
}
|
|
7049
|
+
|
|
7050
|
+
// src/auth/state.ts
|
|
7051
|
+
var PAUSED_FILE = import_path8.default.join(import_os6.default.homedir(), ".node9", "PAUSED");
|
|
7052
|
+
var TRUST_FILE = import_path8.default.join(import_os6.default.homedir(), ".node9", "trust.json");
|
|
6962
7053
|
function extractCommandPattern(toolName, args) {
|
|
6963
7054
|
const lower = toolName.toLowerCase();
|
|
6964
7055
|
if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
|
|
@@ -6970,11 +7061,11 @@ function extractCommandPattern(toolName, args) {
|
|
|
6970
7061
|
}
|
|
6971
7062
|
function checkPause() {
|
|
6972
7063
|
try {
|
|
6973
|
-
if (!
|
|
6974
|
-
const state = JSON.parse(
|
|
7064
|
+
if (!import_fs8.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
7065
|
+
const state = JSON.parse(import_fs8.default.readFileSync(PAUSED_FILE, "utf-8"));
|
|
6975
7066
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
6976
7067
|
try {
|
|
6977
|
-
|
|
7068
|
+
import_fs8.default.unlinkSync(PAUSED_FILE);
|
|
6978
7069
|
} catch {
|
|
6979
7070
|
}
|
|
6980
7071
|
return { paused: false };
|
|
@@ -6984,21 +7075,14 @@ function checkPause() {
|
|
|
6984
7075
|
return { paused: false };
|
|
6985
7076
|
}
|
|
6986
7077
|
}
|
|
6987
|
-
function atomicWriteSync(filePath, data, options) {
|
|
6988
|
-
const dir = import_path7.default.dirname(filePath);
|
|
6989
|
-
if (!import_fs7.default.existsSync(dir)) import_fs7.default.mkdirSync(dir, { recursive: true });
|
|
6990
|
-
const tmpPath = `${filePath}.${import_os6.default.hostname()}.${process.pid}.tmp`;
|
|
6991
|
-
import_fs7.default.writeFileSync(tmpPath, data, options);
|
|
6992
|
-
import_fs7.default.renameSync(tmpPath, filePath);
|
|
6993
|
-
}
|
|
6994
7078
|
function getActiveTrustSession(toolName, args) {
|
|
6995
7079
|
try {
|
|
6996
|
-
if (!
|
|
6997
|
-
const trust = JSON.parse(
|
|
7080
|
+
if (!import_fs8.default.existsSync(TRUST_FILE)) return false;
|
|
7081
|
+
const trust = JSON.parse(import_fs8.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
6998
7082
|
const now = Date.now();
|
|
6999
7083
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
7000
7084
|
if (active.length !== trust.entries.length) {
|
|
7001
|
-
|
|
7085
|
+
atomicWriteSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
7002
7086
|
}
|
|
7003
7087
|
return active.some((e) => {
|
|
7004
7088
|
if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
|
|
@@ -7017,8 +7101,8 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
7017
7101
|
try {
|
|
7018
7102
|
let trust = { entries: [] };
|
|
7019
7103
|
try {
|
|
7020
|
-
if (
|
|
7021
|
-
trust = JSON.parse(
|
|
7104
|
+
if (import_fs8.default.existsSync(TRUST_FILE)) {
|
|
7105
|
+
trust = JSON.parse(import_fs8.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
7022
7106
|
}
|
|
7023
7107
|
} catch {
|
|
7024
7108
|
}
|
|
@@ -7040,9 +7124,9 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
7040
7124
|
}
|
|
7041
7125
|
function getPersistentDecision(toolName) {
|
|
7042
7126
|
try {
|
|
7043
|
-
const file =
|
|
7044
|
-
if (!
|
|
7045
|
-
const decisions = JSON.parse(
|
|
7127
|
+
const file = import_path8.default.join(import_os6.default.homedir(), ".node9", "decisions.json");
|
|
7128
|
+
if (!import_fs8.default.existsSync(file)) return null;
|
|
7129
|
+
const decisions = JSON.parse(import_fs8.default.readFileSync(file, "utf-8"));
|
|
7046
7130
|
const d = decisions[toolName];
|
|
7047
7131
|
if (d === "allow" || d === "deny") return d;
|
|
7048
7132
|
} catch {
|
|
@@ -7051,11 +7135,11 @@ function getPersistentDecision(toolName) {
|
|
|
7051
7135
|
}
|
|
7052
7136
|
|
|
7053
7137
|
// src/auth/daemon.ts
|
|
7054
|
-
var
|
|
7138
|
+
var import_fs9 = __toESM(require("fs"));
|
|
7055
7139
|
var import_net = __toESM(require("net"));
|
|
7056
|
-
var
|
|
7140
|
+
var import_path9 = __toESM(require("path"));
|
|
7057
7141
|
var import_os7 = __toESM(require("os"));
|
|
7058
|
-
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
7142
|
+
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path9.default.join(import_os7.default.tmpdir(), "node9-activity.sock");
|
|
7059
7143
|
function notifyActivitySocket(data) {
|
|
7060
7144
|
return new Promise((resolve) => {
|
|
7061
7145
|
try {
|
|
@@ -7088,9 +7172,9 @@ var DAEMON_PORT = 7391;
|
|
|
7088
7172
|
var DAEMON_HOST = "127.0.0.1";
|
|
7089
7173
|
function getInternalToken() {
|
|
7090
7174
|
try {
|
|
7091
|
-
const pidFile =
|
|
7092
|
-
if (!
|
|
7093
|
-
const data = JSON.parse(
|
|
7175
|
+
const pidFile = import_path9.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
|
|
7176
|
+
if (!import_fs9.default.existsSync(pidFile)) return null;
|
|
7177
|
+
const data = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
|
|
7094
7178
|
process.kill(data.pid, 0);
|
|
7095
7179
|
return data.internalToken ?? null;
|
|
7096
7180
|
} catch {
|
|
@@ -7098,12 +7182,12 @@ function getInternalToken() {
|
|
|
7098
7182
|
}
|
|
7099
7183
|
}
|
|
7100
7184
|
function isDaemonRunning() {
|
|
7101
|
-
const pidFile =
|
|
7102
|
-
if (
|
|
7185
|
+
const pidFile = import_path9.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
|
|
7186
|
+
if (import_fs9.default.existsSync(pidFile)) {
|
|
7103
7187
|
let pid;
|
|
7104
7188
|
let port;
|
|
7105
7189
|
try {
|
|
7106
|
-
const data = JSON.parse(
|
|
7190
|
+
const data = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
|
|
7107
7191
|
pid = data.pid;
|
|
7108
7192
|
port = data.port;
|
|
7109
7193
|
} catch {
|
|
@@ -7121,7 +7205,7 @@ function isDaemonRunning() {
|
|
|
7121
7205
|
} catch (err) {
|
|
7122
7206
|
if (err instanceof Error && "code" in err && err.code === "ESRCH") {
|
|
7123
7207
|
try {
|
|
7124
|
-
|
|
7208
|
+
import_fs9.default.unlinkSync(pidFile);
|
|
7125
7209
|
} catch {
|
|
7126
7210
|
}
|
|
7127
7211
|
}
|
|
@@ -7287,14 +7371,14 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
|
7287
7371
|
}
|
|
7288
7372
|
|
|
7289
7373
|
// src/auth/orchestrator.ts
|
|
7290
|
-
var
|
|
7374
|
+
var import_crypto6 = require("crypto");
|
|
7291
7375
|
|
|
7292
7376
|
// src/ui/native.ts
|
|
7293
7377
|
var import_child_process = require("child_process");
|
|
7294
|
-
var
|
|
7378
|
+
var import_path11 = __toESM(require("path"));
|
|
7295
7379
|
|
|
7296
7380
|
// src/context-sniper.ts
|
|
7297
|
-
var
|
|
7381
|
+
var import_path10 = __toESM(require("path"));
|
|
7298
7382
|
function smartTruncate(str, maxLen = 500) {
|
|
7299
7383
|
if (str.length <= maxLen) return str;
|
|
7300
7384
|
const edge = Math.floor(maxLen / 2) - 3;
|
|
@@ -7362,7 +7446,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
7362
7446
|
intent = "EDIT";
|
|
7363
7447
|
if (obj.file_path) {
|
|
7364
7448
|
editFilePath = String(obj.file_path);
|
|
7365
|
-
editFileName =
|
|
7449
|
+
editFileName = import_path10.default.basename(editFilePath);
|
|
7366
7450
|
}
|
|
7367
7451
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
7368
7452
|
contextSnippet = result.snippet;
|
|
@@ -7426,7 +7510,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
7426
7510
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7427
7511
|
const obj = parsed;
|
|
7428
7512
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
7429
|
-
const file = obj.file_path ?
|
|
7513
|
+
const file = obj.file_path ? import_path11.default.basename(String(obj.file_path)) : "file";
|
|
7430
7514
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
7431
7515
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
7432
7516
|
return {
|
|
@@ -7624,22 +7708,22 @@ end run`;
|
|
|
7624
7708
|
}
|
|
7625
7709
|
|
|
7626
7710
|
// src/canary/registry.ts
|
|
7627
|
-
var
|
|
7711
|
+
var import_fs11 = __toESM(require("fs"));
|
|
7628
7712
|
var import_os9 = __toESM(require("os"));
|
|
7629
|
-
var
|
|
7713
|
+
var import_path13 = __toESM(require("path"));
|
|
7630
7714
|
|
|
7631
7715
|
// src/shields/jail.ts
|
|
7632
|
-
var
|
|
7716
|
+
var import_fs10 = __toESM(require("fs"));
|
|
7633
7717
|
var import_os8 = __toESM(require("os"));
|
|
7634
|
-
var
|
|
7718
|
+
var import_path12 = __toESM(require("path"));
|
|
7635
7719
|
var USER_JAIL_SHIELD = "user-jail";
|
|
7636
7720
|
function jailStorePath() {
|
|
7637
|
-
return
|
|
7721
|
+
return import_path12.default.join(import_os8.default.homedir(), ".node9", "jail-paths.json");
|
|
7638
7722
|
}
|
|
7639
7723
|
function readJailPaths() {
|
|
7640
7724
|
let text;
|
|
7641
7725
|
try {
|
|
7642
|
-
text =
|
|
7726
|
+
text = import_fs10.default.readFileSync(jailStorePath(), "utf8");
|
|
7643
7727
|
} catch (err) {
|
|
7644
7728
|
if (err.code === "ENOENT") return [];
|
|
7645
7729
|
throw err;
|
|
@@ -7668,7 +7752,7 @@ function findJailedPathIn(candidate, paths) {
|
|
|
7668
7752
|
|
|
7669
7753
|
// src/canary/registry.ts
|
|
7670
7754
|
function canaryStorePath() {
|
|
7671
|
-
return
|
|
7755
|
+
return import_path13.default.join(import_os9.default.homedir(), ".node9", "canaries.json");
|
|
7672
7756
|
}
|
|
7673
7757
|
function isRecord(x) {
|
|
7674
7758
|
if (!x || typeof x !== "object") return false;
|
|
@@ -7679,7 +7763,7 @@ function loadCanaries(opts) {
|
|
|
7679
7763
|
const p = canaryStorePath();
|
|
7680
7764
|
let raw;
|
|
7681
7765
|
try {
|
|
7682
|
-
raw =
|
|
7766
|
+
raw = import_fs11.default.readFileSync(p, "utf-8");
|
|
7683
7767
|
} catch (e) {
|
|
7684
7768
|
if (e.code === "ENOENT") return [];
|
|
7685
7769
|
throw e;
|
|
@@ -7705,10 +7789,51 @@ function canaryValues() {
|
|
|
7705
7789
|
init_audit();
|
|
7706
7790
|
|
|
7707
7791
|
// src/auth/cloud.ts
|
|
7708
|
-
var
|
|
7792
|
+
var import_fs13 = __toESM(require("fs"));
|
|
7709
7793
|
var import_os10 = __toESM(require("os"));
|
|
7710
|
-
var
|
|
7794
|
+
var import_path14 = __toESM(require("path"));
|
|
7711
7795
|
init_audit();
|
|
7796
|
+
|
|
7797
|
+
// src/utils/read-capped.ts
|
|
7798
|
+
var import_fs12 = __toESM(require("fs"));
|
|
7799
|
+
function readCapped(file, maxBytes) {
|
|
7800
|
+
if (maxBytes < 0) return null;
|
|
7801
|
+
let fd;
|
|
7802
|
+
try {
|
|
7803
|
+
fd = import_fs12.default.openSync(file, "r");
|
|
7804
|
+
} catch {
|
|
7805
|
+
return null;
|
|
7806
|
+
}
|
|
7807
|
+
try {
|
|
7808
|
+
const st = import_fs12.default.fstatSync(fd);
|
|
7809
|
+
if (!st.isFile()) return null;
|
|
7810
|
+
const want = Math.min(st.size, maxBytes + 1);
|
|
7811
|
+
const buf = Buffer.alloc(want);
|
|
7812
|
+
let read = 0;
|
|
7813
|
+
while (read < want) {
|
|
7814
|
+
const n = import_fs12.default.readSync(fd, buf, read, want - read, read);
|
|
7815
|
+
if (n <= 0) break;
|
|
7816
|
+
read += n;
|
|
7817
|
+
}
|
|
7818
|
+
return {
|
|
7819
|
+
bytes: buf.subarray(0, Math.min(read, maxBytes)),
|
|
7820
|
+
truncated: read > maxBytes
|
|
7821
|
+
};
|
|
7822
|
+
} catch {
|
|
7823
|
+
return null;
|
|
7824
|
+
} finally {
|
|
7825
|
+
try {
|
|
7826
|
+
import_fs12.default.closeSync(fd);
|
|
7827
|
+
} catch {
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
function readCappedText(file, maxBytes) {
|
|
7832
|
+
const r = readCapped(file, maxBytes);
|
|
7833
|
+
return r ? { text: r.bytes.toString("utf8"), truncated: r.truncated } : null;
|
|
7834
|
+
}
|
|
7835
|
+
|
|
7836
|
+
// src/auth/cloud.ts
|
|
7712
7837
|
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
7713
7838
|
const controller = new AbortController();
|
|
7714
7839
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
@@ -7716,10 +7841,11 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
|
|
|
7716
7841
|
let ciContext;
|
|
7717
7842
|
if (process.env.CI) {
|
|
7718
7843
|
try {
|
|
7719
|
-
const ciContextPath =
|
|
7720
|
-
const
|
|
7721
|
-
if (
|
|
7722
|
-
|
|
7844
|
+
const ciContextPath = import_path14.default.join(import_os10.default.homedir(), ".node9", "ci-context.json");
|
|
7845
|
+
const ci = readCappedText(ciContextPath, 1e4);
|
|
7846
|
+
if (!ci) throw new Error("ci-context.json unreadable");
|
|
7847
|
+
if (ci.truncated) throw new Error("ci-context.json exceeds 10 KB");
|
|
7848
|
+
const raw = ci.text;
|
|
7723
7849
|
const parsed = JSON.parse(raw);
|
|
7724
7850
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
7725
7851
|
throw new Error("ci-context.json is not a plain object");
|
|
@@ -7818,14 +7944,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
7818
7944
|
});
|
|
7819
7945
|
clearTimeout(timer);
|
|
7820
7946
|
if (!res.ok) {
|
|
7821
|
-
|
|
7947
|
+
import_fs13.default.appendFileSync(
|
|
7822
7948
|
HOOK_DEBUG_LOG,
|
|
7823
7949
|
`[resolve-cloud] PATCH ${safeMessage(resolveUrl, 200)} \u2192 HTTP ${res.status}
|
|
7824
7950
|
`
|
|
7825
7951
|
);
|
|
7826
7952
|
}
|
|
7827
7953
|
} catch (err) {
|
|
7828
|
-
|
|
7954
|
+
import_fs13.default.appendFileSync(
|
|
7829
7955
|
HOOK_DEBUG_LOG,
|
|
7830
7956
|
`[resolve-cloud] PATCH failed for ${safeMessage(requestId, 64)}: ${safeMessage(err)}
|
|
7831
7957
|
`
|
|
@@ -7834,16 +7960,16 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
7834
7960
|
}
|
|
7835
7961
|
|
|
7836
7962
|
// src/loop-detector.ts
|
|
7837
|
-
var
|
|
7838
|
-
var
|
|
7963
|
+
var import_fs14 = __toESM(require("fs"));
|
|
7964
|
+
var import_path15 = __toESM(require("path"));
|
|
7839
7965
|
var import_os11 = __toESM(require("os"));
|
|
7840
7966
|
function loopStateFile() {
|
|
7841
|
-
return
|
|
7967
|
+
return import_path15.default.join(import_os11.default.homedir(), ".node9", "loop-state.json");
|
|
7842
7968
|
}
|
|
7843
7969
|
function readState() {
|
|
7844
7970
|
try {
|
|
7845
|
-
if (!
|
|
7846
|
-
const raw =
|
|
7971
|
+
if (!import_fs14.default.existsSync(loopStateFile())) return [];
|
|
7972
|
+
const raw = import_fs14.default.readFileSync(loopStateFile(), "utf-8");
|
|
7847
7973
|
const parsed = JSON.parse(raw);
|
|
7848
7974
|
if (!Array.isArray(parsed)) return [];
|
|
7849
7975
|
return parsed;
|
|
@@ -7852,11 +7978,11 @@ function readState() {
|
|
|
7852
7978
|
}
|
|
7853
7979
|
}
|
|
7854
7980
|
function writeState(records) {
|
|
7855
|
-
const dir =
|
|
7856
|
-
if (!
|
|
7981
|
+
const dir = import_path15.default.dirname(loopStateFile());
|
|
7982
|
+
if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
7857
7983
|
const tmpPath = `${loopStateFile()}.${import_os11.default.hostname()}.${process.pid}.tmp`;
|
|
7858
|
-
|
|
7859
|
-
|
|
7984
|
+
import_fs14.default.writeFileSync(tmpPath, JSON.stringify(records));
|
|
7985
|
+
import_fs14.default.renameSync(tmpPath, loopStateFile());
|
|
7860
7986
|
}
|
|
7861
7987
|
function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
7862
7988
|
try {
|
|
@@ -7924,9 +8050,13 @@ async function hasReachableHumanApprover(opts) {
|
|
|
7924
8050
|
if (nativeReachable) return true;
|
|
7925
8051
|
return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
|
|
7926
8052
|
}
|
|
8053
|
+
function mayDowngradeHardBlock(opts) {
|
|
8054
|
+
if (opts.overridable === false) return false;
|
|
8055
|
+
return opts.daemonUp && !opts.isTestEnv && opts.humanApproverReachable;
|
|
8056
|
+
}
|
|
7927
8057
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
7928
8058
|
if (!options?.calledFromDaemon) {
|
|
7929
|
-
const actId = (0,
|
|
8059
|
+
const actId = (0, import_crypto6.randomUUID)();
|
|
7930
8060
|
const actTs = Date.now();
|
|
7931
8061
|
const sanitizedAgent = meta?.agent ? safeMessage(meta.agent, 80) : void 0;
|
|
7932
8062
|
const sanitizedMcpServer = meta?.mcpServer ? safeMessage(meta.mcpServer, 40) : void 0;
|
|
@@ -8314,7 +8444,12 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
8314
8444
|
calledFromDaemon: options?.calledFromDaemon
|
|
8315
8445
|
});
|
|
8316
8446
|
}
|
|
8317
|
-
const mayDowngrade =
|
|
8447
|
+
const mayDowngrade = mayDowngradeHardBlock({
|
|
8448
|
+
daemonUp,
|
|
8449
|
+
isTestEnv: isTestEnv2,
|
|
8450
|
+
humanApproverReachable,
|
|
8451
|
+
overridable: policyResult.overridable
|
|
8452
|
+
});
|
|
8318
8453
|
const hardBlock = () => {
|
|
8319
8454
|
if (!isManual)
|
|
8320
8455
|
appendLocalAudit(
|