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