@node9/proxy 2.14.1 → 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 +1548 -1386
- package/dist/cli.mjs +1479 -1319
- package/dist/dashboard.mjs +91 -11
- package/dist/index.js +230 -92
- package/dist/index.mjs +230 -92
- 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
|
}
|
|
@@ -4484,6 +4486,15 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4484
4486
|
function isIgnoredTool(toolName, config) {
|
|
4485
4487
|
return matchesPattern(toolName, config.policy.ignoredTools);
|
|
4486
4488
|
}
|
|
4489
|
+
var TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
4490
|
+
function stripTerminalEscapes(s) {
|
|
4491
|
+
return s.replace(TERMINAL_ESCAPE_RE, "");
|
|
4492
|
+
}
|
|
4493
|
+
function safeMessage(value, max = 300) {
|
|
4494
|
+
const raw = typeof value === "string" ? value : value instanceof Error ? value.message : String(value ?? "");
|
|
4495
|
+
const s = stripTerminalEscapes(raw).replace(/\s+/g, " ").trim();
|
|
4496
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
4497
|
+
}
|
|
4487
4498
|
var aws_default = {
|
|
4488
4499
|
name: "aws",
|
|
4489
4500
|
description: "Protects AWS infrastructure from destructive AI operations",
|
|
@@ -5857,6 +5868,45 @@ function isTrustedHost(host) {
|
|
|
5857
5868
|
);
|
|
5858
5869
|
}
|
|
5859
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
|
+
|
|
5860
5910
|
// src/config/index.ts
|
|
5861
5911
|
function sanitizeSsrfAllow(entries, source) {
|
|
5862
5912
|
const kept = [];
|
|
@@ -6145,12 +6195,27 @@ var ADVISORY_SMART_RULES = [
|
|
|
6145
6195
|
}
|
|
6146
6196
|
];
|
|
6147
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
|
+
}
|
|
6148
6212
|
function getCredentials() {
|
|
6149
|
-
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
6150
6213
|
if (process.env.NODE9_API_KEY) {
|
|
6151
6214
|
return {
|
|
6152
6215
|
apiKey: process.env.NODE9_API_KEY,
|
|
6153
|
-
|
|
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)
|
|
6154
6219
|
};
|
|
6155
6220
|
}
|
|
6156
6221
|
try {
|
|
@@ -6162,14 +6227,14 @@ function getCredentials() {
|
|
|
6162
6227
|
if (profile?.apiKey) {
|
|
6163
6228
|
return {
|
|
6164
6229
|
apiKey: profile.apiKey,
|
|
6165
|
-
apiUrl: profile.apiUrl || DEFAULT_API_URL,
|
|
6230
|
+
apiUrl: safeApiUrl(profile.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6166
6231
|
localOnly: profile.localOnly === true || profileName !== "default"
|
|
6167
6232
|
};
|
|
6168
6233
|
}
|
|
6169
6234
|
if (creds.apiKey) {
|
|
6170
6235
|
return {
|
|
6171
6236
|
apiKey: creds.apiKey,
|
|
6172
|
-
apiUrl: creds.apiUrl || DEFAULT_API_URL,
|
|
6237
|
+
apiUrl: safeApiUrl(creds.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6173
6238
|
localOnly: creds.localOnly === true
|
|
6174
6239
|
};
|
|
6175
6240
|
}
|
|
@@ -6602,13 +6667,13 @@ function getConfig(cwd) {
|
|
|
6602
6667
|
}
|
|
6603
6668
|
if (Array.isArray(mc.jailPaths)) {
|
|
6604
6669
|
for (const jp of mc.jailPaths) {
|
|
6605
|
-
const
|
|
6606
|
-
if (!
|
|
6670
|
+
const path16 = typeof jp?.path === "string" ? jp.path.trim() : "";
|
|
6671
|
+
if (!path16) continue;
|
|
6607
6672
|
const verdict = jp?.verdict === "review" ? "review" : "block";
|
|
6608
|
-
for (const r of pathRules(
|
|
6673
|
+
for (const r of pathRules(path16, verdict, "org-managed jail")) {
|
|
6609
6674
|
mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
|
|
6610
6675
|
}
|
|
6611
|
-
mergedPolicy.managedJailPaths.push({ path:
|
|
6676
|
+
mergedPolicy.managedJailPaths.push({ path: path16, verdict });
|
|
6612
6677
|
}
|
|
6613
6678
|
}
|
|
6614
6679
|
if (Array.isArray(mc.trustedHosts)) {
|
|
@@ -6945,11 +7010,41 @@ function isIgnoredTool2(toolName) {
|
|
|
6945
7010
|
}
|
|
6946
7011
|
|
|
6947
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
|
|
6948
7018
|
var import_fs7 = __toESM(require("fs"));
|
|
6949
7019
|
var import_path7 = __toESM(require("path"));
|
|
6950
|
-
var
|
|
6951
|
-
|
|
6952
|
-
|
|
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");
|
|
6953
7048
|
function extractCommandPattern(toolName, args) {
|
|
6954
7049
|
const lower = toolName.toLowerCase();
|
|
6955
7050
|
if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
|
|
@@ -6961,11 +7056,11 @@ function extractCommandPattern(toolName, args) {
|
|
|
6961
7056
|
}
|
|
6962
7057
|
function checkPause() {
|
|
6963
7058
|
try {
|
|
6964
|
-
if (!
|
|
6965
|
-
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"));
|
|
6966
7061
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
6967
7062
|
try {
|
|
6968
|
-
|
|
7063
|
+
import_fs8.default.unlinkSync(PAUSED_FILE);
|
|
6969
7064
|
} catch {
|
|
6970
7065
|
}
|
|
6971
7066
|
return { paused: false };
|
|
@@ -6975,21 +7070,14 @@ function checkPause() {
|
|
|
6975
7070
|
return { paused: false };
|
|
6976
7071
|
}
|
|
6977
7072
|
}
|
|
6978
|
-
function atomicWriteSync(filePath, data, options) {
|
|
6979
|
-
const dir = import_path7.default.dirname(filePath);
|
|
6980
|
-
if (!import_fs7.default.existsSync(dir)) import_fs7.default.mkdirSync(dir, { recursive: true });
|
|
6981
|
-
const tmpPath = `${filePath}.${import_os6.default.hostname()}.${process.pid}.tmp`;
|
|
6982
|
-
import_fs7.default.writeFileSync(tmpPath, data, options);
|
|
6983
|
-
import_fs7.default.renameSync(tmpPath, filePath);
|
|
6984
|
-
}
|
|
6985
7073
|
function getActiveTrustSession(toolName, args) {
|
|
6986
7074
|
try {
|
|
6987
|
-
if (!
|
|
6988
|
-
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"));
|
|
6989
7077
|
const now = Date.now();
|
|
6990
7078
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
6991
7079
|
if (active.length !== trust.entries.length) {
|
|
6992
|
-
|
|
7080
|
+
atomicWriteSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
6993
7081
|
}
|
|
6994
7082
|
return active.some((e) => {
|
|
6995
7083
|
if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
|
|
@@ -7008,8 +7096,8 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
7008
7096
|
try {
|
|
7009
7097
|
let trust = { entries: [] };
|
|
7010
7098
|
try {
|
|
7011
|
-
if (
|
|
7012
|
-
trust = JSON.parse(
|
|
7099
|
+
if (import_fs8.default.existsSync(TRUST_FILE)) {
|
|
7100
|
+
trust = JSON.parse(import_fs8.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
7013
7101
|
}
|
|
7014
7102
|
} catch {
|
|
7015
7103
|
}
|
|
@@ -7031,9 +7119,9 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
7031
7119
|
}
|
|
7032
7120
|
function getPersistentDecision(toolName) {
|
|
7033
7121
|
try {
|
|
7034
|
-
const file =
|
|
7035
|
-
if (!
|
|
7036
|
-
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"));
|
|
7037
7125
|
const d = decisions[toolName];
|
|
7038
7126
|
if (d === "allow" || d === "deny") return d;
|
|
7039
7127
|
} catch {
|
|
@@ -7042,11 +7130,11 @@ function getPersistentDecision(toolName) {
|
|
|
7042
7130
|
}
|
|
7043
7131
|
|
|
7044
7132
|
// src/auth/daemon.ts
|
|
7045
|
-
var
|
|
7133
|
+
var import_fs9 = __toESM(require("fs"));
|
|
7046
7134
|
var import_net = __toESM(require("net"));
|
|
7047
|
-
var
|
|
7135
|
+
var import_path9 = __toESM(require("path"));
|
|
7048
7136
|
var import_os7 = __toESM(require("os"));
|
|
7049
|
-
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");
|
|
7050
7138
|
function notifyActivitySocket(data) {
|
|
7051
7139
|
return new Promise((resolve) => {
|
|
7052
7140
|
try {
|
|
@@ -7079,9 +7167,9 @@ var DAEMON_PORT = 7391;
|
|
|
7079
7167
|
var DAEMON_HOST = "127.0.0.1";
|
|
7080
7168
|
function getInternalToken() {
|
|
7081
7169
|
try {
|
|
7082
|
-
const pidFile =
|
|
7083
|
-
if (!
|
|
7084
|
-
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"));
|
|
7085
7173
|
process.kill(data.pid, 0);
|
|
7086
7174
|
return data.internalToken ?? null;
|
|
7087
7175
|
} catch {
|
|
@@ -7089,12 +7177,12 @@ function getInternalToken() {
|
|
|
7089
7177
|
}
|
|
7090
7178
|
}
|
|
7091
7179
|
function isDaemonRunning() {
|
|
7092
|
-
const pidFile =
|
|
7093
|
-
if (
|
|
7180
|
+
const pidFile = import_path9.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
|
|
7181
|
+
if (import_fs9.default.existsSync(pidFile)) {
|
|
7094
7182
|
let pid;
|
|
7095
7183
|
let port;
|
|
7096
7184
|
try {
|
|
7097
|
-
const data = JSON.parse(
|
|
7185
|
+
const data = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
|
|
7098
7186
|
pid = data.pid;
|
|
7099
7187
|
port = data.port;
|
|
7100
7188
|
} catch {
|
|
@@ -7112,7 +7200,7 @@ function isDaemonRunning() {
|
|
|
7112
7200
|
} catch (err) {
|
|
7113
7201
|
if (err instanceof Error && "code" in err && err.code === "ESRCH") {
|
|
7114
7202
|
try {
|
|
7115
|
-
|
|
7203
|
+
import_fs9.default.unlinkSync(pidFile);
|
|
7116
7204
|
} catch {
|
|
7117
7205
|
}
|
|
7118
7206
|
}
|
|
@@ -7278,14 +7366,14 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
|
7278
7366
|
}
|
|
7279
7367
|
|
|
7280
7368
|
// src/auth/orchestrator.ts
|
|
7281
|
-
var
|
|
7369
|
+
var import_crypto6 = require("crypto");
|
|
7282
7370
|
|
|
7283
7371
|
// src/ui/native.ts
|
|
7284
7372
|
var import_child_process = require("child_process");
|
|
7285
|
-
var
|
|
7373
|
+
var import_path11 = __toESM(require("path"));
|
|
7286
7374
|
|
|
7287
7375
|
// src/context-sniper.ts
|
|
7288
|
-
var
|
|
7376
|
+
var import_path10 = __toESM(require("path"));
|
|
7289
7377
|
function smartTruncate(str, maxLen = 500) {
|
|
7290
7378
|
if (str.length <= maxLen) return str;
|
|
7291
7379
|
const edge = Math.floor(maxLen / 2) - 3;
|
|
@@ -7353,7 +7441,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
7353
7441
|
intent = "EDIT";
|
|
7354
7442
|
if (obj.file_path) {
|
|
7355
7443
|
editFilePath = String(obj.file_path);
|
|
7356
|
-
editFileName =
|
|
7444
|
+
editFileName = import_path10.default.basename(editFilePath);
|
|
7357
7445
|
}
|
|
7358
7446
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
7359
7447
|
contextSnippet = result.snippet;
|
|
@@ -7417,7 +7505,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
7417
7505
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7418
7506
|
const obj = parsed;
|
|
7419
7507
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
7420
|
-
const file = obj.file_path ?
|
|
7508
|
+
const file = obj.file_path ? import_path11.default.basename(String(obj.file_path)) : "file";
|
|
7421
7509
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
7422
7510
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
7423
7511
|
return {
|
|
@@ -7483,7 +7571,7 @@ function escapePango(text) {
|
|
|
7483
7571
|
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
7484
7572
|
const lines = [];
|
|
7485
7573
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
7486
|
-
const safeAgent = (agent ?? "AI Agent"
|
|
7574
|
+
const safeAgent = safeMessage(agent ?? "AI Agent", 80);
|
|
7487
7575
|
lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
|
|
7488
7576
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
7489
7577
|
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
@@ -7615,22 +7703,22 @@ end run`;
|
|
|
7615
7703
|
}
|
|
7616
7704
|
|
|
7617
7705
|
// src/canary/registry.ts
|
|
7618
|
-
var
|
|
7706
|
+
var import_fs11 = __toESM(require("fs"));
|
|
7619
7707
|
var import_os9 = __toESM(require("os"));
|
|
7620
|
-
var
|
|
7708
|
+
var import_path13 = __toESM(require("path"));
|
|
7621
7709
|
|
|
7622
7710
|
// src/shields/jail.ts
|
|
7623
|
-
var
|
|
7711
|
+
var import_fs10 = __toESM(require("fs"));
|
|
7624
7712
|
var import_os8 = __toESM(require("os"));
|
|
7625
|
-
var
|
|
7713
|
+
var import_path12 = __toESM(require("path"));
|
|
7626
7714
|
var USER_JAIL_SHIELD = "user-jail";
|
|
7627
7715
|
function jailStorePath() {
|
|
7628
|
-
return
|
|
7716
|
+
return import_path12.default.join(import_os8.default.homedir(), ".node9", "jail-paths.json");
|
|
7629
7717
|
}
|
|
7630
7718
|
function readJailPaths() {
|
|
7631
7719
|
let text;
|
|
7632
7720
|
try {
|
|
7633
|
-
text =
|
|
7721
|
+
text = import_fs10.default.readFileSync(jailStorePath(), "utf8");
|
|
7634
7722
|
} catch (err) {
|
|
7635
7723
|
if (err.code === "ENOENT") return [];
|
|
7636
7724
|
throw err;
|
|
@@ -7659,7 +7747,7 @@ function findJailedPathIn(candidate, paths) {
|
|
|
7659
7747
|
|
|
7660
7748
|
// src/canary/registry.ts
|
|
7661
7749
|
function canaryStorePath() {
|
|
7662
|
-
return
|
|
7750
|
+
return import_path13.default.join(import_os9.default.homedir(), ".node9", "canaries.json");
|
|
7663
7751
|
}
|
|
7664
7752
|
function isRecord(x) {
|
|
7665
7753
|
if (!x || typeof x !== "object") return false;
|
|
@@ -7670,7 +7758,7 @@ function loadCanaries(opts) {
|
|
|
7670
7758
|
const p = canaryStorePath();
|
|
7671
7759
|
let raw;
|
|
7672
7760
|
try {
|
|
7673
|
-
raw =
|
|
7761
|
+
raw = import_fs11.default.readFileSync(p, "utf-8");
|
|
7674
7762
|
} catch (e) {
|
|
7675
7763
|
if (e.code === "ENOENT") return [];
|
|
7676
7764
|
throw e;
|
|
@@ -7696,10 +7784,51 @@ function canaryValues() {
|
|
|
7696
7784
|
init_audit();
|
|
7697
7785
|
|
|
7698
7786
|
// src/auth/cloud.ts
|
|
7699
|
-
var
|
|
7787
|
+
var import_fs13 = __toESM(require("fs"));
|
|
7700
7788
|
var import_os10 = __toESM(require("os"));
|
|
7701
|
-
var
|
|
7789
|
+
var import_path14 = __toESM(require("path"));
|
|
7702
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
|
|
7703
7832
|
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
7704
7833
|
const controller = new AbortController();
|
|
7705
7834
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
@@ -7707,10 +7836,11 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
|
|
|
7707
7836
|
let ciContext;
|
|
7708
7837
|
if (process.env.CI) {
|
|
7709
7838
|
try {
|
|
7710
|
-
const ciContextPath =
|
|
7711
|
-
const
|
|
7712
|
-
if (
|
|
7713
|
-
|
|
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;
|
|
7714
7844
|
const parsed = JSON.parse(raw);
|
|
7715
7845
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
7716
7846
|
throw new Error("ci-context.json is not a plain object");
|
|
@@ -7809,32 +7939,32 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
7809
7939
|
});
|
|
7810
7940
|
clearTimeout(timer);
|
|
7811
7941
|
if (!res.ok) {
|
|
7812
|
-
|
|
7942
|
+
import_fs13.default.appendFileSync(
|
|
7813
7943
|
HOOK_DEBUG_LOG,
|
|
7814
|
-
`[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
|
|
7944
|
+
`[resolve-cloud] PATCH ${safeMessage(resolveUrl, 200)} \u2192 HTTP ${res.status}
|
|
7815
7945
|
`
|
|
7816
7946
|
);
|
|
7817
7947
|
}
|
|
7818
7948
|
} catch (err) {
|
|
7819
|
-
|
|
7949
|
+
import_fs13.default.appendFileSync(
|
|
7820
7950
|
HOOK_DEBUG_LOG,
|
|
7821
|
-
`[resolve-cloud] PATCH failed for ${requestId}: ${err
|
|
7951
|
+
`[resolve-cloud] PATCH failed for ${safeMessage(requestId, 64)}: ${safeMessage(err)}
|
|
7822
7952
|
`
|
|
7823
7953
|
);
|
|
7824
7954
|
}
|
|
7825
7955
|
}
|
|
7826
7956
|
|
|
7827
7957
|
// src/loop-detector.ts
|
|
7828
|
-
var
|
|
7829
|
-
var
|
|
7958
|
+
var import_fs14 = __toESM(require("fs"));
|
|
7959
|
+
var import_path15 = __toESM(require("path"));
|
|
7830
7960
|
var import_os11 = __toESM(require("os"));
|
|
7831
7961
|
function loopStateFile() {
|
|
7832
|
-
return
|
|
7962
|
+
return import_path15.default.join(import_os11.default.homedir(), ".node9", "loop-state.json");
|
|
7833
7963
|
}
|
|
7834
7964
|
function readState() {
|
|
7835
7965
|
try {
|
|
7836
|
-
if (!
|
|
7837
|
-
const raw =
|
|
7966
|
+
if (!import_fs14.default.existsSync(loopStateFile())) return [];
|
|
7967
|
+
const raw = import_fs14.default.readFileSync(loopStateFile(), "utf-8");
|
|
7838
7968
|
const parsed = JSON.parse(raw);
|
|
7839
7969
|
if (!Array.isArray(parsed)) return [];
|
|
7840
7970
|
return parsed;
|
|
@@ -7843,11 +7973,11 @@ function readState() {
|
|
|
7843
7973
|
}
|
|
7844
7974
|
}
|
|
7845
7975
|
function writeState(records) {
|
|
7846
|
-
const dir =
|
|
7847
|
-
if (!
|
|
7976
|
+
const dir = import_path15.default.dirname(loopStateFile());
|
|
7977
|
+
if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
7848
7978
|
const tmpPath = `${loopStateFile()}.${import_os11.default.hostname()}.${process.pid}.tmp`;
|
|
7849
|
-
|
|
7850
|
-
|
|
7979
|
+
import_fs14.default.writeFileSync(tmpPath, JSON.stringify(records));
|
|
7980
|
+
import_fs14.default.renameSync(tmpPath, loopStateFile());
|
|
7851
7981
|
}
|
|
7852
7982
|
function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
7853
7983
|
try {
|
|
@@ -7915,13 +8045,16 @@ async function hasReachableHumanApprover(opts) {
|
|
|
7915
8045
|
if (nativeReachable) return true;
|
|
7916
8046
|
return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
|
|
7917
8047
|
}
|
|
8048
|
+
function mayDowngradeHardBlock(opts) {
|
|
8049
|
+
if (opts.overridable === false) return false;
|
|
8050
|
+
return opts.daemonUp && !opts.isTestEnv && opts.humanApproverReachable;
|
|
8051
|
+
}
|
|
7918
8052
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
7919
8053
|
if (!options?.calledFromDaemon) {
|
|
7920
|
-
const actId = (0,
|
|
8054
|
+
const actId = (0, import_crypto6.randomUUID)();
|
|
7921
8055
|
const actTs = Date.now();
|
|
7922
|
-
const
|
|
7923
|
-
const
|
|
7924
|
-
const sanitizedMcpServer = meta?.mcpServer ? stripAnsi(meta.mcpServer).slice(0, 40) : void 0;
|
|
8056
|
+
const sanitizedAgent = meta?.agent ? safeMessage(meta.agent, 80) : void 0;
|
|
8057
|
+
const sanitizedMcpServer = meta?.mcpServer ? safeMessage(meta.mcpServer, 40) : void 0;
|
|
7925
8058
|
const socketOk = await notifyActivity({
|
|
7926
8059
|
id: actId,
|
|
7927
8060
|
ts: actTs,
|
|
@@ -8306,7 +8439,12 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
8306
8439
|
calledFromDaemon: options?.calledFromDaemon
|
|
8307
8440
|
});
|
|
8308
8441
|
}
|
|
8309
|
-
const mayDowngrade =
|
|
8442
|
+
const mayDowngrade = mayDowngradeHardBlock({
|
|
8443
|
+
daemonUp,
|
|
8444
|
+
isTestEnv: isTestEnv2,
|
|
8445
|
+
humanApproverReachable,
|
|
8446
|
+
overridable: policyResult.overridable
|
|
8447
|
+
});
|
|
8310
8448
|
const hardBlock = () => {
|
|
8311
8449
|
if (!isManual)
|
|
8312
8450
|
appendLocalAudit(
|