@node9/proxy 2.32.0 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +780 -452
- package/dist/cli.mjs +762 -434
- package/dist/dashboard.mjs +369 -29
- package/dist/index.js +583 -325
- package/dist/index.mjs +583 -325
- package/dist/scan-ink.mjs +202 -190
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2184,32 +2184,109 @@ function evaluateSmartConditions(args, rule) {
|
|
|
2184
2184
|
function isBashTool(tool) {
|
|
2185
2185
|
return BASH_TOOL_NAMES.has(tool.toLowerCase());
|
|
2186
2186
|
}
|
|
2187
|
-
function
|
|
2187
|
+
function inspectionLayers(policy) {
|
|
2188
|
+
return { trusted: policy.toolInspection, project: policy.projectToolInspection };
|
|
2189
|
+
}
|
|
2190
|
+
function isShellEntry(value) {
|
|
2191
|
+
return typeof value === "object" && value !== null && value.kind === "shell" && typeof value.field === "string" && Object.keys(value).length === 2;
|
|
2192
|
+
}
|
|
2193
|
+
function isInspectionLayers(input) {
|
|
2194
|
+
const trusted = input.trusted;
|
|
2195
|
+
return typeof trusted === "object" && trusted !== null && !isShellEntry(trusted);
|
|
2196
|
+
}
|
|
2197
|
+
function layersOf(input) {
|
|
2198
|
+
return isInspectionLayers(input) ? { trusted: input.trusted, project: input.project ?? {} } : { trusted: input, project: {} };
|
|
2199
|
+
}
|
|
2200
|
+
function entryOf(tool, inspection) {
|
|
2188
2201
|
const pattern = Object.keys(inspection).find((p) => matchesPattern(tool, p));
|
|
2189
2202
|
return pattern === void 0 ? void 0 : inspection[pattern];
|
|
2190
2203
|
}
|
|
2204
|
+
function inspectionEntry(tool, inspection = {}) {
|
|
2205
|
+
const { trusted, project } = layersOf(inspection);
|
|
2206
|
+
return entryOf(tool, trusted) ?? entryOf(tool, project);
|
|
2207
|
+
}
|
|
2208
|
+
function executorName(tool) {
|
|
2209
|
+
const name = tool.toLowerCase();
|
|
2210
|
+
if (!name.startsWith("mcp__")) return name;
|
|
2211
|
+
const at = name.lastIndexOf("__");
|
|
2212
|
+
return at >= "mcp__".length ? name.slice(at + 2) : name;
|
|
2213
|
+
}
|
|
2214
|
+
function entryFields(entry) {
|
|
2215
|
+
if (typeof entry === "object") return [entry.field];
|
|
2216
|
+
return entry === "command" ? ["command"] : null;
|
|
2217
|
+
}
|
|
2191
2218
|
function shellFields(tool, inspection) {
|
|
2192
2219
|
if (isBashTool(tool)) return ["command"];
|
|
2193
|
-
const
|
|
2194
|
-
|
|
2195
|
-
if (
|
|
2196
|
-
const
|
|
2197
|
-
|
|
2220
|
+
const { trusted, project } = layersOf(inspection);
|
|
2221
|
+
const trustedEntry = entryOf(tool, trusted);
|
|
2222
|
+
if (trustedEntry !== void 0) return entryFields(trustedEntry);
|
|
2223
|
+
const builtin = MCP_EXECUTORS.has(executorName(tool)) ? BUILTIN_FIELDS : null;
|
|
2224
|
+
const projectEntry = entryOf(tool, project);
|
|
2225
|
+
if (projectEntry === void 0) return builtin;
|
|
2226
|
+
const added = entryFields(projectEntry);
|
|
2227
|
+
if (!builtin) return added;
|
|
2228
|
+
if (!added) return builtin;
|
|
2229
|
+
return [.../* @__PURE__ */ new Set([...builtin, ...added])];
|
|
2198
2230
|
}
|
|
2199
2231
|
function isShellShapedTool(tool, inspection = {}) {
|
|
2200
2232
|
return shellFields(tool, inspection) !== null;
|
|
2201
2233
|
}
|
|
2234
|
+
function shellWord(word) {
|
|
2235
|
+
if (BARE_WORD.test(word)) return word;
|
|
2236
|
+
return word === "" ? "''" : `'${word.replace(/'/g, `'\\''`)}'`;
|
|
2237
|
+
}
|
|
2238
|
+
function isStringArray(value) {
|
|
2239
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
2240
|
+
}
|
|
2241
|
+
function commandLine(args, fieldPath, value, native) {
|
|
2242
|
+
const missing = { reason: "Shell command field is missing or is not a string" };
|
|
2243
|
+
const unreadable = { reason: "Shell command arguments could not be read" };
|
|
2244
|
+
if (typeof value !== "string" && (native || !Array.isArray(value))) return missing;
|
|
2245
|
+
const dot = fieldPath.lastIndexOf(".");
|
|
2246
|
+
const parent = dot === -1 ? args : getNestedValue(args, fieldPath.slice(0, dot));
|
|
2247
|
+
const siblings = ARGV_FIELDS.map((key) => ({
|
|
2248
|
+
key,
|
|
2249
|
+
value: parent && typeof parent === "object" ? parent[key] : void 0
|
|
2250
|
+
})).filter(({ value: value2 }) => value2 !== void 0 && value2 !== null);
|
|
2251
|
+
let argv = siblings[0];
|
|
2252
|
+
if (native) argv = siblings.find(({ value: v }) => isStringArray(v));
|
|
2253
|
+
else if (siblings.length === 2 && JSON.stringify(siblings[0].value) !== JSON.stringify(siblings[1].value))
|
|
2254
|
+
return { reason: "Shell command arguments disagree" };
|
|
2255
|
+
let command;
|
|
2256
|
+
let source = fieldPath;
|
|
2257
|
+
if (Array.isArray(value)) {
|
|
2258
|
+
if (argv || !value.length || !isStringArray(value)) return unreadable;
|
|
2259
|
+
command = value.map(shellWord).join(" ");
|
|
2260
|
+
} else if (!argv) {
|
|
2261
|
+
return { command: value, fieldPath };
|
|
2262
|
+
} else {
|
|
2263
|
+
if (!isStringArray(argv.value)) return unreadable;
|
|
2264
|
+
if (!argv.value.length) return { command: value, fieldPath };
|
|
2265
|
+
command = `${value} ${argv.value.map(shellWord).join(" ")}`;
|
|
2266
|
+
source = `${fieldPath}+${argv.key}`;
|
|
2267
|
+
}
|
|
2268
|
+
if (command.length > MAX_ARGV_CHARACTERS)
|
|
2269
|
+
return { reason: "Shell command arguments exceed the inspectable size" };
|
|
2270
|
+
return { command, fieldPath: source };
|
|
2271
|
+
}
|
|
2202
2272
|
function resolveShellInput(tool, args, inspection = {}) {
|
|
2203
2273
|
const fields = shellFields(tool, inspection);
|
|
2204
2274
|
if (!fields) return { kind: "not-shell" };
|
|
2205
2275
|
const values = fields.map((fieldPath) => ({ fieldPath, value: getNestedValue(args, fieldPath) })).filter(({ value }) => value !== void 0 && value !== null && value !== "");
|
|
2206
|
-
if (!values.length
|
|
2276
|
+
if (!values.length) {
|
|
2207
2277
|
return { kind: "uninspectable", reason: "Shell command field is missing or is not a string" };
|
|
2208
2278
|
}
|
|
2209
|
-
|
|
2279
|
+
const native = isBashTool(tool);
|
|
2280
|
+
const lines = values.map(({ fieldPath, value }) => commandLine(args, fieldPath, value, native));
|
|
2281
|
+
const failed = lines.find((line) => "reason" in line);
|
|
2282
|
+
if (failed) return { kind: "uninspectable", reason: failed.reason };
|
|
2283
|
+
const commands = lines.filter(
|
|
2284
|
+
(line) => "command" in line
|
|
2285
|
+
);
|
|
2286
|
+
if (new Set(commands.map(({ command }) => command)).size !== 1) {
|
|
2210
2287
|
return { kind: "uninspectable", reason: "Multiple shell command fields disagree" };
|
|
2211
2288
|
}
|
|
2212
|
-
return { kind: "shell",
|
|
2289
|
+
return { kind: "shell", ...commands[0] };
|
|
2213
2290
|
}
|
|
2214
2291
|
function shellRuleArgs(tool, args, ruleTool, inspection = {}) {
|
|
2215
2292
|
if (!ruleTool || !matchesPattern("bash", ruleTool) || matchesPattern(tool, ruleTool)) return args;
|
|
@@ -2950,20 +3027,25 @@ function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
|
2950
3027
|
}
|
|
2951
3028
|
return best;
|
|
2952
3029
|
}
|
|
2953
|
-
function findMnemonic(text, budget = {
|
|
2954
|
-
const words = [...text.matchAll(/[a-zA-Z]+/g)];
|
|
3030
|
+
function findMnemonic(text, budget = { hashes: 0, reasons: /* @__PURE__ */ new Set() }) {
|
|
2955
3031
|
let run = [];
|
|
3032
|
+
let runIndex = [];
|
|
2956
3033
|
let previousEnd = 0;
|
|
2957
3034
|
const rejected = /* @__PURE__ */ new Set();
|
|
2958
3035
|
const inspect = () => {
|
|
3036
|
+
if (run.length < 12) return false;
|
|
3037
|
+
const ascending = new Array(run.length).fill(0);
|
|
3038
|
+
for (let i = 1; i < run.length; i++)
|
|
3039
|
+
ascending[i] = ascending[i - 1] + (runIndex[i] > runIndex[i - 1] ? 1 : 0);
|
|
2959
3040
|
for (let start = 0; start + 12 <= run.length; start++) {
|
|
2960
3041
|
for (const count of lengths) {
|
|
2961
3042
|
if (start + count > run.length) continue;
|
|
3043
|
+
if (ascending[start + count - 1] - ascending[start] === count - 1) continue;
|
|
2962
3044
|
const candidate = run.slice(start, start + count);
|
|
2963
3045
|
const sentence2 = candidate.join(" ");
|
|
2964
3046
|
if (rejected.has(sentence2)) continue;
|
|
2965
|
-
if (++budget.
|
|
2966
|
-
budget.reasons.add("mnemonic-
|
|
3047
|
+
if (++budget.hashes > MAX_MNEMONIC_HASHES) {
|
|
3048
|
+
budget.reasons.add("mnemonic-budget");
|
|
2967
3049
|
return false;
|
|
2968
3050
|
}
|
|
2969
3051
|
if (BIP39_PUBLIC_VECTORS.has(hash(sentence2).toString("hex"))) {
|
|
@@ -2981,13 +3063,18 @@ function findMnemonic(text, budget = { candidates: 0, reasons: /* @__PURE__ */ n
|
|
|
2981
3063
|
}
|
|
2982
3064
|
return false;
|
|
2983
3065
|
};
|
|
2984
|
-
for (const word of
|
|
2985
|
-
|
|
3066
|
+
for (const word of text.matchAll(/[a-zA-Z]+/g)) {
|
|
3067
|
+
const index = indices.get(word[0]);
|
|
3068
|
+
if (index === void 0 || run.length && !/^\s+$/.test(text.slice(previousEnd, word.index))) {
|
|
2986
3069
|
if (inspect()) return true;
|
|
2987
|
-
if (budget.reasons.has("mnemonic-
|
|
3070
|
+
if (budget.reasons.has("mnemonic-budget")) return false;
|
|
2988
3071
|
run = [];
|
|
3072
|
+
runIndex = [];
|
|
3073
|
+
}
|
|
3074
|
+
if (index !== void 0) {
|
|
3075
|
+
run.push(word[0]);
|
|
3076
|
+
runIndex.push(index);
|
|
2989
3077
|
}
|
|
2990
|
-
if (indices.has(word[0])) run.push(word[0]);
|
|
2991
3078
|
previousEnd = word.index + word[0].length;
|
|
2992
3079
|
}
|
|
2993
3080
|
return inspect();
|
|
@@ -3118,6 +3205,9 @@ function validateCask(raw) {
|
|
|
3118
3205
|
const second = b64urlIndex(s[tsAt + 5]);
|
|
3119
3206
|
return month <= 11 && day <= 30 && hour <= 23 && minute <= 59 && second <= 59;
|
|
3120
3207
|
}
|
|
3208
|
+
function isInvisibleChar(char) {
|
|
3209
|
+
return INVISIBLE_ONE.test(char);
|
|
3210
|
+
}
|
|
3121
3211
|
function stripInvisible(text) {
|
|
3122
3212
|
return text.replace(INVISIBLE_RE, "");
|
|
3123
3213
|
}
|
|
@@ -3183,7 +3273,7 @@ function scanInjection(text, ctx = {}) {
|
|
|
3183
3273
|
}
|
|
3184
3274
|
}
|
|
3185
3275
|
if (matched.length === 0) return null;
|
|
3186
|
-
const untrusted = !!ctx.tool && UNTRUSTED_TOOLS.test(ctx.tool);
|
|
3276
|
+
const untrusted = ctx.untrusted ?? (!!ctx.tool && UNTRUSTED_TOOLS.test(ctx.tool));
|
|
3187
3277
|
const score = matched.length + (untrusted ? 1 : 0);
|
|
3188
3278
|
const confidence = score >= 3 ? "high" : score === 2 ? "medium" : "low";
|
|
3189
3279
|
return { signals: untrusted ? [...matched, "untrusted-origin"] : matched, confidence };
|
|
@@ -3371,6 +3461,45 @@ function matchArgumentText(text, fieldPath, budget) {
|
|
|
3371
3461
|
}
|
|
3372
3462
|
return best;
|
|
3373
3463
|
}
|
|
3464
|
+
function matchArgumentBatch(fragments, fieldPath, budget) {
|
|
3465
|
+
if (!fragments.length) return null;
|
|
3466
|
+
const text = fragments.join(BATCH_SEPARATOR);
|
|
3467
|
+
const lower2 = text.toLowerCase();
|
|
3468
|
+
let best = null;
|
|
3469
|
+
for (const pattern of DLP_PATTERNS) {
|
|
3470
|
+
if (pattern.keywords && !pattern.keywords.some((kw) => lower2.includes(kw.toLowerCase())))
|
|
3471
|
+
continue;
|
|
3472
|
+
const flags = pattern.regex.flags.replace(/[gmy]/g, "");
|
|
3473
|
+
if (!new RegExp(pattern.regex.source, flags + "m").test(text)) continue;
|
|
3474
|
+
for (const fragment of fragments) {
|
|
3475
|
+
if (firstAcceptedMatch(pattern, fragment) === null) continue;
|
|
3476
|
+
const severity = pattern.contextBoost && isAssignmentContext(fragment) ? "block" : pattern.severity;
|
|
3477
|
+
if (severity === "block")
|
|
3478
|
+
return {
|
|
3479
|
+
patternName: pattern.name,
|
|
3480
|
+
fieldPath,
|
|
3481
|
+
redactedSample: "[encoded secret]",
|
|
3482
|
+
severity
|
|
3483
|
+
};
|
|
3484
|
+
best ??= {
|
|
3485
|
+
patternName: pattern.name,
|
|
3486
|
+
fieldPath,
|
|
3487
|
+
redactedSample: "[encoded secret]",
|
|
3488
|
+
severity
|
|
3489
|
+
};
|
|
3490
|
+
if (!pattern.contextBoost) break;
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
if (findMnemonic(text, budget)) {
|
|
3494
|
+
return {
|
|
3495
|
+
patternName: "BIP-39 Recovery Phrase",
|
|
3496
|
+
fieldPath,
|
|
3497
|
+
redactedSample: "[recovery phrase]",
|
|
3498
|
+
severity: "block"
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
return best;
|
|
3502
|
+
}
|
|
3374
3503
|
function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
3375
3504
|
if (depth > MAX_DEPTH2 || args === null || args === void 0) return null;
|
|
3376
3505
|
if (Array.isArray(args)) {
|
|
@@ -4339,6 +4468,9 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
4339
4468
|
return null;
|
|
4340
4469
|
}
|
|
4341
4470
|
}
|
|
4471
|
+
function newSecretViewBudget() {
|
|
4472
|
+
return { hashes: 0, nestedBytes: 0, joinedLabelBytes: 0, reasons: /* @__PURE__ */ new Set() };
|
|
4473
|
+
}
|
|
4342
4474
|
function textBytes(buf) {
|
|
4343
4475
|
if (buf.length < 16) return null;
|
|
4344
4476
|
const text = buf.toString("utf8");
|
|
@@ -4357,13 +4489,41 @@ function percentOnce(text) {
|
|
|
4357
4489
|
}
|
|
4358
4490
|
});
|
|
4359
4491
|
}
|
|
4492
|
+
function* wrappedBase64(text) {
|
|
4493
|
+
if (!text.includes("\n")) return;
|
|
4494
|
+
const word = /^[A-Za-z0-9+/_-]+={0,2}$/;
|
|
4495
|
+
let run = [];
|
|
4496
|
+
let width = 0;
|
|
4497
|
+
const flush = function* () {
|
|
4498
|
+
if (run.length >= 2) yield run.join("");
|
|
4499
|
+
run = [];
|
|
4500
|
+
width = 0;
|
|
4501
|
+
};
|
|
4502
|
+
for (const raw of text.split("\n")) {
|
|
4503
|
+
const line = raw.trim();
|
|
4504
|
+
const full = line.length >= 16 && word.test(line) && !line.includes("=");
|
|
4505
|
+
if (full && (!run.length || line.length === width)) {
|
|
4506
|
+
run.push(line);
|
|
4507
|
+
width = line.length;
|
|
4508
|
+
continue;
|
|
4509
|
+
}
|
|
4510
|
+
if (run.length && word.test(line) && line.length >= 2 && line.length <= width) {
|
|
4511
|
+
run.push(line);
|
|
4512
|
+
yield* flush();
|
|
4513
|
+
continue;
|
|
4514
|
+
}
|
|
4515
|
+
yield* flush();
|
|
4516
|
+
if (full) {
|
|
4517
|
+
run.push(line);
|
|
4518
|
+
width = line.length;
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
yield* flush();
|
|
4522
|
+
}
|
|
4360
4523
|
function* encodedRegions(text, budget) {
|
|
4361
4524
|
yield [text, false];
|
|
4525
|
+
for (const block of wrappedBase64(text)) yield [block, false];
|
|
4362
4526
|
for (const match of text.matchAll(/(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,30}:\/\/[^\s<>"']+/gi)) {
|
|
4363
|
-
if (++budget.candidates > SECRET_VIEW_LIMITS.maxCandidates) {
|
|
4364
|
-
budget.reasons.add("decode-candidates");
|
|
4365
|
-
return;
|
|
4366
|
-
}
|
|
4367
4527
|
try {
|
|
4368
4528
|
new URL(match[0]);
|
|
4369
4529
|
} catch {
|
|
@@ -4383,6 +4543,8 @@ function* encodedRegions(text, budget) {
|
|
|
4383
4543
|
];
|
|
4384
4544
|
for (const region of regions) if (region) yield [region, false];
|
|
4385
4545
|
const labels = host2.split(".");
|
|
4546
|
+
if (host2.length > SECRET_VIEW_LIMITS.maxHostLength || labels.length > SECRET_VIEW_LIMITS.maxLabels || !labels.every((l) => l.length >= 1 && l.length <= SECRET_VIEW_LIMITS.maxLabelLength))
|
|
4547
|
+
continue;
|
|
4386
4548
|
for (let start = 0; start < labels.length; start++) {
|
|
4387
4549
|
let joined = labels[start];
|
|
4388
4550
|
for (let end = start + 1; end < Math.min(labels.length, start + 8); end++) {
|
|
@@ -4390,11 +4552,13 @@ function* encodedRegions(text, budget) {
|
|
|
4390
4552
|
if (!run.every((s) => /^[a-f0-9]{2,}$/i.test(s)) && !run.every((s) => /^[A-Z2-7]{4,}$/i.test(s)))
|
|
4391
4553
|
break;
|
|
4392
4554
|
joined += labels[end];
|
|
4393
|
-
if (
|
|
4394
|
-
|
|
4555
|
+
if (joined.length < 16) continue;
|
|
4556
|
+
budget.joinedLabelBytes += joined.length;
|
|
4557
|
+
if (budget.joinedLabelBytes > SECRET_VIEW_LIMITS.maxJoinedLabelBytes) {
|
|
4558
|
+
budget.reasons.add("decode-labels");
|
|
4395
4559
|
return;
|
|
4396
4560
|
}
|
|
4397
|
-
|
|
4561
|
+
yield [joined, true];
|
|
4398
4562
|
}
|
|
4399
4563
|
}
|
|
4400
4564
|
}
|
|
@@ -4431,10 +4595,6 @@ function* decodedCandidates(text, budget) {
|
|
|
4431
4595
|
if (joined) yield ["dns-labels", region];
|
|
4432
4596
|
for (const [kind, re] of families) {
|
|
4433
4597
|
for (const match of region.matchAll(re)) {
|
|
4434
|
-
if (++budget.candidates > SECRET_VIEW_LIMITS.maxCandidates) {
|
|
4435
|
-
budget.reasons.add("decode-candidates");
|
|
4436
|
-
return;
|
|
4437
|
-
}
|
|
4438
4598
|
const raw = match[0];
|
|
4439
4599
|
let buf;
|
|
4440
4600
|
if (kind === "base64") {
|
|
@@ -4457,42 +4617,76 @@ function* decodedCandidates(text, budget) {
|
|
|
4457
4617
|
}
|
|
4458
4618
|
}
|
|
4459
4619
|
}
|
|
4460
|
-
function
|
|
4620
|
+
function secretViews(text, budget) {
|
|
4461
4621
|
const seen = /* @__PURE__ */ new Set([text]);
|
|
4462
|
-
const
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
for (const
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4622
|
+
const out = [];
|
|
4623
|
+
let frontier = [{ text, transforms: [] }];
|
|
4624
|
+
for (let depth = 1; depth <= SECRET_VIEW_LIMITS.maxDepth; depth++) {
|
|
4625
|
+
const next = [];
|
|
4626
|
+
for (const parent of frontier) {
|
|
4627
|
+
let perLeaf = 0;
|
|
4628
|
+
for (const [kind, decoded] of decodedCandidates(parent.text, budget)) {
|
|
4629
|
+
if (seen.has(decoded)) continue;
|
|
4630
|
+
seen.add(decoded);
|
|
4631
|
+
const normalizing = kind === "invisibles" || kind === "percent";
|
|
4632
|
+
if (depth >= 2 && !normalizing) {
|
|
4633
|
+
budget.nestedBytes += Buffer.byteLength(decoded);
|
|
4634
|
+
if (++perLeaf > SECRET_VIEW_LIMITS.maxNestedViewsPerLeaf || budget.nestedBytes > SECRET_VIEW_LIMITS.maxNestedBytes) {
|
|
4635
|
+
budget.reasons.add("decode-nested");
|
|
4636
|
+
return out;
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
const view = { text: decoded, transforms: [...parent.transforms, kind] };
|
|
4640
|
+
out.push(view);
|
|
4641
|
+
next.push(view);
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
frontier = next;
|
|
4645
|
+
if (!frontier.length) return out;
|
|
4646
|
+
}
|
|
4647
|
+
for (const view of frontier) {
|
|
4648
|
+
if (!decodedCandidates(view.text, budget).next().done) {
|
|
4649
|
+
budget.reasons.add("decode-nested");
|
|
4650
|
+
break;
|
|
4481
4651
|
}
|
|
4482
|
-
if (budget.candidates > SECRET_VIEW_LIMITS.maxCandidates) return;
|
|
4483
4652
|
}
|
|
4653
|
+
return out;
|
|
4654
|
+
}
|
|
4655
|
+
function batches(views) {
|
|
4656
|
+
const out = [];
|
|
4657
|
+
let current = [];
|
|
4658
|
+
let size = 0;
|
|
4659
|
+
for (const { text } of views) {
|
|
4660
|
+
if (current.length && size + text.length > SECRET_VIEW_LIMITS.batchCharacters) {
|
|
4661
|
+
out.push(current);
|
|
4662
|
+
current = [];
|
|
4663
|
+
size = 0;
|
|
4664
|
+
}
|
|
4665
|
+
current.push(text);
|
|
4666
|
+
size += text.length;
|
|
4667
|
+
}
|
|
4668
|
+
if (current.length) out.push(current);
|
|
4669
|
+
return out;
|
|
4484
4670
|
}
|
|
4485
4671
|
function inspectArgumentSecrets(args) {
|
|
4486
|
-
const budget =
|
|
4672
|
+
const budget = newSecretViewBudget();
|
|
4487
4673
|
const ancestors = /* @__PURE__ */ new Set();
|
|
4488
4674
|
let nodes = 0;
|
|
4489
4675
|
let sourceBytes = 0;
|
|
4490
4676
|
let tailBytes = 0;
|
|
4491
4677
|
let best = null;
|
|
4678
|
+
const blocked = () => best?.severity === "block";
|
|
4492
4679
|
const accept = (match) => {
|
|
4493
4680
|
if (match && (!best || best.severity !== "block" && match.severity === "block")) best = match;
|
|
4494
4681
|
};
|
|
4682
|
+
const decodeSegment = (segment, fieldPath) => {
|
|
4683
|
+
for (const batch of batches(secretViews(segment, budget))) {
|
|
4684
|
+
accept(matchArgumentBatch(batch, fieldPath, budget));
|
|
4685
|
+
if (blocked()) return;
|
|
4686
|
+
}
|
|
4687
|
+
};
|
|
4495
4688
|
const visit2 = (value, depth, fieldPath) => {
|
|
4689
|
+
if (blocked()) return;
|
|
4496
4690
|
if (++nodes > ARGUMENT_SCAN_LIMITS.maxNodes) {
|
|
4497
4691
|
budget.reasons.add("argument-nodes");
|
|
4498
4692
|
return;
|
|
@@ -4534,14 +4728,11 @@ function inspectArgumentSecrets(args) {
|
|
|
4534
4728
|
}
|
|
4535
4729
|
sourceBytes += Math.min(bytes, Math.max(0, remaining));
|
|
4536
4730
|
if (!text) return;
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
if (hit)
|
|
4540
|
-
accept(view.transforms.length ? { ...hit, redactedSample: "[encoded secret]" } : hit);
|
|
4541
|
-
}
|
|
4731
|
+
accept(matchArgumentText(text, fieldPath, budget));
|
|
4732
|
+
let scannedEnd = text.length;
|
|
4542
4733
|
if (!truncated && value.length > window) {
|
|
4543
|
-
budget.reasons.add("decode-window");
|
|
4544
4734
|
for (let start = window - overlap; start < value.length; start += window - overlap) {
|
|
4735
|
+
if (blocked()) return;
|
|
4545
4736
|
const chunk2 = value.slice(start, start + window);
|
|
4546
4737
|
const chunkBytes = Buffer.byteLength(chunk2);
|
|
4547
4738
|
if (tailBytes + chunkBytes > ARGUMENT_SCAN_LIMITS.maxTailBytes) {
|
|
@@ -4550,9 +4741,16 @@ function inspectArgumentSecrets(args) {
|
|
|
4550
4741
|
}
|
|
4551
4742
|
tailBytes += chunkBytes;
|
|
4552
4743
|
accept(matchArgumentText(chunk2, fieldPath, budget));
|
|
4744
|
+
scannedEnd = start + chunk2.length;
|
|
4553
4745
|
if (start + window >= value.length) break;
|
|
4554
4746
|
}
|
|
4555
4747
|
}
|
|
4748
|
+
for (let start = 0; start < scannedEnd && !blocked(); ) {
|
|
4749
|
+
let end = Math.min(start + window, scannedEnd);
|
|
4750
|
+
while (end < scannedEnd && runChar(value, end - 1) && runChar(value, end)) end++;
|
|
4751
|
+
decodeSegment(value.slice(start, end), fieldPath);
|
|
4752
|
+
start = end;
|
|
4753
|
+
}
|
|
4556
4754
|
const trimmed = text.trim();
|
|
4557
4755
|
if (value.length < ARGUMENT_SCAN_LIMITS.maxJsonCharacters && (trimmed.startsWith("{") || trimmed.startsWith("["))) {
|
|
4558
4756
|
try {
|
|
@@ -4571,7 +4769,7 @@ function inspectArgumentSecrets(args) {
|
|
|
4571
4769
|
function scanArgumentSecrets(args) {
|
|
4572
4770
|
const result = inspectArgumentSecrets(args);
|
|
4573
4771
|
if (result.match) return result.match;
|
|
4574
|
-
return result.
|
|
4772
|
+
return result.coverage === "incomplete" ? {
|
|
4575
4773
|
patternName: "Argument inspection incomplete",
|
|
4576
4774
|
fieldPath: "args",
|
|
4577
4775
|
redactedSample: "[uninspected content]",
|
|
@@ -4599,18 +4797,13 @@ function tokenize2(toolName) {
|
|
|
4599
4797
|
return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
|
|
4600
4798
|
}
|
|
4601
4799
|
function extractShellCommand(toolName, args, toolInspection) {
|
|
4602
|
-
const
|
|
4603
|
-
|
|
4604
|
-
if (!matchingPattern) return null;
|
|
4605
|
-
const fieldPath = toolInspection[matchingPattern];
|
|
4800
|
+
const fieldPath = inspectionEntry(toolName, toolInspection);
|
|
4801
|
+
if (fieldPath === void 0) return null;
|
|
4606
4802
|
const value = getNestedValue(args, typeof fieldPath === "string" ? fieldPath : fieldPath.field);
|
|
4607
4803
|
return typeof value === "string" ? value : null;
|
|
4608
4804
|
}
|
|
4609
4805
|
function isSqlTool(toolName, toolInspection) {
|
|
4610
|
-
const
|
|
4611
|
-
const matchingPattern = patterns.find((p) => matchesPattern(toolName, p));
|
|
4612
|
-
if (!matchingPattern) return false;
|
|
4613
|
-
const fieldName = toolInspection[matchingPattern];
|
|
4806
|
+
const fieldName = inspectionEntry(toolName, toolInspection);
|
|
4614
4807
|
return fieldName === "sql" || fieldName === "query";
|
|
4615
4808
|
}
|
|
4616
4809
|
function pipeChainVerdict(command, isTrustedHost2, highAction = "review") {
|
|
@@ -4677,9 +4870,10 @@ function egressPolicyVerdict(eg) {
|
|
|
4677
4870
|
}
|
|
4678
4871
|
async function evaluatePolicy(config, toolName, args, context = {}, hooks = {}) {
|
|
4679
4872
|
let protectedVerdict = null;
|
|
4873
|
+
const layers = inspectionLayers(config.policy);
|
|
4680
4874
|
if (context.agent !== "Terminal") {
|
|
4681
4875
|
try {
|
|
4682
|
-
const accesses = context.accesses ?? collectFileAccesses(toolName, args, context,
|
|
4876
|
+
const accesses = context.accesses ?? collectFileAccesses(toolName, args, context, layers);
|
|
4683
4877
|
protectedVerdict = evaluateProtectedFileAccess(accesses, context, catalogSettings(config));
|
|
4684
4878
|
} catch {
|
|
4685
4879
|
protectedVerdict = incompleteFileVerdict();
|
|
@@ -4714,7 +4908,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4714
4908
|
hooks
|
|
4715
4909
|
);
|
|
4716
4910
|
if (result.decision === "block") return result;
|
|
4717
|
-
if (context.agent !== "Terminal" && resolveShellInput(toolName, args,
|
|
4911
|
+
if (context.agent !== "Terminal" && resolveShellInput(toolName, args, layers).kind === "uninspectable") {
|
|
4718
4912
|
return {
|
|
4719
4913
|
decision: "review",
|
|
4720
4914
|
ruleName: "shell-inspection-incomplete",
|
|
@@ -4730,13 +4924,14 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4730
4924
|
const { agent, cwd, activeEnvironment } = context;
|
|
4731
4925
|
const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2 } = hooks;
|
|
4732
4926
|
const settingsView = catalogSettings(config);
|
|
4927
|
+
const layers = inspectionLayers(config.policy);
|
|
4733
4928
|
const cv = (id) => checkValue(settingsView, id);
|
|
4734
4929
|
let loggedFinding;
|
|
4735
4930
|
const noteLogged = (v) => {
|
|
4736
4931
|
if (v?.logged && !loggedFinding) loggedFinding = v;
|
|
4737
4932
|
};
|
|
4738
4933
|
const allowed = () => loggedFinding ?? { decision: "allow" };
|
|
4739
|
-
const shellInput = resolveShellInput(toolName, args,
|
|
4934
|
+
const shellInput = resolveShellInput(toolName, args, layers);
|
|
4740
4935
|
const wouldBeIgnored = matchesPattern(toolName, config.policy.ignoredTools);
|
|
4741
4936
|
{
|
|
4742
4937
|
const dest = ssrfDestinationFloor(toolName, args, {
|
|
@@ -4841,13 +5036,10 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4841
5036
|
return { id, value: cv(id) };
|
|
4842
5037
|
};
|
|
4843
5038
|
const matches = config.policy.smartRules.filter((rule) => {
|
|
4844
|
-
if (!toolMatchesRule(toolName, rule.tool,
|
|
5039
|
+
if (!toolMatchesRule(toolName, rule.tool, layers)) return false;
|
|
4845
5040
|
if (astSuppressed(rule)) return false;
|
|
4846
5041
|
if (rmCleanupWaiver && rule.name === "review-rm" && rule.verdict === "review") return false;
|
|
4847
|
-
if (!evaluateSmartConditions(
|
|
4848
|
-
shellRuleArgs(toolName, args, rule.tool, config.policy.toolInspection),
|
|
4849
|
-
rule
|
|
4850
|
-
))
|
|
5042
|
+
if (!evaluateSmartConditions(shellRuleArgs(toolName, args, rule.tool, layers), rule))
|
|
4851
5043
|
return false;
|
|
4852
5044
|
const check = ruleCheck(rule);
|
|
4853
5045
|
if (check?.value === "off") return false;
|
|
@@ -4894,7 +5086,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4894
5086
|
let allTokens = [];
|
|
4895
5087
|
let pathTokens = [];
|
|
4896
5088
|
if (pendingToolEgress) return pendingToolEgress;
|
|
4897
|
-
const shellCommand = shellInput.kind === "shell" ? shellInput.command : shellInput.kind === "not-shell" ? extractShellCommand(toolName, args,
|
|
5089
|
+
const shellCommand = shellInput.kind === "shell" ? shellInput.command : shellInput.kind === "not-shell" ? extractShellCommand(toolName, args, layers) : null;
|
|
4898
5090
|
if (shellCommand) {
|
|
4899
5091
|
const analyzed = analyzeShellCommand(shellCommand);
|
|
4900
5092
|
allTokens = analyzed.allTokens;
|
|
@@ -4971,7 +5163,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4971
5163
|
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
4972
5164
|
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
4973
5165
|
const sshHosts = extractAllSshHosts(rawTokens.slice(1));
|
|
4974
|
-
allTokens.
|
|
5166
|
+
allTokens = allTokens.concat(sshHosts);
|
|
4975
5167
|
}
|
|
4976
5168
|
if (firstToken && firstToken.startsWith("/") && checkProvenance2) {
|
|
4977
5169
|
const prov = checkProvenance2(firstToken, cwd);
|
|
@@ -5000,7 +5192,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
5000
5192
|
};
|
|
5001
5193
|
}
|
|
5002
5194
|
}
|
|
5003
|
-
if (isSqlTool(toolName,
|
|
5195
|
+
if (isSqlTool(toolName, layers)) {
|
|
5004
5196
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
|
|
5005
5197
|
}
|
|
5006
5198
|
} else {
|
|
@@ -5008,7 +5200,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
5008
5200
|
if (args && typeof args === "object") {
|
|
5009
5201
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
5010
5202
|
const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
|
|
5011
|
-
allTokens.
|
|
5203
|
+
allTokens = allTokens.concat(extraTokens);
|
|
5012
5204
|
}
|
|
5013
5205
|
}
|
|
5014
5206
|
const isManual = agent === "Terminal";
|
|
@@ -6303,7 +6495,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
6303
6495
|
}
|
|
6304
6496
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
6305
6497
|
}
|
|
6306
|
-
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, CHECK_GROUPS, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, syntax2, MAX_ALTERNATIVES, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, SECRET_VIEW_LIMITS, ARGUMENT_SCAN_LIMITS, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, syntax4, EMPTY, CURL_SEND_FLAGS, WGET_SEND_FLAGS, IWR_SEND_FLAGS, WRITE_METHODS, GH_READ, GH_WRITE, GH_HOSTNAME_FLAGS, GH_REPO_VALUE_FLAGS, GH_API_VALUE_FLAGS, GIT_FETCH, PUBLISH, SHELLS2, OWN_HEADS, MAX_DEPTH3, CWD_CHANGERS, TERMINAL_ESCAPE_RE, CONTROL_CHAR_RE, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, MAX_BLAST_PATH, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, ENGINE_VERSION;
|
|
6498
|
+
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, CHECK_GROUPS, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, MAX_ALTERNATIVES, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, BATCH_SEPARATOR, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, SECRET_VIEW_LIMITS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, syntax4, EMPTY, CURL_SEND_FLAGS, WGET_SEND_FLAGS, IWR_SEND_FLAGS, WRITE_METHODS, GH_READ, GH_WRITE, GH_HOSTNAME_FLAGS, GH_REPO_VALUE_FLAGS, GH_API_VALUE_FLAGS, GIT_FETCH, PUBLISH, SHELLS2, OWN_HEADS, MAX_DEPTH3, CWD_CHANGERS, TERMINAL_ESCAPE_RE, CONTROL_CHAR_RE, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, MAX_BLAST_PATH, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, ENGINE_VERSION;
|
|
6307
6499
|
var init_dist = __esm({
|
|
6308
6500
|
"packages/policy-engine/dist/index.mjs"() {
|
|
6309
6501
|
"use strict";
|
|
@@ -8676,7 +8868,23 @@ var init_dist = __esm({
|
|
|
8676
8868
|
"shell",
|
|
8677
8869
|
"exec_command"
|
|
8678
8870
|
]);
|
|
8679
|
-
MCP_EXECUTORS = /* @__PURE__ */ new Set([
|
|
8871
|
+
MCP_EXECUTORS = /* @__PURE__ */ new Set([
|
|
8872
|
+
...BASH_TOOL_NAMES,
|
|
8873
|
+
"exec",
|
|
8874
|
+
"run_command",
|
|
8875
|
+
"terminal",
|
|
8876
|
+
"execute_command",
|
|
8877
|
+
"execute_shell",
|
|
8878
|
+
"execute_shell_command",
|
|
8879
|
+
"shell_execute",
|
|
8880
|
+
"run_shell",
|
|
8881
|
+
"run_terminal_command",
|
|
8882
|
+
"start_process"
|
|
8883
|
+
]);
|
|
8884
|
+
BUILTIN_FIELDS = ["command", "cmd", "script"];
|
|
8885
|
+
ARGV_FIELDS = ["args", "argv"];
|
|
8886
|
+
BARE_WORD = /^[A-Za-z0-9_./+:@%,-]+$/;
|
|
8887
|
+
MAX_ARGV_CHARACTERS = 262144;
|
|
8680
8888
|
({ syntax: syntax2 } = mvdanSh2);
|
|
8681
8889
|
MAX_ALTERNATIVES = 64;
|
|
8682
8890
|
MAX_DEPTH = 64;
|
|
@@ -10802,6 +11010,7 @@ zoo`.split("\n");
|
|
|
10802
11010
|
indices = new Map(BIP39_ENGLISH.map((word, i) => [word, i]));
|
|
10803
11011
|
lengths = [24, 21, 18, 15, 12];
|
|
10804
11012
|
hash = (value) => createHash2("sha256").update(value).digest();
|
|
11013
|
+
MAX_MNEMONIC_HASHES = 25e3;
|
|
10805
11014
|
IBAN_LENGTH = {
|
|
10806
11015
|
AD: 24,
|
|
10807
11016
|
AE: 23,
|
|
@@ -10909,6 +11118,7 @@ zoo`.split("\n");
|
|
|
10909
11118
|
CASK_TWO_ZERO_SUFFIX = /* @__PURE__ */ new Set([..."AEIMQUYcgkosw048"]);
|
|
10910
11119
|
CASK_FOUR_ZERO_SUFFIX = /* @__PURE__ */ new Set([..."AQgw"]);
|
|
10911
11120
|
INVISIBLE_RE = /[͏----︀-️]|[\u{E0000}-\u{E007F}]/gu;
|
|
11121
|
+
INVISIBLE_ONE = new RegExp(`^(?:${INVISIBLE_RE.source.split("|")[0]})$`, "u");
|
|
10912
11122
|
HOMOGLYPHS = {
|
|
10913
11123
|
// Cyrillic lowercase
|
|
10914
11124
|
\u0430: "a",
|
|
@@ -11615,6 +11825,7 @@ zoo`.split("\n");
|
|
|
11615
11825
|
/** Max nesting depth walked; anything deeper is NOT scanned. */
|
|
11616
11826
|
maxDepth: MAX_DEPTH2
|
|
11617
11827
|
};
|
|
11828
|
+
BATCH_SEPARATOR = "\n";
|
|
11618
11829
|
({ syntax: syntax3 } = mvdanSh3);
|
|
11619
11830
|
NPM_INSTALL_VERBS = /* @__PURE__ */ new Set([
|
|
11620
11831
|
"install",
|
|
@@ -12084,16 +12295,24 @@ zoo`.split("\n");
|
|
|
12084
12295
|
["browser_batch", ["actions[].input.url"]]
|
|
12085
12296
|
]);
|
|
12086
12297
|
SECRET_VIEW_LIMITS = {
|
|
12298
|
+
/** A decoded text that itself decodes is followed this many layers; a deeper layer is reviewed. */
|
|
12087
12299
|
maxDepth: 4,
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12300
|
+
/** Nested views per parent view, and nested text per call; past either the call is reviewed. */
|
|
12301
|
+
maxNestedViewsPerLeaf: 64,
|
|
12302
|
+
maxNestedBytes: 1048576,
|
|
12303
|
+
/** Joined hostname-label text per call; past it the call is reviewed. */
|
|
12304
|
+
maxJoinedLabelBytes: 1048576,
|
|
12305
|
+
/** Decoded fragments are matched in batches of about this many characters. */
|
|
12306
|
+
batchCharacters: 65536,
|
|
12307
|
+
/** Only a valid DNS name has its labels joined. */
|
|
12308
|
+
maxHostLength: 253,
|
|
12309
|
+
maxLabelLength: 63,
|
|
12310
|
+
maxLabels: 127
|
|
12092
12311
|
};
|
|
12093
12312
|
ARGUMENT_SCAN_LIMITS = {
|
|
12094
12313
|
maxDepth: 12,
|
|
12095
12314
|
maxNodes: 16384,
|
|
12096
|
-
/**
|
|
12315
|
+
/** Text is matched and decoded in windows of this size. */
|
|
12097
12316
|
maxStringCharacters: 1e5,
|
|
12098
12317
|
/** Windows overlap by this much so a token on a window edge is still seen whole. */
|
|
12099
12318
|
windowOverlap: 4096,
|
|
@@ -12103,6 +12322,11 @@ zoo`.split("\n");
|
|
|
12103
12322
|
maxTailBytes: 4194304,
|
|
12104
12323
|
maxJsonCharacters: 1e4
|
|
12105
12324
|
};
|
|
12325
|
+
RUN_CHAR = /[A-Za-z0-9+/=_%\\.-]/;
|
|
12326
|
+
runChar = (value, index) => {
|
|
12327
|
+
const char = value[index] ?? "";
|
|
12328
|
+
return RUN_CHAR.test(char) || char !== "" && isInvisibleChar(char);
|
|
12329
|
+
};
|
|
12106
12330
|
VERDICT_RANK = {
|
|
12107
12331
|
allow: 0,
|
|
12108
12332
|
review: 1,
|
|
@@ -12362,7 +12586,7 @@ zoo`.split("\n");
|
|
|
12362
12586
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
12363
12587
|
];
|
|
12364
12588
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
12365
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
12589
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v26";
|
|
12366
12590
|
DEDUPE_PREVIEW_LEN = 120;
|
|
12367
12591
|
ENGINE_VERSION = "1.4.0";
|
|
12368
12592
|
}
|
|
@@ -12492,6 +12716,10 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
12492
12716
|
taintFromEid: meta.taintFromEid,
|
|
12493
12717
|
...meta.taintSource && { taintSource: meta.taintSource }
|
|
12494
12718
|
} : {};
|
|
12719
|
+
const exposureFields = {
|
|
12720
|
+
...meta?.exposure ? { exposure: meta.exposure } : {},
|
|
12721
|
+
...meta?.exposureFromEid ? { exposureFromEid: meta.exposureFromEid } : {}
|
|
12722
|
+
};
|
|
12495
12723
|
const eid = generateEventId();
|
|
12496
12724
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
12497
12725
|
// eid first: the outbox shipper dedups on it, and a fixed leading field
|
|
@@ -12514,6 +12742,7 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
12514
12742
|
...loopCountField,
|
|
12515
12743
|
...transcriptPathField,
|
|
12516
12744
|
...taintFields,
|
|
12745
|
+
...exposureFields,
|
|
12517
12746
|
...testRun,
|
|
12518
12747
|
agent: meta?.agent,
|
|
12519
12748
|
mcpServer: meta?.mcpServer,
|
|
@@ -12542,6 +12771,82 @@ var init_audit = __esm({
|
|
|
12542
12771
|
}
|
|
12543
12772
|
});
|
|
12544
12773
|
|
|
12774
|
+
// src/auth/trusted-recipients.ts
|
|
12775
|
+
import fs2 from "fs";
|
|
12776
|
+
import path2 from "path";
|
|
12777
|
+
import os2 from "os";
|
|
12778
|
+
function getTrustedRecipientsPath() {
|
|
12779
|
+
return path2.join(os2.homedir(), ".node9", "trusted-recipients.json");
|
|
12780
|
+
}
|
|
12781
|
+
function readTrustedRecipients() {
|
|
12782
|
+
try {
|
|
12783
|
+
const raw = fs2.readFileSync(getTrustedRecipientsPath(), "utf8");
|
|
12784
|
+
const parsed = JSON.parse(raw);
|
|
12785
|
+
return Array.isArray(parsed.entries) ? parsed.entries.filter((e) => e && typeof e.value === "string") : [];
|
|
12786
|
+
} catch {
|
|
12787
|
+
return [];
|
|
12788
|
+
}
|
|
12789
|
+
}
|
|
12790
|
+
function writeTrustedRecipients(entries) {
|
|
12791
|
+
const filePath = getTrustedRecipientsPath();
|
|
12792
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
12793
|
+
const tmp = filePath + ".node9-tmp";
|
|
12794
|
+
fs2.writeFileSync(tmp, JSON.stringify({ entries }, null, 2), { mode: 384 });
|
|
12795
|
+
fs2.renameSync(tmp, filePath);
|
|
12796
|
+
}
|
|
12797
|
+
function normalizeRecipient(raw) {
|
|
12798
|
+
const v = raw.trim();
|
|
12799
|
+
if (!v) return null;
|
|
12800
|
+
if (/^(email|host|iban):/i.test(v)) {
|
|
12801
|
+
const i = v.indexOf(":");
|
|
12802
|
+
const kind = v.slice(0, i).toLowerCase();
|
|
12803
|
+
const inner = normalizeRecipient(v.slice(i + 1));
|
|
12804
|
+
return inner && inner.startsWith(`${kind}:`) ? inner : null;
|
|
12805
|
+
}
|
|
12806
|
+
const found = extractArgDestinations({ to: v });
|
|
12807
|
+
return found.length === 1 ? found[0] : null;
|
|
12808
|
+
}
|
|
12809
|
+
function addTrustedRecipient(raw, addedBy = "user") {
|
|
12810
|
+
const value = normalizeRecipient(raw);
|
|
12811
|
+
if (!value) return null;
|
|
12812
|
+
const entries = readTrustedRecipients();
|
|
12813
|
+
if (!entries.some((e) => e.value === value)) {
|
|
12814
|
+
entries.push({ value, addedAt: Date.now(), addedBy });
|
|
12815
|
+
writeTrustedRecipients(entries);
|
|
12816
|
+
}
|
|
12817
|
+
return value;
|
|
12818
|
+
}
|
|
12819
|
+
function removeTrustedRecipient(raw) {
|
|
12820
|
+
const value = normalizeRecipient(raw) ?? raw.trim();
|
|
12821
|
+
const entries = readTrustedRecipients();
|
|
12822
|
+
const next = entries.filter((e) => e.value !== value);
|
|
12823
|
+
if (next.length === entries.length) return false;
|
|
12824
|
+
writeTrustedRecipients(next);
|
|
12825
|
+
return true;
|
|
12826
|
+
}
|
|
12827
|
+
function mtimeOf() {
|
|
12828
|
+
try {
|
|
12829
|
+
return fs2.statSync(getTrustedRecipientsPath()).mtimeMs;
|
|
12830
|
+
} catch {
|
|
12831
|
+
return 0;
|
|
12832
|
+
}
|
|
12833
|
+
}
|
|
12834
|
+
function isTrustedRecipient(value) {
|
|
12835
|
+
const mtime = mtimeOf();
|
|
12836
|
+
if (!cache || cache.mtime !== mtime) {
|
|
12837
|
+
cache = { mtime, values: new Set(readTrustedRecipients().map((e) => e.value)) };
|
|
12838
|
+
}
|
|
12839
|
+
return cache.values.has(value);
|
|
12840
|
+
}
|
|
12841
|
+
var cache;
|
|
12842
|
+
var init_trusted_recipients = __esm({
|
|
12843
|
+
"src/auth/trusted-recipients.ts"() {
|
|
12844
|
+
"use strict";
|
|
12845
|
+
init_dist();
|
|
12846
|
+
cache = null;
|
|
12847
|
+
}
|
|
12848
|
+
});
|
|
12849
|
+
|
|
12545
12850
|
// src/config-schema.ts
|
|
12546
12851
|
import { z } from "zod";
|
|
12547
12852
|
function formatIssues(issues) {
|
|
@@ -12815,15 +13120,15 @@ var init_config_schema = __esm({
|
|
|
12815
13120
|
});
|
|
12816
13121
|
|
|
12817
13122
|
// src/shields.ts
|
|
12818
|
-
import
|
|
12819
|
-
import
|
|
12820
|
-
import
|
|
13123
|
+
import fs3 from "fs";
|
|
13124
|
+
import path3 from "path";
|
|
13125
|
+
import os3 from "os";
|
|
12821
13126
|
import crypto3 from "crypto";
|
|
12822
13127
|
function loadUserShields() {
|
|
12823
13128
|
const result = {};
|
|
12824
13129
|
let entries;
|
|
12825
13130
|
try {
|
|
12826
|
-
entries =
|
|
13131
|
+
entries = fs3.readdirSync(USER_SHIELDS_DIR).filter((f) => f.endsWith(".json"));
|
|
12827
13132
|
} catch (err2) {
|
|
12828
13133
|
if (err2.code !== "ENOENT") {
|
|
12829
13134
|
process.stderr.write(
|
|
@@ -12834,9 +13139,9 @@ function loadUserShields() {
|
|
|
12834
13139
|
return result;
|
|
12835
13140
|
}
|
|
12836
13141
|
for (const file2 of entries) {
|
|
12837
|
-
const filePath =
|
|
13142
|
+
const filePath = path3.join(USER_SHIELDS_DIR, file2);
|
|
12838
13143
|
try {
|
|
12839
|
-
const raw = JSON.parse(
|
|
13144
|
+
const raw = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
12840
13145
|
const v = validateShieldDefinition(raw);
|
|
12841
13146
|
if ("error" in v) {
|
|
12842
13147
|
process.stderr.write(`[node9] ${v.error}: ${filePath}
|
|
@@ -12871,7 +13176,7 @@ function listShields() {
|
|
|
12871
13176
|
}
|
|
12872
13177
|
function readShieldsFile() {
|
|
12873
13178
|
try {
|
|
12874
|
-
const raw =
|
|
13179
|
+
const raw = fs3.readFileSync(SHIELDS_STATE_FILE, "utf-8");
|
|
12875
13180
|
if (!raw.trim()) return { active: [] };
|
|
12876
13181
|
const parsed = JSON.parse(raw);
|
|
12877
13182
|
const active = Array.isArray(parsed.active) ? parsed.active.filter(
|
|
@@ -12892,12 +13197,12 @@ function readShieldsFile() {
|
|
|
12892
13197
|
}
|
|
12893
13198
|
}
|
|
12894
13199
|
function writeShieldsFile(data) {
|
|
12895
|
-
|
|
13200
|
+
fs3.mkdirSync(path3.dirname(SHIELDS_STATE_FILE), { recursive: true });
|
|
12896
13201
|
const tmp = `${SHIELDS_STATE_FILE}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
|
|
12897
13202
|
const toWrite = { active: data.active };
|
|
12898
13203
|
if (data.overrides && Object.keys(data.overrides).length > 0) toWrite.overrides = data.overrides;
|
|
12899
|
-
|
|
12900
|
-
|
|
13204
|
+
fs3.writeFileSync(tmp, JSON.stringify(toWrite, null, 2), { mode: 384 });
|
|
13205
|
+
fs3.renameSync(tmp, SHIELDS_STATE_FILE);
|
|
12901
13206
|
}
|
|
12902
13207
|
function readActiveShields() {
|
|
12903
13208
|
return readShieldsFile().active;
|
|
@@ -12981,11 +13286,11 @@ function installShield(name, shieldJson) {
|
|
|
12981
13286
|
if (v.ok.name !== name) {
|
|
12982
13287
|
throw new Error(`Shield name mismatch: file declares '${v.ok.name}' but expected '${name}'`);
|
|
12983
13288
|
}
|
|
12984
|
-
|
|
12985
|
-
const filePath =
|
|
13289
|
+
fs3.mkdirSync(USER_SHIELDS_DIR, { recursive: true });
|
|
13290
|
+
const filePath = path3.join(USER_SHIELDS_DIR, `${name}.json`);
|
|
12986
13291
|
const tmp = `${filePath}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
|
|
12987
|
-
|
|
12988
|
-
|
|
13292
|
+
fs3.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
|
|
13293
|
+
fs3.renameSync(tmp, filePath);
|
|
12989
13294
|
}
|
|
12990
13295
|
var USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS, USER_SHIELDS_DIR_PATH;
|
|
12991
13296
|
var init_shields = __esm({
|
|
@@ -12993,9 +13298,9 @@ var init_shields = __esm({
|
|
|
12993
13298
|
"use strict";
|
|
12994
13299
|
init_dist();
|
|
12995
13300
|
init_dist();
|
|
12996
|
-
USER_SHIELDS_DIR =
|
|
13301
|
+
USER_SHIELDS_DIR = path3.join(os3.homedir(), ".node9", "shields");
|
|
12997
13302
|
SHIELDS = buildSHIELDS();
|
|
12998
|
-
SHIELDS_STATE_FILE =
|
|
13303
|
+
SHIELDS_STATE_FILE = path3.join(os3.homedir(), ".node9", "shields.json");
|
|
12999
13304
|
RULE_KEY_MIGRATIONS = [
|
|
13000
13305
|
// 2026-05-21 — project-jail .env reads promoted review → block. Renamed
|
|
13001
13306
|
// so the key honestly describes the verdict. Credential-file rules
|
|
@@ -13229,15 +13534,15 @@ var init_build = __esm({
|
|
|
13229
13534
|
});
|
|
13230
13535
|
|
|
13231
13536
|
// src/auth/trusted-hosts.ts
|
|
13232
|
-
import
|
|
13233
|
-
import
|
|
13234
|
-
import
|
|
13537
|
+
import fs4 from "fs";
|
|
13538
|
+
import path4 from "path";
|
|
13539
|
+
import os4 from "os";
|
|
13235
13540
|
function getTrustedHostsPath() {
|
|
13236
|
-
return
|
|
13541
|
+
return path4.join(os4.homedir(), ".node9", "trusted-hosts.json");
|
|
13237
13542
|
}
|
|
13238
13543
|
function readTrustedHosts() {
|
|
13239
13544
|
try {
|
|
13240
|
-
const raw =
|
|
13545
|
+
const raw = fs4.readFileSync(getTrustedHostsPath(), "utf8");
|
|
13241
13546
|
const parsed = JSON.parse(raw);
|
|
13242
13547
|
return Array.isArray(parsed.hosts) ? parsed.hosts : [];
|
|
13243
13548
|
} catch {
|
|
@@ -13246,7 +13551,7 @@ function readTrustedHosts() {
|
|
|
13246
13551
|
}
|
|
13247
13552
|
function getFileMtime() {
|
|
13248
13553
|
try {
|
|
13249
|
-
return
|
|
13554
|
+
return fs4.statSync(getTrustedHostsPath()).mtimeMs;
|
|
13250
13555
|
} catch {
|
|
13251
13556
|
return 0;
|
|
13252
13557
|
}
|
|
@@ -13263,10 +13568,10 @@ function getCachedHosts() {
|
|
|
13263
13568
|
}
|
|
13264
13569
|
function writeTrustedHosts(hosts) {
|
|
13265
13570
|
const filePath = getTrustedHostsPath();
|
|
13266
|
-
|
|
13571
|
+
fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
13267
13572
|
const tmp = filePath + ".node9-tmp";
|
|
13268
|
-
|
|
13269
|
-
|
|
13573
|
+
fs4.writeFileSync(tmp, JSON.stringify({ hosts }, null, 2), { mode: 384 });
|
|
13574
|
+
fs4.renameSync(tmp, filePath);
|
|
13270
13575
|
_cache = { hosts, expiry: Date.now() + CACHE_TTL_MS, mtime: getFileMtime() };
|
|
13271
13576
|
}
|
|
13272
13577
|
function addTrustedHost(host2) {
|
|
@@ -13572,9 +13877,9 @@ var init_api_url = __esm({
|
|
|
13572
13877
|
});
|
|
13573
13878
|
|
|
13574
13879
|
// src/config/index.ts
|
|
13575
|
-
import
|
|
13576
|
-
import
|
|
13577
|
-
import
|
|
13880
|
+
import fs5 from "fs";
|
|
13881
|
+
import path5 from "path";
|
|
13882
|
+
import os5 from "os";
|
|
13578
13883
|
function sanitizeSsrfAllow(entries, source) {
|
|
13579
13884
|
const kept = [];
|
|
13580
13885
|
for (const entry of entries) {
|
|
@@ -13596,9 +13901,9 @@ function _resetConfigCache() {
|
|
|
13596
13901
|
}
|
|
13597
13902
|
function getGlobalSettings() {
|
|
13598
13903
|
try {
|
|
13599
|
-
const globalConfigPath2 =
|
|
13600
|
-
if (
|
|
13601
|
-
const raw = JSON.parse(
|
|
13904
|
+
const globalConfigPath2 = path5.join(os5.homedir(), ".node9", "config.json");
|
|
13905
|
+
if (fs5.existsSync(globalConfigPath2)) {
|
|
13906
|
+
const raw = JSON.parse(fs5.readFileSync(globalConfigPath2, "utf-8"));
|
|
13602
13907
|
const parsed = isV2File(raw) ? v2ToLegacy(raw).legacy : raw;
|
|
13603
13908
|
const settings = parsed.settings || {};
|
|
13604
13909
|
return {
|
|
@@ -13627,8 +13932,8 @@ function noteRejectedApiUrl(raw) {
|
|
|
13627
13932
|
if (rejectedApiUrlsSeen.has(key)) return;
|
|
13628
13933
|
rejectedApiUrlsSeen.add(key);
|
|
13629
13934
|
try {
|
|
13630
|
-
|
|
13631
|
-
|
|
13935
|
+
fs5.appendFileSync(
|
|
13936
|
+
path5.join(os5.homedir(), ".node9", "hook-debug.log"),
|
|
13632
13937
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] REFUSED apiUrl, using the default instead: ${String(raw).slice(0, 200).replace(/[\r\n]+/g, " ")}
|
|
13633
13938
|
`
|
|
13634
13939
|
);
|
|
@@ -13645,9 +13950,9 @@ function getCredentials() {
|
|
|
13645
13950
|
};
|
|
13646
13951
|
}
|
|
13647
13952
|
try {
|
|
13648
|
-
const credPath =
|
|
13649
|
-
if (
|
|
13650
|
-
const creds = JSON.parse(
|
|
13953
|
+
const credPath = path5.join(os5.homedir(), ".node9", "credentials.json");
|
|
13954
|
+
if (fs5.existsSync(credPath)) {
|
|
13955
|
+
const creds = JSON.parse(fs5.readFileSync(credPath, "utf-8"));
|
|
13651
13956
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
13652
13957
|
const profile = creds[profileName];
|
|
13653
13958
|
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
@@ -13679,7 +13984,7 @@ function readRulesCacheResilient(cacheFile) {
|
|
|
13679
13984
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
13680
13985
|
let content;
|
|
13681
13986
|
try {
|
|
13682
|
-
content =
|
|
13987
|
+
content = fs5.readFileSync(cacheFile, "utf-8");
|
|
13683
13988
|
existed = true;
|
|
13684
13989
|
} catch (err2) {
|
|
13685
13990
|
if (err2.code === "ENOENT") return {};
|
|
@@ -13694,10 +13999,10 @@ function readRulesCacheResilient(cacheFile) {
|
|
|
13694
13999
|
}
|
|
13695
14000
|
}
|
|
13696
14001
|
if (existed || sawReadError) {
|
|
13697
|
-
const backup =
|
|
14002
|
+
const backup = path5.join(path5.dirname(cacheFile), "rules-cache.last-good.json");
|
|
13698
14003
|
if (backup !== cacheFile) {
|
|
13699
14004
|
try {
|
|
13700
|
-
const raw = JSON.parse(
|
|
14005
|
+
const raw = JSON.parse(fs5.readFileSync(backup, "utf-8"));
|
|
13701
14006
|
logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
|
|
13702
14007
|
lastParsedRulesCache = raw;
|
|
13703
14008
|
return raw;
|
|
@@ -13717,8 +14022,8 @@ function logCacheReadIssue(cacheFile, kind) {
|
|
|
13717
14022
|
if (now - cacheReadLastLoggedAt < CACHE_LOG_REARM_MS) return;
|
|
13718
14023
|
cacheReadLastLoggedAt = now;
|
|
13719
14024
|
try {
|
|
13720
|
-
|
|
13721
|
-
|
|
14025
|
+
fs5.appendFileSync(
|
|
14026
|
+
path5.join(os5.homedir(), ".node9", "hook-debug.log"),
|
|
13722
14027
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
|
|
13723
14028
|
`
|
|
13724
14029
|
);
|
|
@@ -13730,8 +14035,8 @@ function catalogSettingsFrom(settings, policy) {
|
|
|
13730
14035
|
}
|
|
13731
14036
|
function getConfig(cwd) {
|
|
13732
14037
|
if (!cwd && cachedConfig) return cachedConfig;
|
|
13733
|
-
const globalPath =
|
|
13734
|
-
const projectPath =
|
|
14038
|
+
const globalPath = path5.join(os5.homedir(), ".node9", "config.json");
|
|
14039
|
+
const projectPath = path5.join(cwd ?? process.cwd(), "node9.config.json");
|
|
13735
14040
|
const globalConfig = tryLoadConfig(globalPath);
|
|
13736
14041
|
const projectConfig = tryLoadConfig(projectPath, { project: true });
|
|
13737
14042
|
const mergedSettings = {
|
|
@@ -13744,6 +14049,7 @@ function getConfig(cwd) {
|
|
|
13744
14049
|
dangerousWords: [...DEFAULT_CONFIG.policy.dangerousWords],
|
|
13745
14050
|
ignoredTools: [...DEFAULT_CONFIG.policy.ignoredTools],
|
|
13746
14051
|
toolInspection: { ...DEFAULT_CONFIG.policy.toolInspection },
|
|
14052
|
+
projectToolInspection: {},
|
|
13747
14053
|
// Shipped rules carry `builtin`: the catalog's per-check value governs
|
|
13748
14054
|
// them and only them (a user rule that reuses a name keeps its verdict).
|
|
13749
14055
|
smartRules: DEFAULT_CONFIG.policy.smartRules.map((r) => ({ ...r, builtin: true })),
|
|
@@ -13770,7 +14076,8 @@ function getConfig(cwd) {
|
|
|
13770
14076
|
untrustedContent: {
|
|
13771
14077
|
...DEFAULT_CONFIG.policy.untrustedContent,
|
|
13772
14078
|
orgDomains: [...DEFAULT_CONFIG.policy.untrustedContent.orgDomains],
|
|
13773
|
-
roles: {}
|
|
14079
|
+
roles: {},
|
|
14080
|
+
trustedRecipients: []
|
|
13774
14081
|
},
|
|
13775
14082
|
// Left empty on purpose: the local file is read fresh at policy-eval time
|
|
13776
14083
|
// (getCachedHosts via isTrustedHost), NOT snapshotted into the frozen config
|
|
@@ -13816,8 +14123,14 @@ function getConfig(cwd) {
|
|
|
13816
14123
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
13817
14124
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
13818
14125
|
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
13819
|
-
if (p.toolInspection)
|
|
13820
|
-
|
|
14126
|
+
if (p.toolInspection) {
|
|
14127
|
+
if (isProject)
|
|
14128
|
+
mergedPolicy.projectToolInspection = {
|
|
14129
|
+
...mergedPolicy.projectToolInspection,
|
|
14130
|
+
...p.toolInspection
|
|
14131
|
+
};
|
|
14132
|
+
else mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
|
|
14133
|
+
}
|
|
13821
14134
|
if (p.smartRules) {
|
|
13822
14135
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
13823
14136
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
@@ -13965,7 +14278,7 @@ function getConfig(cwd) {
|
|
|
13965
14278
|
let cloudMandatesEnforcement = false;
|
|
13966
14279
|
let cloudMandatesAppPerm = false;
|
|
13967
14280
|
{
|
|
13968
|
-
const cacheFile =
|
|
14281
|
+
const cacheFile = path5.join(os5.homedir(), ".node9", "rules-cache.json");
|
|
13969
14282
|
try {
|
|
13970
14283
|
const raw = readRulesCacheResilient(cacheFile);
|
|
13971
14284
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
@@ -14358,10 +14671,10 @@ function getConfig(cwd) {
|
|
|
14358
14671
|
}
|
|
14359
14672
|
function tryLoadConfig(filePath, options = {}) {
|
|
14360
14673
|
v2ChecksByPath.delete(filePath);
|
|
14361
|
-
if (!
|
|
14674
|
+
if (!fs5.existsSync(filePath)) return null;
|
|
14362
14675
|
let raw;
|
|
14363
14676
|
try {
|
|
14364
|
-
raw = JSON.parse(
|
|
14677
|
+
raw = JSON.parse(fs5.readFileSync(filePath, "utf-8"));
|
|
14365
14678
|
} catch (err2) {
|
|
14366
14679
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
14367
14680
|
process.stderr.write(
|
|
@@ -14436,6 +14749,13 @@ function applyUntrustedContent(cur, raw, replaceLists = false, isProject = false
|
|
|
14436
14749
|
if (replaceLists) cur.orgDomains = domains;
|
|
14437
14750
|
else for (const d of domains) if (!cur.orgDomains.includes(d)) cur.orgDomains.push(d);
|
|
14438
14751
|
}
|
|
14752
|
+
if (replaceLists && Array.isArray(uc.trustedRecipients)) {
|
|
14753
|
+
cur.trustedRecipients = [
|
|
14754
|
+
...new Set(
|
|
14755
|
+
uc.trustedRecipients.filter((v) => typeof v === "string").map((v) => normalizeRecipient(v)).filter((v) => v !== null)
|
|
14756
|
+
)
|
|
14757
|
+
].slice(0, 200);
|
|
14758
|
+
}
|
|
14439
14759
|
if (uc.roles && typeof uc.roles === "object" && !Array.isArray(uc.roles)) {
|
|
14440
14760
|
const next = replaceLists ? {} : cur.roles;
|
|
14441
14761
|
for (const [server, tools] of Object.entries(uc.roles)) {
|
|
@@ -14456,6 +14776,7 @@ var DANGEROUS_WORDS, RUNTIME_ONLY_CONFIG_KEYS, DEFAULT_CONFIG, RM_SAFE_PATH_PATT
|
|
|
14456
14776
|
var init_config = __esm({
|
|
14457
14777
|
"src/config/index.ts"() {
|
|
14458
14778
|
"use strict";
|
|
14779
|
+
init_trusted_recipients();
|
|
14459
14780
|
init_config_schema();
|
|
14460
14781
|
init_shields();
|
|
14461
14782
|
init_managed();
|
|
@@ -14710,7 +15031,8 @@ var init_config = __esm({
|
|
|
14710
15031
|
spotlight: true,
|
|
14711
15032
|
afterExposure: "ask-once",
|
|
14712
15033
|
orgDomains: [],
|
|
14713
|
-
roles: {}
|
|
15034
|
+
roles: {},
|
|
15035
|
+
trustedRecipients: []
|
|
14714
15036
|
},
|
|
14715
15037
|
trustedHosts: [],
|
|
14716
15038
|
trustedHostsManaged: false,
|
|
@@ -14803,18 +15125,18 @@ var init_regex = __esm({
|
|
|
14803
15125
|
});
|
|
14804
15126
|
|
|
14805
15127
|
// src/dlp.ts
|
|
14806
|
-
import
|
|
14807
|
-
import
|
|
15128
|
+
import fs6 from "fs";
|
|
15129
|
+
import path6 from "path";
|
|
14808
15130
|
function scanFilePath(filePath, cwd = process.cwd(), options = {}) {
|
|
14809
15131
|
if (!filePath) return null;
|
|
14810
15132
|
let resolved;
|
|
14811
15133
|
try {
|
|
14812
|
-
const absolute =
|
|
14813
|
-
resolved =
|
|
15134
|
+
const absolute = path6.resolve(cwd, filePath);
|
|
15135
|
+
resolved = fs6.realpathSync.native(absolute);
|
|
14814
15136
|
} catch (err2) {
|
|
14815
15137
|
const code2 = err2.code;
|
|
14816
15138
|
if (code2 === "ENOENT" || code2 === "ENOTDIR") {
|
|
14817
|
-
resolved =
|
|
15139
|
+
resolved = path6.resolve(cwd, filePath);
|
|
14818
15140
|
} else {
|
|
14819
15141
|
return sensitivePathMatch(filePath);
|
|
14820
15142
|
}
|
|
@@ -14830,29 +15152,29 @@ var init_dlp = __esm({
|
|
|
14830
15152
|
});
|
|
14831
15153
|
|
|
14832
15154
|
// src/policy/file-access.ts
|
|
14833
|
-
import
|
|
14834
|
-
import
|
|
14835
|
-
import
|
|
15155
|
+
import fs7 from "fs";
|
|
15156
|
+
import os6 from "os";
|
|
15157
|
+
import path7 from "path";
|
|
14836
15158
|
function absoluteSpelling(p, cwd, home) {
|
|
14837
15159
|
if (p === "~" || p.startsWith("~/")) p = home + p.slice(1);
|
|
14838
|
-
return
|
|
15160
|
+
return path7.isAbsolute(p) ? p : `${cwd}/${p}`;
|
|
14839
15161
|
}
|
|
14840
15162
|
function physical(p, followFinal, links = 0) {
|
|
14841
15163
|
if (links > 40) throw new Error("Too many symbolic links");
|
|
14842
15164
|
if (!followFinal) {
|
|
14843
|
-
const parent = physical(
|
|
14844
|
-
return { path: `${parent.path}/${
|
|
15165
|
+
const parent = physical(path7.dirname(p), true, links);
|
|
15166
|
+
return { path: `${parent.path}/${path7.basename(p)}`, missing: parent.missing };
|
|
14845
15167
|
}
|
|
14846
15168
|
try {
|
|
14847
|
-
return { path:
|
|
15169
|
+
return { path: fs7.realpathSync.native(p), missing: false };
|
|
14848
15170
|
} catch (error) {
|
|
14849
15171
|
const code2 = error.code;
|
|
14850
15172
|
if (!["ENOENT", "ENOTDIR"].includes(code2 ?? "")) throw error;
|
|
14851
15173
|
try {
|
|
14852
|
-
if (
|
|
14853
|
-
const target =
|
|
15174
|
+
if (fs7.lstatSync(p).isSymbolicLink()) {
|
|
15175
|
+
const target = fs7.readlinkSync(p);
|
|
14854
15176
|
return physical(
|
|
14855
|
-
|
|
15177
|
+
path7.isAbsolute(target) ? target : `${path7.dirname(p)}/${target}`,
|
|
14856
15178
|
true,
|
|
14857
15179
|
links + 1
|
|
14858
15180
|
);
|
|
@@ -14861,13 +15183,13 @@ function physical(p, followFinal, links = 0) {
|
|
|
14861
15183
|
if (!["ENOENT", "ENOTDIR"].includes(linkError.code ?? ""))
|
|
14862
15184
|
throw linkError;
|
|
14863
15185
|
}
|
|
14864
|
-
const parent =
|
|
15186
|
+
const parent = path7.dirname(p);
|
|
14865
15187
|
if (parent === p) return { path: p, missing: true };
|
|
14866
15188
|
const resolved = physical(parent, true, links);
|
|
14867
|
-
return { path: `${resolved.path}/${
|
|
15189
|
+
return { path: `${resolved.path}/${path7.basename(p)}`, missing: true };
|
|
14868
15190
|
}
|
|
14869
15191
|
}
|
|
14870
|
-
function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, home =
|
|
15192
|
+
function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, home = os6.homedir()) {
|
|
14871
15193
|
const context = { cwd, homeDirs: [home, "/root"] };
|
|
14872
15194
|
const accesses = collectFileAccesses(tool, args, context, inspection);
|
|
14873
15195
|
const handledPaths = /* @__PURE__ */ new Set();
|
|
@@ -14890,14 +15212,14 @@ function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, h
|
|
|
14890
15212
|
const raw = absoluteSpelling(access.path, access.cwd ?? cwd, home);
|
|
14891
15213
|
if (access.directoryTarget) {
|
|
14892
15214
|
try {
|
|
14893
|
-
if (!
|
|
15215
|
+
if (!fs7.statSync(absoluteSpelling(access.directoryTarget, access.cwd ?? cwd, home)).isDirectory())
|
|
14894
15216
|
continue;
|
|
14895
15217
|
} catch {
|
|
14896
15218
|
}
|
|
14897
15219
|
}
|
|
14898
15220
|
if (access.operation === "create") {
|
|
14899
15221
|
try {
|
|
14900
|
-
|
|
15222
|
+
fs7.statSync(raw);
|
|
14901
15223
|
continue;
|
|
14902
15224
|
} catch {
|
|
14903
15225
|
}
|
|
@@ -14952,17 +15274,17 @@ var init_file_access = __esm({
|
|
|
14952
15274
|
});
|
|
14953
15275
|
|
|
14954
15276
|
// src/utils/provenance.ts
|
|
14955
|
-
import
|
|
14956
|
-
import
|
|
14957
|
-
import
|
|
15277
|
+
import fs8 from "fs";
|
|
15278
|
+
import path8 from "path";
|
|
15279
|
+
import os7 from "os";
|
|
14958
15280
|
function findInPath(cmd) {
|
|
14959
|
-
if (
|
|
15281
|
+
if (path8.posix.isAbsolute(cmd)) return cmd;
|
|
14960
15282
|
const pathEnv = process.env.PATH ?? "";
|
|
14961
|
-
for (const dir2 of pathEnv.split(
|
|
15283
|
+
for (const dir2 of pathEnv.split(path8.delimiter)) {
|
|
14962
15284
|
if (!dir2) continue;
|
|
14963
|
-
const full =
|
|
15285
|
+
const full = path8.join(dir2, cmd);
|
|
14964
15286
|
try {
|
|
14965
|
-
|
|
15287
|
+
fs8.accessSync(full, fs8.constants.X_OK);
|
|
14966
15288
|
return full;
|
|
14967
15289
|
} catch {
|
|
14968
15290
|
}
|
|
@@ -14973,7 +15295,7 @@ function _classifyPath(resolved, cwd) {
|
|
|
14973
15295
|
if (cwd && resolved.startsWith(cwd + "/")) {
|
|
14974
15296
|
return { trustLevel: "user", reason: "binary in project directory" };
|
|
14975
15297
|
}
|
|
14976
|
-
const osTmp =
|
|
15298
|
+
const osTmp = os7.tmpdir();
|
|
14977
15299
|
const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
|
|
14978
15300
|
if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
14979
15301
|
return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
|
|
@@ -14991,7 +15313,7 @@ function _classifyPath(resolved, cwd) {
|
|
|
14991
15313
|
}
|
|
14992
15314
|
function checkProvenance(cmd, cwd) {
|
|
14993
15315
|
const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
|
|
14994
|
-
if (
|
|
15316
|
+
if (path8.posix.isAbsolute(bare)) {
|
|
14995
15317
|
const early = _classifyPath(bare, cwd);
|
|
14996
15318
|
if (early.trustLevel === "suspect") {
|
|
14997
15319
|
return { resolvedPath: bare, ...early };
|
|
@@ -15007,7 +15329,7 @@ function checkProvenance(cmd, cwd) {
|
|
|
15007
15329
|
reason: "binary not found in PATH"
|
|
15008
15330
|
};
|
|
15009
15331
|
}
|
|
15010
|
-
resolved =
|
|
15332
|
+
resolved = fs8.realpathSync(found);
|
|
15011
15333
|
} catch {
|
|
15012
15334
|
return {
|
|
15013
15335
|
resolvedPath: cmd,
|
|
@@ -15016,7 +15338,7 @@ function checkProvenance(cmd, cwd) {
|
|
|
15016
15338
|
};
|
|
15017
15339
|
}
|
|
15018
15340
|
try {
|
|
15019
|
-
const stat =
|
|
15341
|
+
const stat = fs8.statSync(resolved);
|
|
15020
15342
|
if (stat.mode & 2) {
|
|
15021
15343
|
return {
|
|
15022
15344
|
resolvedPath: resolved,
|
|
@@ -15041,23 +15363,23 @@ var init_provenance = __esm({
|
|
|
15041
15363
|
SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
|
|
15042
15364
|
MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
|
|
15043
15365
|
USER_PREFIXES = [
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15366
|
+
path8.join(os7.homedir(), "bin"),
|
|
15367
|
+
path8.join(os7.homedir(), ".local", "bin"),
|
|
15368
|
+
path8.join(os7.homedir(), ".cargo", "bin"),
|
|
15369
|
+
path8.join(os7.homedir(), ".npm-global", "bin"),
|
|
15370
|
+
path8.join(os7.homedir(), ".volta", "bin")
|
|
15049
15371
|
];
|
|
15050
15372
|
SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
|
|
15051
15373
|
}
|
|
15052
15374
|
});
|
|
15053
15375
|
|
|
15054
15376
|
// src/policy/index.ts
|
|
15055
|
-
import
|
|
15056
|
-
import
|
|
15057
|
-
import
|
|
15377
|
+
import fs9 from "fs";
|
|
15378
|
+
import path9 from "path";
|
|
15379
|
+
import os8 from "os";
|
|
15058
15380
|
async function evaluatePolicy2(toolName, args, agent, cwd, opts) {
|
|
15059
15381
|
const config = opts?.config ?? getConfig(cwd);
|
|
15060
|
-
const prepared = opts?.fileAccess ?? prepareFileAccesses(toolName, args, cwd, config.policy
|
|
15382
|
+
const prepared = opts?.fileAccess ?? prepareFileAccesses(toolName, args, cwd, inspectionLayers(config.policy));
|
|
15061
15383
|
const activeEnvironment = getActiveEnvironment(config) ?? void 0;
|
|
15062
15384
|
return evaluatePolicy(
|
|
15063
15385
|
config,
|
|
@@ -15089,8 +15411,11 @@ function tokenize3(toolName) {
|
|
|
15089
15411
|
}
|
|
15090
15412
|
function explainExtractShellCommand(toolName, args, toolInspection) {
|
|
15091
15413
|
const shell = resolveShellInput(toolName, args, toolInspection);
|
|
15092
|
-
const matchingPattern =
|
|
15093
|
-
|
|
15414
|
+
const matchingPattern = [
|
|
15415
|
+
...Object.keys(toolInspection.trusted),
|
|
15416
|
+
...Object.keys(toolInspection.project ?? {})
|
|
15417
|
+
].find((p) => matchesPattern(toolName, p));
|
|
15418
|
+
const entry = inspectionEntry(toolName, toolInspection);
|
|
15094
15419
|
const legacyValue = shell.kind === "not-shell" && typeof entry === "string" ? getNestedValue(args, entry) : null;
|
|
15095
15420
|
return {
|
|
15096
15421
|
command: shell.kind === "shell" ? shell.command : typeof legacyValue === "string" ? legacyValue : null,
|
|
@@ -15099,15 +15424,17 @@ function explainExtractShellCommand(toolName, args, toolInspection) {
|
|
|
15099
15424
|
};
|
|
15100
15425
|
}
|
|
15101
15426
|
function explainIsSqlTool(toolName, toolInspection) {
|
|
15102
|
-
const
|
|
15103
|
-
const matchingPattern = patterns.find((p) => matchesPattern(toolName, p));
|
|
15104
|
-
if (!matchingPattern) return false;
|
|
15105
|
-
const fieldName = toolInspection[matchingPattern];
|
|
15427
|
+
const fieldName = inspectionEntry(toolName, toolInspection);
|
|
15106
15428
|
return fieldName === "sql" || fieldName === "query";
|
|
15107
15429
|
}
|
|
15108
15430
|
async function explainPolicy(toolName, args, agent = EXPLAIN_AGENT) {
|
|
15109
15431
|
const config = getConfig();
|
|
15110
|
-
const prepared = prepareFileAccesses(
|
|
15432
|
+
const prepared = prepareFileAccesses(
|
|
15433
|
+
toolName,
|
|
15434
|
+
args,
|
|
15435
|
+
process.cwd(),
|
|
15436
|
+
inspectionLayers(config.policy)
|
|
15437
|
+
);
|
|
15111
15438
|
const derived = await deriveExplainTrace(toolName, args, prepared);
|
|
15112
15439
|
const engine = await evaluatePolicy2(toolName, args, agent, prepared.cwd, {
|
|
15113
15440
|
fileAccess: prepared,
|
|
@@ -15136,10 +15463,11 @@ async function explainPolicy(toolName, args, agent = EXPLAIN_AGENT) {
|
|
|
15136
15463
|
}
|
|
15137
15464
|
async function deriveExplainTrace(toolName, args, prepared) {
|
|
15138
15465
|
const steps = [];
|
|
15139
|
-
const globalPath =
|
|
15140
|
-
const projectPath =
|
|
15141
|
-
const credsPath =
|
|
15466
|
+
const globalPath = path9.join(os8.homedir(), ".node9", "config.json");
|
|
15467
|
+
const projectPath = path9.join(process.cwd(), "node9.config.json");
|
|
15468
|
+
const credsPath = path9.join(os8.homedir(), ".node9", "credentials.json");
|
|
15142
15469
|
const config = getConfig();
|
|
15470
|
+
const layers = inspectionLayers(config.policy);
|
|
15143
15471
|
const wsGoverned = config.policySource === "workspace";
|
|
15144
15472
|
const waterfall = [
|
|
15145
15473
|
{
|
|
@@ -15151,22 +15479,22 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15151
15479
|
{
|
|
15152
15480
|
tier: 2,
|
|
15153
15481
|
label: wsGoverned ? "Workspace config" : "Cloud policy",
|
|
15154
|
-
status:
|
|
15155
|
-
note:
|
|
15482
|
+
status: fs9.existsSync(credsPath) ? "active" : "missing",
|
|
15483
|
+
note: fs9.existsSync(credsPath) ? wsGoverned ? "authoritative \u2014 this machine follows the workspace configuration (app.node9.ai)" : "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
|
|
15156
15484
|
},
|
|
15157
15485
|
{
|
|
15158
15486
|
tier: 3,
|
|
15159
15487
|
label: "Project config",
|
|
15160
|
-
status:
|
|
15488
|
+
status: fs9.existsSync(projectPath) ? "active" : "missing",
|
|
15161
15489
|
path: projectPath,
|
|
15162
|
-
...wsGoverned &&
|
|
15490
|
+
...wsGoverned && fs9.existsSync(projectPath) ? { note: "present \u2014 ignored (workspace config governs policy)" } : {}
|
|
15163
15491
|
},
|
|
15164
15492
|
{
|
|
15165
15493
|
tier: 4,
|
|
15166
15494
|
label: "Global config",
|
|
15167
|
-
status:
|
|
15495
|
+
status: fs9.existsSync(globalPath) ? "active" : "missing",
|
|
15168
15496
|
path: globalPath,
|
|
15169
|
-
...wsGoverned &&
|
|
15497
|
+
...wsGoverned && fs9.existsSync(globalPath) ? { note: "present \u2014 ignored (workspace config governs policy)" } : {}
|
|
15170
15498
|
},
|
|
15171
15499
|
{
|
|
15172
15500
|
tier: 5,
|
|
@@ -15217,10 +15545,7 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15217
15545
|
});
|
|
15218
15546
|
if (config.policy.smartRules.length > 0) {
|
|
15219
15547
|
const matchedRule = config.policy.smartRules.find(
|
|
15220
|
-
(rule) => toolMatchesRule(toolName, rule.tool,
|
|
15221
|
-
shellRuleArgs(toolName, args, rule.tool, config.policy.toolInspection),
|
|
15222
|
-
rule
|
|
15223
|
-
)
|
|
15548
|
+
(rule) => toolMatchesRule(toolName, rule.tool, layers) && evaluateSmartConditions(shellRuleArgs(toolName, args, rule.tool, layers), rule)
|
|
15224
15549
|
);
|
|
15225
15550
|
if (matchedRule) {
|
|
15226
15551
|
const label3 = `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`;
|
|
@@ -15262,7 +15587,7 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15262
15587
|
command: shellCommand,
|
|
15263
15588
|
matchingPattern,
|
|
15264
15589
|
fieldName
|
|
15265
|
-
} = explainExtractShellCommand(toolName, args,
|
|
15590
|
+
} = explainExtractShellCommand(toolName, args, layers);
|
|
15266
15591
|
if (shellCommand) {
|
|
15267
15592
|
const analyzed = analyzeShellCommand(shellCommand);
|
|
15268
15593
|
allTokens = analyzed.allTokens;
|
|
@@ -15313,7 +15638,7 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15313
15638
|
outcome: "checked",
|
|
15314
15639
|
detail: "No dangerous eval detected"
|
|
15315
15640
|
});
|
|
15316
|
-
if (explainIsSqlTool(toolName,
|
|
15641
|
+
if (explainIsSqlTool(toolName, layers)) {
|
|
15317
15642
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS2.has(t.toLowerCase()));
|
|
15318
15643
|
steps.push({
|
|
15319
15644
|
name: "SQL token stripping",
|
|
@@ -15442,27 +15767,27 @@ var init_policy = __esm({
|
|
|
15442
15767
|
});
|
|
15443
15768
|
|
|
15444
15769
|
// src/utils/atomic-write.ts
|
|
15445
|
-
import
|
|
15446
|
-
import
|
|
15770
|
+
import fs10 from "fs";
|
|
15771
|
+
import path10 from "path";
|
|
15447
15772
|
import { randomUUID } from "crypto";
|
|
15448
15773
|
function atomicWriteSync(filePath, data, options) {
|
|
15449
|
-
const dir2 =
|
|
15450
|
-
if (!
|
|
15774
|
+
const dir2 = path10.dirname(filePath);
|
|
15775
|
+
if (!fs10.existsSync(dir2)) fs10.mkdirSync(dir2, { recursive: true });
|
|
15451
15776
|
const tmpPath = `${filePath}.${randomUUID()}.tmp`;
|
|
15452
15777
|
try {
|
|
15453
|
-
|
|
15778
|
+
fs10.writeFileSync(tmpPath, data, options);
|
|
15454
15779
|
} catch (err2) {
|
|
15455
15780
|
try {
|
|
15456
|
-
|
|
15781
|
+
fs10.unlinkSync(tmpPath);
|
|
15457
15782
|
} catch {
|
|
15458
15783
|
}
|
|
15459
15784
|
throw err2;
|
|
15460
15785
|
}
|
|
15461
15786
|
try {
|
|
15462
|
-
|
|
15787
|
+
fs10.renameSync(tmpPath, filePath);
|
|
15463
15788
|
} catch (err2) {
|
|
15464
15789
|
try {
|
|
15465
|
-
|
|
15790
|
+
fs10.unlinkSync(tmpPath);
|
|
15466
15791
|
} catch {
|
|
15467
15792
|
}
|
|
15468
15793
|
throw err2;
|
|
@@ -15475,9 +15800,9 @@ var init_atomic_write = __esm({
|
|
|
15475
15800
|
});
|
|
15476
15801
|
|
|
15477
15802
|
// src/auth/state.ts
|
|
15478
|
-
import
|
|
15479
|
-
import
|
|
15480
|
-
import
|
|
15803
|
+
import fs11 from "fs";
|
|
15804
|
+
import path11 from "path";
|
|
15805
|
+
import os9 from "os";
|
|
15481
15806
|
function extractCommandPattern(toolName, args) {
|
|
15482
15807
|
const lower2 = toolName.toLowerCase();
|
|
15483
15808
|
if (lower2 !== "bash" && lower2 !== "execute_bash" && lower2 !== "shell") return void 0;
|
|
@@ -15489,11 +15814,11 @@ function extractCommandPattern(toolName, args) {
|
|
|
15489
15814
|
}
|
|
15490
15815
|
function checkPause() {
|
|
15491
15816
|
try {
|
|
15492
|
-
if (!
|
|
15493
|
-
const state = JSON.parse(
|
|
15817
|
+
if (!fs11.existsSync(PAUSED_FILE)) return { paused: false };
|
|
15818
|
+
const state = JSON.parse(fs11.readFileSync(PAUSED_FILE, "utf-8"));
|
|
15494
15819
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
15495
15820
|
try {
|
|
15496
|
-
|
|
15821
|
+
fs11.unlinkSync(PAUSED_FILE);
|
|
15497
15822
|
} catch {
|
|
15498
15823
|
}
|
|
15499
15824
|
return { paused: false };
|
|
@@ -15509,14 +15834,14 @@ function pauseNode9(durationMs, durationStr) {
|
|
|
15509
15834
|
}
|
|
15510
15835
|
function resumeNode9() {
|
|
15511
15836
|
try {
|
|
15512
|
-
if (
|
|
15837
|
+
if (fs11.existsSync(PAUSED_FILE)) fs11.unlinkSync(PAUSED_FILE);
|
|
15513
15838
|
} catch {
|
|
15514
15839
|
}
|
|
15515
15840
|
}
|
|
15516
15841
|
function getActiveTrustSession(toolName, args) {
|
|
15517
15842
|
try {
|
|
15518
|
-
if (!
|
|
15519
|
-
const trust = JSON.parse(
|
|
15843
|
+
if (!fs11.existsSync(TRUST_FILE)) return false;
|
|
15844
|
+
const trust = JSON.parse(fs11.readFileSync(TRUST_FILE, "utf-8"));
|
|
15520
15845
|
const now = Date.now();
|
|
15521
15846
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
15522
15847
|
if (active.length !== trust.entries.length) {
|
|
@@ -15539,8 +15864,8 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
15539
15864
|
try {
|
|
15540
15865
|
let trust = { entries: [] };
|
|
15541
15866
|
try {
|
|
15542
|
-
if (
|
|
15543
|
-
trust = JSON.parse(
|
|
15867
|
+
if (fs11.existsSync(TRUST_FILE)) {
|
|
15868
|
+
trust = JSON.parse(fs11.readFileSync(TRUST_FILE, "utf-8"));
|
|
15544
15869
|
}
|
|
15545
15870
|
} catch {
|
|
15546
15871
|
}
|
|
@@ -15562,9 +15887,9 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
15562
15887
|
}
|
|
15563
15888
|
function getPersistentDecision(toolName) {
|
|
15564
15889
|
try {
|
|
15565
|
-
const file2 =
|
|
15566
|
-
if (!
|
|
15567
|
-
const decisions = JSON.parse(
|
|
15890
|
+
const file2 = path11.join(os9.homedir(), ".node9", "decisions.json");
|
|
15891
|
+
if (!fs11.existsSync(file2)) return null;
|
|
15892
|
+
const decisions = JSON.parse(fs11.readFileSync(file2, "utf-8"));
|
|
15568
15893
|
const d = decisions[toolName];
|
|
15569
15894
|
if (d === "allow" || d === "deny") return d;
|
|
15570
15895
|
} catch {
|
|
@@ -15577,16 +15902,16 @@ var init_state = __esm({
|
|
|
15577
15902
|
"use strict";
|
|
15578
15903
|
init_policy();
|
|
15579
15904
|
init_atomic_write();
|
|
15580
|
-
PAUSED_FILE =
|
|
15581
|
-
TRUST_FILE =
|
|
15905
|
+
PAUSED_FILE = path11.join(os9.homedir(), ".node9", "PAUSED");
|
|
15906
|
+
TRUST_FILE = path11.join(os9.homedir(), ".node9", "trust.json");
|
|
15582
15907
|
}
|
|
15583
15908
|
});
|
|
15584
15909
|
|
|
15585
15910
|
// src/auth/daemon.ts
|
|
15586
|
-
import
|
|
15911
|
+
import fs12 from "fs";
|
|
15587
15912
|
import net from "net";
|
|
15588
|
-
import
|
|
15589
|
-
import
|
|
15913
|
+
import path12 from "path";
|
|
15914
|
+
import os10 from "os";
|
|
15590
15915
|
function notifyActivitySocket(data) {
|
|
15591
15916
|
return new Promise((resolve2) => {
|
|
15592
15917
|
try {
|
|
@@ -15625,9 +15950,9 @@ function resolveDaemonPort(env = process.env) {
|
|
|
15625
15950
|
}
|
|
15626
15951
|
function getInternalToken() {
|
|
15627
15952
|
try {
|
|
15628
|
-
const pidFile =
|
|
15629
|
-
if (!
|
|
15630
|
-
const data = JSON.parse(
|
|
15953
|
+
const pidFile = path12.join(os10.homedir(), ".node9", "daemon.pid");
|
|
15954
|
+
if (!fs12.existsSync(pidFile)) return null;
|
|
15955
|
+
const data = JSON.parse(fs12.readFileSync(pidFile, "utf-8"));
|
|
15631
15956
|
process.kill(data.pid, 0);
|
|
15632
15957
|
return data.internalToken ?? null;
|
|
15633
15958
|
} catch {
|
|
@@ -15635,12 +15960,12 @@ function getInternalToken() {
|
|
|
15635
15960
|
}
|
|
15636
15961
|
}
|
|
15637
15962
|
function isDaemonRunning() {
|
|
15638
|
-
const pidFile =
|
|
15639
|
-
if (
|
|
15963
|
+
const pidFile = path12.join(os10.homedir(), ".node9", "daemon.pid");
|
|
15964
|
+
if (fs12.existsSync(pidFile)) {
|
|
15640
15965
|
let pid;
|
|
15641
15966
|
let port;
|
|
15642
15967
|
try {
|
|
15643
|
-
const data = JSON.parse(
|
|
15968
|
+
const data = JSON.parse(fs12.readFileSync(pidFile, "utf-8"));
|
|
15644
15969
|
pid = data.pid;
|
|
15645
15970
|
port = data.port;
|
|
15646
15971
|
} catch {
|
|
@@ -15658,7 +15983,7 @@ function isDaemonRunning() {
|
|
|
15658
15983
|
} catch (err2) {
|
|
15659
15984
|
if (err2 instanceof Error && "code" in err2 && err2.code === "ESRCH") {
|
|
15660
15985
|
try {
|
|
15661
|
-
|
|
15986
|
+
fs12.unlinkSync(pidFile);
|
|
15662
15987
|
} catch {
|
|
15663
15988
|
}
|
|
15664
15989
|
}
|
|
@@ -15976,7 +16301,7 @@ var ACTIVITY_SOCKET_PATH, DEFAULT_DAEMON_PORT, DAEMON_PORT, DAEMON_HOST;
|
|
|
15976
16301
|
var init_daemon = __esm({
|
|
15977
16302
|
"src/auth/daemon.ts"() {
|
|
15978
16303
|
"use strict";
|
|
15979
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
16304
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path12.join(os10.tmpdir(), "node9-activity.sock");
|
|
15980
16305
|
DEFAULT_DAEMON_PORT = 7391;
|
|
15981
16306
|
DAEMON_PORT = resolveDaemonPort();
|
|
15982
16307
|
DAEMON_HOST = "127.0.0.1";
|
|
@@ -15984,7 +16309,7 @@ var init_daemon = __esm({
|
|
|
15984
16309
|
});
|
|
15985
16310
|
|
|
15986
16311
|
// src/context-sniper.ts
|
|
15987
|
-
import
|
|
16312
|
+
import path13 from "path";
|
|
15988
16313
|
function smartTruncate(str2, maxLen = 500) {
|
|
15989
16314
|
if (str2.length <= maxLen) return str2;
|
|
15990
16315
|
const edge = Math.floor(maxLen / 2) - 3;
|
|
@@ -16036,7 +16361,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
16036
16361
|
intent = "EDIT";
|
|
16037
16362
|
if (obj.file_path) {
|
|
16038
16363
|
editFilePath = String(obj.file_path);
|
|
16039
|
-
editFileName =
|
|
16364
|
+
editFileName = path13.basename(editFilePath);
|
|
16040
16365
|
}
|
|
16041
16366
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
16042
16367
|
contextSnippet = result.snippet;
|
|
@@ -16101,7 +16426,7 @@ var init_safe_text = __esm({
|
|
|
16101
16426
|
|
|
16102
16427
|
// src/ui/native.ts
|
|
16103
16428
|
import { spawn } from "child_process";
|
|
16104
|
-
import
|
|
16429
|
+
import path14 from "path";
|
|
16105
16430
|
function resolveNativeDecision(opts) {
|
|
16106
16431
|
const { code: code2, output, elapsedMs, locked } = opts;
|
|
16107
16432
|
if (locked) return "deny";
|
|
@@ -16129,7 +16454,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
16129
16454
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
16130
16455
|
const obj = parsed;
|
|
16131
16456
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
16132
|
-
const file2 = obj.file_path ?
|
|
16457
|
+
const file2 = obj.file_path ? path14.basename(String(obj.file_path)) : "file";
|
|
16133
16458
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
16134
16459
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
16135
16460
|
return {
|
|
@@ -16360,16 +16685,16 @@ var init_native = __esm({
|
|
|
16360
16685
|
});
|
|
16361
16686
|
|
|
16362
16687
|
// src/shields/jail.ts
|
|
16363
|
-
import
|
|
16364
|
-
import
|
|
16365
|
-
import
|
|
16688
|
+
import fs13 from "fs";
|
|
16689
|
+
import os11 from "os";
|
|
16690
|
+
import path15 from "path";
|
|
16366
16691
|
function jailStorePath() {
|
|
16367
|
-
return
|
|
16692
|
+
return path15.join(os11.homedir(), ".node9", "jail-paths.json");
|
|
16368
16693
|
}
|
|
16369
16694
|
function readJailPaths() {
|
|
16370
16695
|
let text;
|
|
16371
16696
|
try {
|
|
16372
|
-
text =
|
|
16697
|
+
text = fs13.readFileSync(jailStorePath(), "utf8");
|
|
16373
16698
|
} catch (err2) {
|
|
16374
16699
|
if (err2.code === "ENOENT") return [];
|
|
16375
16700
|
throw err2;
|
|
@@ -16387,8 +16712,8 @@ function readJailPaths() {
|
|
|
16387
16712
|
}
|
|
16388
16713
|
function writeJailPaths(paths) {
|
|
16389
16714
|
const p = jailStorePath();
|
|
16390
|
-
|
|
16391
|
-
|
|
16715
|
+
fs13.mkdirSync(path15.dirname(p), { recursive: true });
|
|
16716
|
+
fs13.writeFileSync(p, JSON.stringify({ paths }, null, 2) + "\n", { mode: 384 });
|
|
16392
16717
|
}
|
|
16393
16718
|
function addJailPath(rawPath, verdict) {
|
|
16394
16719
|
const norm = rawPath.trim();
|
|
@@ -16427,14 +16752,14 @@ function removeJailPath(rawPath) {
|
|
|
16427
16752
|
return { removed, paths: after };
|
|
16428
16753
|
}
|
|
16429
16754
|
function regenerateUserJail(paths) {
|
|
16430
|
-
const file2 =
|
|
16755
|
+
const file2 = path15.join(USER_SHIELDS_DIR_PATH, `${USER_JAIL_SHIELD}.json`);
|
|
16431
16756
|
if (paths.length === 0) {
|
|
16432
16757
|
const active2 = readActiveShields();
|
|
16433
16758
|
if (active2.includes(USER_JAIL_SHIELD)) {
|
|
16434
16759
|
writeActiveShields(active2.filter((s) => s !== USER_JAIL_SHIELD));
|
|
16435
16760
|
}
|
|
16436
16761
|
try {
|
|
16437
|
-
|
|
16762
|
+
fs13.rmSync(file2, { force: true });
|
|
16438
16763
|
} catch {
|
|
16439
16764
|
}
|
|
16440
16765
|
return;
|
|
@@ -16461,12 +16786,12 @@ var init_jail = __esm({
|
|
|
16461
16786
|
});
|
|
16462
16787
|
|
|
16463
16788
|
// src/canary/registry.ts
|
|
16464
|
-
import
|
|
16465
|
-
import
|
|
16466
|
-
import
|
|
16789
|
+
import fs14 from "fs";
|
|
16790
|
+
import os12 from "os";
|
|
16791
|
+
import path16 from "path";
|
|
16467
16792
|
import { createHash as createHash3, randomUUID as randomUUID2, randomBytes } from "crypto";
|
|
16468
16793
|
function canaryStorePath() {
|
|
16469
|
-
return
|
|
16794
|
+
return path16.join(os12.homedir(), ".node9", "canaries.json");
|
|
16470
16795
|
}
|
|
16471
16796
|
function isRecord(x) {
|
|
16472
16797
|
if (!x || typeof x !== "object") return false;
|
|
@@ -16477,7 +16802,7 @@ function loadCanaries(opts) {
|
|
|
16477
16802
|
const p = canaryStorePath();
|
|
16478
16803
|
let raw;
|
|
16479
16804
|
try {
|
|
16480
|
-
raw =
|
|
16805
|
+
raw = fs14.readFileSync(p, "utf-8");
|
|
16481
16806
|
} catch (e) {
|
|
16482
16807
|
if (e.code === "ENOENT") return [];
|
|
16483
16808
|
throw e;
|
|
@@ -16499,19 +16824,19 @@ function saveCanaries(records) {
|
|
|
16499
16824
|
const p = canaryStorePath();
|
|
16500
16825
|
const paths = addJailPath(p, "block");
|
|
16501
16826
|
regenerateUserJail(paths);
|
|
16502
|
-
|
|
16827
|
+
fs14.mkdirSync(path16.dirname(p), { recursive: true });
|
|
16503
16828
|
const tmp = `${p}.${randomBytes(8).toString("hex")}.tmp`;
|
|
16504
16829
|
try {
|
|
16505
|
-
|
|
16830
|
+
fs14.writeFileSync(tmp, JSON.stringify({ version: 1, records }, null, 2) + "\n", {
|
|
16506
16831
|
mode: 384,
|
|
16507
16832
|
flag: "wx"
|
|
16508
16833
|
});
|
|
16509
|
-
|
|
16510
|
-
|
|
16834
|
+
fs14.chmodSync(tmp, 384);
|
|
16835
|
+
fs14.renameSync(tmp, p);
|
|
16511
16836
|
} finally {
|
|
16512
|
-
if (
|
|
16837
|
+
if (fs14.existsSync(tmp)) {
|
|
16513
16838
|
try {
|
|
16514
|
-
|
|
16839
|
+
fs14.unlinkSync(tmp);
|
|
16515
16840
|
} catch {
|
|
16516
16841
|
}
|
|
16517
16842
|
}
|
|
@@ -16561,21 +16886,21 @@ var init_registry = __esm({
|
|
|
16561
16886
|
});
|
|
16562
16887
|
|
|
16563
16888
|
// src/utils/git-remotes.ts
|
|
16564
|
-
import
|
|
16565
|
-
import
|
|
16889
|
+
import fs15 from "fs";
|
|
16890
|
+
import path17 from "path";
|
|
16566
16891
|
function readSmallFile(p, max) {
|
|
16567
16892
|
let fd;
|
|
16568
16893
|
try {
|
|
16569
|
-
fd =
|
|
16570
|
-
const st =
|
|
16894
|
+
fd = fs15.openSync(p, fs15.constants.O_RDONLY | (fs15.constants.O_NOFOLLOW ?? 0));
|
|
16895
|
+
const st = fs15.fstatSync(fd);
|
|
16571
16896
|
if (!st.isFile() || st.size > max) return null;
|
|
16572
16897
|
const buf = Buffer.alloc(st.size);
|
|
16573
|
-
const n =
|
|
16898
|
+
const n = fs15.readSync(fd, buf, 0, st.size, 0);
|
|
16574
16899
|
return buf.subarray(0, n).toString("utf8");
|
|
16575
16900
|
} catch {
|
|
16576
16901
|
return null;
|
|
16577
16902
|
} finally {
|
|
16578
|
-
if (fd !== void 0)
|
|
16903
|
+
if (fd !== void 0) fs15.closeSync(fd);
|
|
16579
16904
|
}
|
|
16580
16905
|
}
|
|
16581
16906
|
function gitRemoteHost(url) {
|
|
@@ -16594,20 +16919,20 @@ function gitRemoteHost(url) {
|
|
|
16594
16919
|
return null;
|
|
16595
16920
|
}
|
|
16596
16921
|
function findGitConfig(dir2, walkUp = true) {
|
|
16597
|
-
let cur =
|
|
16922
|
+
let cur = path17.resolve(dir2);
|
|
16598
16923
|
for (let i = 0; i < (walkUp ? 64 : 1); i++) {
|
|
16599
|
-
const dotGit =
|
|
16600
|
-
const direct = readSmallFile(
|
|
16924
|
+
const dotGit = path17.join(cur, ".git");
|
|
16925
|
+
const direct = readSmallFile(path17.join(dotGit, "config"), 1e6);
|
|
16601
16926
|
if (direct !== null) return direct;
|
|
16602
16927
|
const pointer = readSmallFile(dotGit, 4096);
|
|
16603
16928
|
const m = pointer && /^gitdir:\s*(.+?)\s*$/m.exec(pointer);
|
|
16604
16929
|
if (m) {
|
|
16605
|
-
let gitDir =
|
|
16606
|
-
const common = readSmallFile(
|
|
16607
|
-
if (common !== null) gitDir =
|
|
16608
|
-
return readSmallFile(
|
|
16930
|
+
let gitDir = path17.resolve(cur, m[1]);
|
|
16931
|
+
const common = readSmallFile(path17.join(gitDir, "commondir"), 4096);
|
|
16932
|
+
if (common !== null) gitDir = path17.resolve(gitDir, common.trim());
|
|
16933
|
+
return readSmallFile(path17.join(gitDir, "config"), 1e6);
|
|
16609
16934
|
}
|
|
16610
|
-
const parent =
|
|
16935
|
+
const parent = path17.dirname(cur);
|
|
16611
16936
|
if (parent === cur) return null;
|
|
16612
16937
|
cur = parent;
|
|
16613
16938
|
}
|
|
@@ -16616,12 +16941,12 @@ function findGitConfig(dir2, walkUp = true) {
|
|
|
16616
16941
|
function readGitRemotes(dir2, opts = {}) {
|
|
16617
16942
|
if (!dir2) return [];
|
|
16618
16943
|
const walkUp = opts.walkUp !== false;
|
|
16619
|
-
const key = `${walkUp ? "up" : "at"}:${
|
|
16620
|
-
const hit =
|
|
16944
|
+
const key = `${walkUp ? "up" : "at"}:${path17.resolve(dir2)}`;
|
|
16945
|
+
const hit = cache2.get(key);
|
|
16621
16946
|
if (hit) return hit;
|
|
16622
16947
|
const out = [];
|
|
16623
16948
|
try {
|
|
16624
|
-
const cfg = findGitConfig(
|
|
16949
|
+
const cfg = findGitConfig(path17.resolve(dir2), walkUp);
|
|
16625
16950
|
if (cfg !== null) {
|
|
16626
16951
|
let current = null;
|
|
16627
16952
|
for (const line of cfg.split(/\r?\n/)) {
|
|
@@ -16643,31 +16968,31 @@ function readGitRemotes(dir2, opts = {}) {
|
|
|
16643
16968
|
}
|
|
16644
16969
|
} catch {
|
|
16645
16970
|
}
|
|
16646
|
-
|
|
16971
|
+
cache2.set(key, out);
|
|
16647
16972
|
return out;
|
|
16648
16973
|
}
|
|
16649
|
-
var
|
|
16974
|
+
var cache2;
|
|
16650
16975
|
var init_git_remotes = __esm({
|
|
16651
16976
|
"src/utils/git-remotes.ts"() {
|
|
16652
16977
|
"use strict";
|
|
16653
16978
|
init_dist();
|
|
16654
|
-
|
|
16979
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
16655
16980
|
}
|
|
16656
16981
|
});
|
|
16657
16982
|
|
|
16658
16983
|
// src/exposure/known-hosts.ts
|
|
16659
|
-
import
|
|
16660
|
-
import
|
|
16984
|
+
import fs16 from "fs";
|
|
16985
|
+
import path18 from "path";
|
|
16661
16986
|
function manifestRoot(dir2) {
|
|
16662
|
-
let cur =
|
|
16987
|
+
let cur = path18.resolve(dir2);
|
|
16663
16988
|
for (let i = 0; i < 64; i++) {
|
|
16664
16989
|
for (const [m] of ECOSYSTEMS) {
|
|
16665
16990
|
try {
|
|
16666
|
-
if (
|
|
16991
|
+
if (fs16.existsSync(path18.join(cur, m))) return cur;
|
|
16667
16992
|
} catch {
|
|
16668
16993
|
}
|
|
16669
16994
|
}
|
|
16670
|
-
const parent =
|
|
16995
|
+
const parent = path18.dirname(cur);
|
|
16671
16996
|
if (parent === cur) return null;
|
|
16672
16997
|
cur = parent;
|
|
16673
16998
|
}
|
|
@@ -16676,8 +17001,8 @@ function manifestRoot(dir2) {
|
|
|
16676
17001
|
function knownProjectHosts(dir2) {
|
|
16677
17002
|
const empty = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
|
|
16678
17003
|
if (!dir2) return empty;
|
|
16679
|
-
const key =
|
|
16680
|
-
const hit =
|
|
17004
|
+
const key = path18.resolve(dir2);
|
|
17005
|
+
const hit = cache3.get(key);
|
|
16681
17006
|
if (hit) return hit;
|
|
16682
17007
|
const out = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
|
|
16683
17008
|
try {
|
|
@@ -16689,20 +17014,20 @@ function knownProjectHosts(dir2) {
|
|
|
16689
17014
|
const root = manifestRoot(key);
|
|
16690
17015
|
if (root !== null) {
|
|
16691
17016
|
for (const [m, registry] of ECOSYSTEMS) {
|
|
16692
|
-
if (
|
|
17017
|
+
if (fs16.existsSync(path18.join(root, m))) out.hosts.add(registry);
|
|
16693
17018
|
}
|
|
16694
17019
|
try {
|
|
16695
|
-
if (
|
|
17020
|
+
if (fs16.readdirSync(root).some((f) => f.endsWith(".gemspec")))
|
|
16696
17021
|
out.hosts.add("host:rubygems.org");
|
|
16697
17022
|
} catch {
|
|
16698
17023
|
}
|
|
16699
17024
|
}
|
|
16700
17025
|
} catch {
|
|
16701
17026
|
}
|
|
16702
|
-
|
|
17027
|
+
cache3.set(key, out);
|
|
16703
17028
|
return out;
|
|
16704
17029
|
}
|
|
16705
|
-
var ECOSYSTEMS,
|
|
17030
|
+
var ECOSYSTEMS, cache3;
|
|
16706
17031
|
var init_known_hosts = __esm({
|
|
16707
17032
|
"src/exposure/known-hosts.ts"() {
|
|
16708
17033
|
"use strict";
|
|
@@ -16714,14 +17039,14 @@ var init_known_hosts = __esm({
|
|
|
16714
17039
|
["Cargo.toml", "host:crates.io"],
|
|
16715
17040
|
["Gemfile", "host:rubygems.org"]
|
|
16716
17041
|
];
|
|
16717
|
-
|
|
17042
|
+
cache3 = /* @__PURE__ */ new Map();
|
|
16718
17043
|
}
|
|
16719
17044
|
});
|
|
16720
17045
|
|
|
16721
17046
|
// src/exposure/writes.ts
|
|
16722
|
-
import
|
|
16723
|
-
import
|
|
16724
|
-
import
|
|
17047
|
+
import fs17 from "fs";
|
|
17048
|
+
import os13 from "os";
|
|
17049
|
+
import path19 from "path";
|
|
16725
17050
|
function writtenContent(args) {
|
|
16726
17051
|
if (!args || typeof args !== "object" || Array.isArray(args)) return [];
|
|
16727
17052
|
const a = args;
|
|
@@ -16748,7 +17073,7 @@ function packageScriptsTouched(args, absPath, shellCommand) {
|
|
|
16748
17073
|
if (typeof a.content === "string") {
|
|
16749
17074
|
try {
|
|
16750
17075
|
const next = JSON.parse(a.content);
|
|
16751
|
-
const prev = JSON.parse(
|
|
17076
|
+
const prev = JSON.parse(fs17.readFileSync(absPath, "utf8"));
|
|
16752
17077
|
return JSON.stringify(next.scripts ?? null) !== JSON.stringify(prev.scripts ?? null);
|
|
16753
17078
|
} catch {
|
|
16754
17079
|
return LIFECYCLE_RE.test(a.content);
|
|
@@ -16758,8 +17083,8 @@ function packageScriptsTouched(args, absPath, shellCommand) {
|
|
|
16758
17083
|
}
|
|
16759
17084
|
function judgeWritesAfterExposure(opts) {
|
|
16760
17085
|
const holds = [];
|
|
16761
|
-
const home = opts.home ??
|
|
16762
|
-
const tmp = normalizeFilePath(
|
|
17086
|
+
const home = opts.home ?? os13.homedir();
|
|
17087
|
+
const tmp = normalizeFilePath(os13.tmpdir());
|
|
16763
17088
|
const root = opts.cwd ? normalizeFilePath(opts.cwd, "", home) : null;
|
|
16764
17089
|
const seen = /* @__PURE__ */ new Set();
|
|
16765
17090
|
for (const access of opts.accesses) {
|
|
@@ -16769,7 +17094,7 @@ function judgeWritesAfterExposure(opts) {
|
|
|
16769
17094
|
seen.add(p);
|
|
16770
17095
|
if (p === "/dev" || p.startsWith("/dev/")) continue;
|
|
16771
17096
|
if (root !== null && isInside(root, p)) {
|
|
16772
|
-
if (isAgentInstructionPath(p, root) && (
|
|
17097
|
+
if (isAgentInstructionPath(p, root) && (path19.basename(p) !== "package.json" || packageScriptsTouched(opts.args, p, opts.shellCommand)))
|
|
16773
17098
|
holds.push({ path: p, why: "agent-instruction" });
|
|
16774
17099
|
continue;
|
|
16775
17100
|
}
|
|
@@ -16869,6 +17194,7 @@ var init_exposure_store = __esm({
|
|
|
16869
17194
|
tool: source.tool,
|
|
16870
17195
|
server: source.server,
|
|
16871
17196
|
...source.word ? { word: source.word } : {},
|
|
17197
|
+
...source.eid ? { eid: source.eid } : {},
|
|
16872
17198
|
at: source.at || Date.now()
|
|
16873
17199
|
});
|
|
16874
17200
|
if (e.sources.length > MAX_SOURCES) e.sources.splice(0, e.sources.length - MAX_SOURCES);
|
|
@@ -16916,33 +17242,33 @@ var init_exposure_store = __esm({
|
|
|
16916
17242
|
|
|
16917
17243
|
// src/exposure/marker.ts
|
|
16918
17244
|
import crypto4 from "crypto";
|
|
16919
|
-
import
|
|
16920
|
-
import
|
|
16921
|
-
import
|
|
17245
|
+
import fs18 from "fs";
|
|
17246
|
+
import os14 from "os";
|
|
17247
|
+
import path20 from "path";
|
|
16922
17248
|
function dir() {
|
|
16923
|
-
return
|
|
17249
|
+
return path20.join(os14.homedir(), ".node9", "exposure-sessions");
|
|
16924
17250
|
}
|
|
16925
17251
|
function file(sessionId) {
|
|
16926
17252
|
const id = crypto4.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
16927
|
-
return
|
|
17253
|
+
return path20.join(dir(), id);
|
|
16928
17254
|
}
|
|
16929
17255
|
function markSessionExposed(sessionId) {
|
|
16930
17256
|
if (!sessionId) return;
|
|
16931
17257
|
try {
|
|
16932
|
-
|
|
17258
|
+
fs18.mkdirSync(dir(), { recursive: true, mode: 448 });
|
|
16933
17259
|
const f = file(sessionId);
|
|
16934
17260
|
const now = /* @__PURE__ */ new Date();
|
|
16935
17261
|
try {
|
|
16936
|
-
|
|
17262
|
+
fs18.utimesSync(f, now, now);
|
|
16937
17263
|
} catch {
|
|
16938
|
-
|
|
17264
|
+
fs18.writeFileSync(f, "", { mode: 384 });
|
|
16939
17265
|
}
|
|
16940
17266
|
if (Math.random() < 0.05) {
|
|
16941
17267
|
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
16942
|
-
for (const name of
|
|
17268
|
+
for (const name of fs18.readdirSync(dir())) {
|
|
16943
17269
|
try {
|
|
16944
|
-
const p =
|
|
16945
|
-
if (
|
|
17270
|
+
const p = path20.join(dir(), name);
|
|
17271
|
+
if (fs18.statSync(p).mtimeMs < cutoff) fs18.unlinkSync(p);
|
|
16946
17272
|
} catch {
|
|
16947
17273
|
}
|
|
16948
17274
|
}
|
|
@@ -16953,7 +17279,7 @@ function markSessionExposed(sessionId) {
|
|
|
16953
17279
|
function maybeSessionExposed(sessionId) {
|
|
16954
17280
|
if (!sessionId) return false;
|
|
16955
17281
|
try {
|
|
16956
|
-
const st =
|
|
17282
|
+
const st = fs18.statSync(file(sessionId));
|
|
16957
17283
|
return Date.now() - st.mtimeMs < SESSION_EXPOSURE_TTL_MS;
|
|
16958
17284
|
} catch (e) {
|
|
16959
17285
|
return e.code !== "ENOENT";
|
|
@@ -16961,8 +17287,8 @@ function maybeSessionExposed(sessionId) {
|
|
|
16961
17287
|
}
|
|
16962
17288
|
function clearSessionMarkers(sessionId) {
|
|
16963
17289
|
try {
|
|
16964
|
-
if (sessionId)
|
|
16965
|
-
else
|
|
17290
|
+
if (sessionId) fs18.unlinkSync(file(sessionId));
|
|
17291
|
+
else fs18.rmSync(dir(), { recursive: true, force: true });
|
|
16966
17292
|
} catch {
|
|
16967
17293
|
}
|
|
16968
17294
|
}
|
|
@@ -17017,9 +17343,9 @@ var init_mcp_cmd = __esm({
|
|
|
17017
17343
|
});
|
|
17018
17344
|
|
|
17019
17345
|
// src/daemon/mcp-tools.ts
|
|
17020
|
-
import
|
|
17021
|
-
import
|
|
17022
|
-
import
|
|
17346
|
+
import fs19 from "fs";
|
|
17347
|
+
import path21 from "path";
|
|
17348
|
+
import os15 from "os";
|
|
17023
17349
|
function sanitizeToolAnnotations(raw) {
|
|
17024
17350
|
if (!raw || typeof raw !== "object") return void 0;
|
|
17025
17351
|
const a = raw;
|
|
@@ -17053,13 +17379,13 @@ function deriveServerName(cmd) {
|
|
|
17053
17379
|
return strip(base2) || "MCP Server";
|
|
17054
17380
|
}
|
|
17055
17381
|
function getMcpToolsFile() {
|
|
17056
|
-
return
|
|
17382
|
+
return path21.join(os15.homedir(), ".node9", "mcp-tools.json");
|
|
17057
17383
|
}
|
|
17058
17384
|
function readMcpToolsConfig() {
|
|
17059
17385
|
try {
|
|
17060
17386
|
const file2 = getMcpToolsFile();
|
|
17061
|
-
if (!
|
|
17062
|
-
const raw =
|
|
17387
|
+
if (!fs19.existsSync(file2)) return {};
|
|
17388
|
+
const raw = fs19.readFileSync(file2, "utf-8");
|
|
17063
17389
|
return JSON.parse(raw);
|
|
17064
17390
|
} catch {
|
|
17065
17391
|
return {};
|
|
@@ -17068,11 +17394,11 @@ function readMcpToolsConfig() {
|
|
|
17068
17394
|
function writeMcpToolsConfig(config) {
|
|
17069
17395
|
try {
|
|
17070
17396
|
const file2 = getMcpToolsFile();
|
|
17071
|
-
const dir2 =
|
|
17072
|
-
if (!
|
|
17073
|
-
const tmpPath = `${file2}.${
|
|
17074
|
-
|
|
17075
|
-
|
|
17397
|
+
const dir2 = path21.dirname(file2);
|
|
17398
|
+
if (!fs19.existsSync(dir2)) fs19.mkdirSync(dir2, { recursive: true });
|
|
17399
|
+
const tmpPath = `${file2}.${os15.hostname()}.${process.pid}.tmp`;
|
|
17400
|
+
fs19.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
|
|
17401
|
+
fs19.renameSync(tmpPath, file2);
|
|
17076
17402
|
} catch (e) {
|
|
17077
17403
|
console.error("Failed to write mcp-tools.json", e);
|
|
17078
17404
|
}
|
|
@@ -17135,13 +17461,13 @@ var init_mcp_tools = __esm({
|
|
|
17135
17461
|
});
|
|
17136
17462
|
|
|
17137
17463
|
// src/exposure/roles.ts
|
|
17138
|
-
import
|
|
17464
|
+
import fs20 from "fs";
|
|
17139
17465
|
function foldServerName(name) {
|
|
17140
17466
|
return name.replace(/[^A-Za-z0-9_-]/g, "_").toLowerCase();
|
|
17141
17467
|
}
|
|
17142
17468
|
function inventoryFileStamp() {
|
|
17143
17469
|
try {
|
|
17144
|
-
const st =
|
|
17470
|
+
const st = fs20.statSync(getMcpToolsFile());
|
|
17145
17471
|
return `${st.mtimeMs}:${st.size}`;
|
|
17146
17472
|
} catch {
|
|
17147
17473
|
return "";
|
|
@@ -17236,82 +17562,6 @@ var init_roles = __esm({
|
|
|
17236
17562
|
}
|
|
17237
17563
|
});
|
|
17238
17564
|
|
|
17239
|
-
// src/auth/trusted-recipients.ts
|
|
17240
|
-
import fs20 from "fs";
|
|
17241
|
-
import path21 from "path";
|
|
17242
|
-
import os15 from "os";
|
|
17243
|
-
function getTrustedRecipientsPath() {
|
|
17244
|
-
return path21.join(os15.homedir(), ".node9", "trusted-recipients.json");
|
|
17245
|
-
}
|
|
17246
|
-
function readTrustedRecipients() {
|
|
17247
|
-
try {
|
|
17248
|
-
const raw = fs20.readFileSync(getTrustedRecipientsPath(), "utf8");
|
|
17249
|
-
const parsed = JSON.parse(raw);
|
|
17250
|
-
return Array.isArray(parsed.entries) ? parsed.entries.filter((e) => e && typeof e.value === "string") : [];
|
|
17251
|
-
} catch {
|
|
17252
|
-
return [];
|
|
17253
|
-
}
|
|
17254
|
-
}
|
|
17255
|
-
function writeTrustedRecipients(entries) {
|
|
17256
|
-
const filePath = getTrustedRecipientsPath();
|
|
17257
|
-
fs20.mkdirSync(path21.dirname(filePath), { recursive: true });
|
|
17258
|
-
const tmp = filePath + ".node9-tmp";
|
|
17259
|
-
fs20.writeFileSync(tmp, JSON.stringify({ entries }, null, 2), { mode: 384 });
|
|
17260
|
-
fs20.renameSync(tmp, filePath);
|
|
17261
|
-
}
|
|
17262
|
-
function normalizeRecipient(raw) {
|
|
17263
|
-
const v = raw.trim();
|
|
17264
|
-
if (!v) return null;
|
|
17265
|
-
if (/^(email|host|iban):/i.test(v)) {
|
|
17266
|
-
const i = v.indexOf(":");
|
|
17267
|
-
const kind = v.slice(0, i).toLowerCase();
|
|
17268
|
-
const inner = normalizeRecipient(v.slice(i + 1));
|
|
17269
|
-
return inner && inner.startsWith(`${kind}:`) ? inner : null;
|
|
17270
|
-
}
|
|
17271
|
-
const found = extractArgDestinations({ to: v });
|
|
17272
|
-
return found.length === 1 ? found[0] : null;
|
|
17273
|
-
}
|
|
17274
|
-
function addTrustedRecipient(raw, addedBy = "user") {
|
|
17275
|
-
const value = normalizeRecipient(raw);
|
|
17276
|
-
if (!value) return null;
|
|
17277
|
-
const entries = readTrustedRecipients();
|
|
17278
|
-
if (!entries.some((e) => e.value === value)) {
|
|
17279
|
-
entries.push({ value, addedAt: Date.now(), addedBy });
|
|
17280
|
-
writeTrustedRecipients(entries);
|
|
17281
|
-
}
|
|
17282
|
-
return value;
|
|
17283
|
-
}
|
|
17284
|
-
function removeTrustedRecipient(raw) {
|
|
17285
|
-
const value = normalizeRecipient(raw) ?? raw.trim();
|
|
17286
|
-
const entries = readTrustedRecipients();
|
|
17287
|
-
const next = entries.filter((e) => e.value !== value);
|
|
17288
|
-
if (next.length === entries.length) return false;
|
|
17289
|
-
writeTrustedRecipients(next);
|
|
17290
|
-
return true;
|
|
17291
|
-
}
|
|
17292
|
-
function mtimeOf() {
|
|
17293
|
-
try {
|
|
17294
|
-
return fs20.statSync(getTrustedRecipientsPath()).mtimeMs;
|
|
17295
|
-
} catch {
|
|
17296
|
-
return 0;
|
|
17297
|
-
}
|
|
17298
|
-
}
|
|
17299
|
-
function isTrustedRecipient(value) {
|
|
17300
|
-
const mtime = mtimeOf();
|
|
17301
|
-
if (!cache3 || cache3.mtime !== mtime) {
|
|
17302
|
-
cache3 = { mtime, values: new Set(readTrustedRecipients().map((e) => e.value)) };
|
|
17303
|
-
}
|
|
17304
|
-
return cache3.values.has(value);
|
|
17305
|
-
}
|
|
17306
|
-
var cache3;
|
|
17307
|
-
var init_trusted_recipients = __esm({
|
|
17308
|
-
"src/auth/trusted-recipients.ts"() {
|
|
17309
|
-
"use strict";
|
|
17310
|
-
init_dist();
|
|
17311
|
-
cache3 = null;
|
|
17312
|
-
}
|
|
17313
|
-
});
|
|
17314
|
-
|
|
17315
17565
|
// src/utils/read-capped.ts
|
|
17316
17566
|
import fs21 from "fs";
|
|
17317
17567
|
function readCapped(file2, maxBytes) {
|
|
@@ -17379,7 +17629,8 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, contai
|
|
|
17379
17629
|
const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
|
|
17380
17630
|
const cleanedRiskMetadata = riskMetadata ? Object.fromEntries(
|
|
17381
17631
|
Object.entries(riskMetadata).filter(
|
|
17382
|
-
([, v]) => typeof v === "string" && v.length > 0 || typeof v === "number" && Number.isFinite(v)
|
|
17632
|
+
([k, v]) => typeof v === "string" && v.length > 0 || typeof v === "number" && Number.isFinite(v) || // fw#318 2b: the one array the SaaS schema declares.
|
|
17633
|
+
k === "exposureDestinations" && Array.isArray(v) && v.length > 0
|
|
17383
17634
|
)
|
|
17384
17635
|
) : void 0;
|
|
17385
17636
|
const hasRiskMetadata = cleanedRiskMetadata && Object.keys(cleanedRiskMetadata).length > 0;
|
|
@@ -17459,11 +17710,15 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
|
|
|
17459
17710
|
cwd: process.cwd(),
|
|
17460
17711
|
platform: os16.platform()
|
|
17461
17712
|
},
|
|
17462
|
-
//
|
|
17463
|
-
//
|
|
17713
|
+
// The SaaS validates riskMetadata strictly and would drop it whole on
|
|
17714
|
+
// an unknown key, so only fields its schema declares go on the wire.
|
|
17715
|
+
// exposureDestinations ships since the dashboard's 2b contract
|
|
17716
|
+
// (fw#318): "Approve and trust" needs them.
|
|
17464
17717
|
...riskMetadata && {
|
|
17465
17718
|
riskMetadata: Object.fromEntries(
|
|
17466
|
-
Object.entries(riskMetadata).filter(
|
|
17719
|
+
Object.entries(riskMetadata).filter(
|
|
17720
|
+
([k, v]) => !Array.isArray(v) || k === "exposureDestinations"
|
|
17721
|
+
)
|
|
17467
17722
|
)
|
|
17468
17723
|
},
|
|
17469
17724
|
...ciContext && { ciContext },
|
|
@@ -18323,7 +18578,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18323
18578
|
let fileAccess;
|
|
18324
18579
|
let fileFinding;
|
|
18325
18580
|
try {
|
|
18326
|
-
fileAccess = prepareFileAccesses(toolName, args, options?.cwd, config.policy
|
|
18581
|
+
fileAccess = prepareFileAccesses(toolName, args, options?.cwd, inspectionLayers(config.policy));
|
|
18327
18582
|
fileFinding = isManual ? null : evaluateProtectedFileAccess(
|
|
18328
18583
|
fileAccess.accesses,
|
|
18329
18584
|
fileAccess,
|
|
@@ -18342,7 +18597,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18342
18597
|
fileAccess = { cwd: options?.cwd, homeDirs: [], accesses: [], handledPaths: /* @__PURE__ */ new Set() };
|
|
18343
18598
|
fileFinding = isManual ? null : incompleteFileVerdict();
|
|
18344
18599
|
}
|
|
18345
|
-
const shellInput = resolveShellInput(toolName, args, config.policy
|
|
18600
|
+
const shellInput = resolveShellInput(toolName, args, inspectionLayers(config.policy));
|
|
18346
18601
|
const networkShell = shellInput.kind === "shell" && NETWORK_COMMAND_RE.test(shellInput.command);
|
|
18347
18602
|
const shellReview = !isManual && shellInput.kind === "uninspectable";
|
|
18348
18603
|
if (shellReview) {
|
|
@@ -18452,6 +18707,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18452
18707
|
let exposureReview = null;
|
|
18453
18708
|
let exposureKeys = [];
|
|
18454
18709
|
let exposureDestinations = [];
|
|
18710
|
+
let exposureFromEid;
|
|
18455
18711
|
const ucPolicy = config.policy.untrustedContent;
|
|
18456
18712
|
if (ucPolicy?.enabled && !taintWarning && meta?.sessionId && !isManual) {
|
|
18457
18713
|
const role = toolRole(toolName, ucPolicy);
|
|
@@ -18459,7 +18715,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18459
18715
|
const shellExp = shellInput.kind === "shell" ? classifyShellExposure(shellInput.command) : null;
|
|
18460
18716
|
const bareName = splitMcpToolName(toolName)?.bare;
|
|
18461
18717
|
const mcpFileWrite = bareName !== void 0 && fileToolOperation(bareName, args) === "write";
|
|
18462
|
-
const writes = (mcpFileWrite ? prepareFileAccesses(bareName, args, options?.cwd, config.policy
|
|
18718
|
+
const writes = (mcpFileWrite ? prepareFileAccesses(bareName, args, options?.cwd, inspectionLayers(config.policy)).accesses : fileAccess.accesses).filter((a) => ["write", "create", "remove"].includes(a.operation));
|
|
18463
18719
|
const sendsSomething = acts && !mcpFileWrite || !!shellExp?.sends;
|
|
18464
18720
|
const mayBeExposed = daemonSaysExposed ?? (maybeSessionExposed(meta.sessionId) || writes.length > 0 && judgeWritesAfterExposure({
|
|
18465
18721
|
accesses: writes,
|
|
@@ -18503,7 +18759,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18503
18759
|
const isKnown = (d) => {
|
|
18504
18760
|
const domain = destinationDomain(d);
|
|
18505
18761
|
const value = destinationValue(d);
|
|
18506
|
-
return domain !== null && ucPolicy.orgDomains.includes(domain) || mentioned.has(d) || isTrustedRecipient(d) || destinationKind(d) === "host" && (isLoopbackHost(value) || isTrustedHost(value) || config.policy.trustedHostsManaged && matchesTrustedHost(value, config.policy.trustedHosts) || config.policy.egress.allow.some((pat) => hostMatches(value, pat)) || writeProjectHosts.hosts.has(d));
|
|
18762
|
+
return domain !== null && ucPolicy.orgDomains.includes(domain) || mentioned.has(d) || isTrustedRecipient(d) || ucPolicy.trustedRecipients.includes(d) || destinationKind(d) === "host" && (isLoopbackHost(value) || isTrustedHost(value) || config.policy.trustedHostsManaged && matchesTrustedHost(value, config.policy.trustedHosts) || config.policy.egress.allow.some((pat) => hostMatches(value, pat)) || writeProjectHosts.hosts.has(d));
|
|
18507
18763
|
};
|
|
18508
18764
|
const approvedOnce = (key) => ucPolicy.afterExposure === "ask-once" && approved.has(key);
|
|
18509
18765
|
const repos = (shellExp?.sends ? shellExp.repos : []).filter(
|
|
@@ -18567,6 +18823,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18567
18823
|
);
|
|
18568
18824
|
}
|
|
18569
18825
|
exposureReview = `\u{1F6E1}\uFE0F Node9: this session read ${read2}. This call ${parts.join("; and ")}.`;
|
|
18826
|
+
exposureFromEid = last?.eid;
|
|
18827
|
+
if (exposureFromEid) meta = { ...meta, exposureFromEid };
|
|
18570
18828
|
exposureKeys = [
|
|
18571
18829
|
...held.filter((h) => h.dest || h.why === "unresolved" || h.why === "repository").map(
|
|
18572
18830
|
(h) => h.why === "repository" ? `${toolName}|repo:${h.repo}` : `${toolName}|${h.dest || "unresolved"}`
|
|
@@ -18580,6 +18838,11 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18580
18838
|
exposureReview += ` To trust ${exposureDestinations.length === 1 ? "this address" : "these addresses"} for good, the user runs: ` + exposureDestinations.map((d) => `node9 trust recipient add ${destinationValue(d)}`).join("; ") + ".";
|
|
18581
18839
|
}
|
|
18582
18840
|
} else if (held.length > 0 || writeHolds.length > 0 || logged.length > 0) {
|
|
18841
|
+
meta = {
|
|
18842
|
+
...meta,
|
|
18843
|
+
exposure: { kind: "logged", destinations: logged.length },
|
|
18844
|
+
...last?.eid ? { exposureFromEid: last.eid } : {}
|
|
18845
|
+
};
|
|
18583
18846
|
appendToLog(HOOK_DEBUG_LOG, {
|
|
18584
18847
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18585
18848
|
event: "exposure-logged",
|
|
@@ -19133,6 +19396,7 @@ ${packageReview}`;
|
|
|
19133
19396
|
// deferReview and serverKey never co-occur in production — see above.)
|
|
19134
19397
|
reason: taintWarning || packageReview || exposureReview || explainableLabel || "Node9 flagged this action for review.",
|
|
19135
19398
|
...exposureReview && exposureKeys.length > 0 ? { exposureKeys } : {},
|
|
19399
|
+
...exposureReview && exposureFromEid ? { exposureFromEid } : {},
|
|
19136
19400
|
ruleDescription: policyRuleDescription,
|
|
19137
19401
|
blockedByLabel: explainableLabel
|
|
19138
19402
|
};
|
|
@@ -30896,7 +31160,7 @@ var init_ship = __esm({
|
|
|
30896
31160
|
});
|
|
30897
31161
|
|
|
30898
31162
|
// src/policy-snapshot/build.ts
|
|
30899
|
-
function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, statusEntries = [], syncHealth, identity) {
|
|
31163
|
+
function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, statusEntries = [], syncHealth, identity, trustedRecipients = []) {
|
|
30900
31164
|
const p = config.policy;
|
|
30901
31165
|
const statusByKey = /* @__PURE__ */ new Map();
|
|
30902
31166
|
for (const s of statusEntries) {
|
|
@@ -30906,7 +31170,7 @@ function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, st
|
|
|
30906
31170
|
const connectedRows = Object.entries(mcpTools).slice(0, MAX_MCP_SERVERS).map(([key, cfg]) => ({
|
|
30907
31171
|
key,
|
|
30908
31172
|
...cfg.name && { name: cfg.name },
|
|
30909
|
-
tools: cfg.tools.map((t) => t.name
|
|
31173
|
+
tools: cfg.tools.slice(0, MAX_MCP_TOOLS).map((t) => ({ name: t.name, ...t.annotations ? { annotations: t.annotations } : {} })),
|
|
30910
31174
|
toolCount: cfg.tools.length,
|
|
30911
31175
|
status: cfg.status,
|
|
30912
31176
|
// In mcp-tools.json ⇒ it launched ⇒ connected, unless the resolver says stale.
|
|
@@ -30956,6 +31220,15 @@ function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, st
|
|
|
30956
31220
|
engineVersion: ENGINE_VERSION,
|
|
30957
31221
|
// Connected rows first so they win the cap; non-connected SEE rows fill the rest.
|
|
30958
31222
|
mcpServers: [...connectedRows, ...extraRows].slice(0, MAX_MCP_SERVERS),
|
|
31223
|
+
// fw#318 2b: the developer's personal trusted recipients (A1: read-only
|
|
31224
|
+
// on the dashboard). Omitted when empty, so an old SaaS sees no change.
|
|
31225
|
+
...trustedRecipients.length > 0 && {
|
|
31226
|
+
trustedRecipients: trustedRecipients.slice(0, 200).map((e) => ({
|
|
31227
|
+
value: e.value,
|
|
31228
|
+
addedBy: e.addedBy,
|
|
31229
|
+
...e.addedAt ? { addedAt: e.addedAt } : {}
|
|
31230
|
+
}))
|
|
31231
|
+
},
|
|
30959
31232
|
// fleet-ship Step 2: ship the proxy's own sync health so the dashboard can
|
|
30960
31233
|
// derive a "sync failing" badge. Only included when the caller passes it.
|
|
30961
31234
|
...syncHealth && {
|
|
@@ -31476,6 +31749,10 @@ function extractManagedConfig(body) {
|
|
|
31476
31749
|
out3.afterExposure = u.afterExposure;
|
|
31477
31750
|
if (Array.isArray(u.orgDomains))
|
|
31478
31751
|
out3.orgDomains = u.orgDomains.filter((x) => typeof x === "string");
|
|
31752
|
+
if (Array.isArray(u.trustedRecipients))
|
|
31753
|
+
out3.trustedRecipients = u.trustedRecipients.filter(
|
|
31754
|
+
(x) => typeof x === "string"
|
|
31755
|
+
);
|
|
31479
31756
|
if (u.roles && typeof u.roles === "object" && !Array.isArray(u.roles)) {
|
|
31480
31757
|
const roles = {};
|
|
31481
31758
|
for (const [server, tools] of Object.entries(u.roles)) {
|
|
@@ -31652,6 +31929,17 @@ async function pushPostureSnapshot(creds) {
|
|
|
31652
31929
|
} catch {
|
|
31653
31930
|
}
|
|
31654
31931
|
}
|
|
31932
|
+
function personalTrustedRecipients() {
|
|
31933
|
+
try {
|
|
31934
|
+
return readTrustedRecipients().map((e) => ({
|
|
31935
|
+
value: e.value,
|
|
31936
|
+
addedBy: e.addedBy === "approval" ? "approval" : "user",
|
|
31937
|
+
...Number.isFinite(e.addedAt) ? { addedAt: new Date(e.addedAt).toISOString() } : {}
|
|
31938
|
+
}));
|
|
31939
|
+
} catch {
|
|
31940
|
+
return [];
|
|
31941
|
+
}
|
|
31942
|
+
}
|
|
31655
31943
|
async function pushPolicySnapshot(creds) {
|
|
31656
31944
|
try {
|
|
31657
31945
|
const cfg = getConfig();
|
|
@@ -31674,7 +31962,9 @@ async function pushPolicySnapshot(creds) {
|
|
|
31674
31962
|
readSyncHealth(),
|
|
31675
31963
|
// Machine identity — heals legacy machineId-less keys on the SaaS side
|
|
31676
31964
|
// (login-v2 duplicate-key fix). getMachineId mints on first need.
|
|
31677
|
-
{ machineId: getMachineId(), platform: process.platform }
|
|
31965
|
+
{ machineId: getMachineId(), platform: process.platform },
|
|
31966
|
+
// fw#318 2b: the developer's personal trusted recipients.
|
|
31967
|
+
personalTrustedRecipients()
|
|
31678
31968
|
);
|
|
31679
31969
|
await shipPolicySnapshot(body, creds);
|
|
31680
31970
|
} catch {
|
|
@@ -31705,7 +31995,9 @@ async function runPolicyPush() {
|
|
|
31705
31995
|
// (daemon here, user shell in runPolicyPush); see the P2 design's two-env note.
|
|
31706
31996
|
resolveMcpStatus(),
|
|
31707
31997
|
readSyncHealth(),
|
|
31708
|
-
{ machineId: getMachineId(), platform: process.platform }
|
|
31998
|
+
{ machineId: getMachineId(), platform: process.platform },
|
|
31999
|
+
// fw#318 2b: the developer's personal trusted recipients.
|
|
32000
|
+
personalTrustedRecipients()
|
|
31709
32001
|
);
|
|
31710
32002
|
const sent = await shipPolicySnapshot(body, creds);
|
|
31711
32003
|
return sent ? { ok: true } : { ok: false, reason: "Push failed (network or server error)" };
|
|
@@ -31883,6 +32175,7 @@ var init_sync = __esm({
|
|
|
31883
32175
|
init_ship();
|
|
31884
32176
|
init_build2();
|
|
31885
32177
|
init_mcp_tools();
|
|
32178
|
+
init_trusted_recipients();
|
|
31886
32179
|
init_state2();
|
|
31887
32180
|
init_mcp_status();
|
|
31888
32181
|
init_ship2();
|
|
@@ -31939,6 +32232,13 @@ import fs56 from "fs";
|
|
|
31939
32232
|
import path55 from "path";
|
|
31940
32233
|
import os47 from "os";
|
|
31941
32234
|
import crypto7 from "crypto";
|
|
32235
|
+
function exposureOf(v) {
|
|
32236
|
+
if (!v || typeof v !== "object") return null;
|
|
32237
|
+
const o = v;
|
|
32238
|
+
if (o.kind !== "source" && o.kind !== "logged") return null;
|
|
32239
|
+
const n = typeof o.destinations === "number" && Number.isFinite(o.destinations) ? o.destinations : 0;
|
|
32240
|
+
return { kind: o.kind, destinations: Math.max(0, Math.min(1e3, Math.floor(n))) };
|
|
32241
|
+
}
|
|
31942
32242
|
function fileSignature(filePath) {
|
|
31943
32243
|
const fd = fs56.openSync(filePath, "r");
|
|
31944
32244
|
try {
|
|
@@ -32014,7 +32314,9 @@ function buildWireRows(chunk2) {
|
|
|
32014
32314
|
...typeof parsed.loopCount === "number" ? { loopCount: parsed.loopCount } : {},
|
|
32015
32315
|
...typeof parsed.transcriptPath === "string" ? { transcriptPath: parsed.transcriptPath } : {},
|
|
32016
32316
|
...typeof parsed.taintFromEid === "string" ? { taintFromEid: parsed.taintFromEid } : {},
|
|
32017
|
-
...typeof parsed.taintSource === "string" ? { taintSource: parsed.taintSource } : {}
|
|
32317
|
+
...typeof parsed.taintSource === "string" ? { taintSource: parsed.taintSource } : {},
|
|
32318
|
+
...exposureOf(parsed.exposure) ? { exposure: exposureOf(parsed.exposure) } : {},
|
|
32319
|
+
...typeof parsed.exposureFromEid === "string" ? { exposureFromEid: parsed.exposureFromEid } : {}
|
|
32018
32320
|
});
|
|
32019
32321
|
}
|
|
32020
32322
|
return { rows, consumed: lastNl + 1 };
|
|
@@ -34474,6 +34776,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34474
34776
|
tool: body.source.tool.slice(0, 200),
|
|
34475
34777
|
server: typeof body.source.server === "string" ? body.source.server.slice(0, 80) : void 0,
|
|
34476
34778
|
word: typeof body.source.word === "string" ? body.source.word.slice(0, 40) : void 0,
|
|
34779
|
+
eid: typeof body.source.eid === "string" ? body.source.eid.slice(0, 64) : void 0,
|
|
34477
34780
|
at: Date.now()
|
|
34478
34781
|
},
|
|
34479
34782
|
strings(body.destinations)
|
|
@@ -64301,7 +64604,8 @@ RAW: ${raw}
|
|
|
64301
64604
|
sessionId: sid,
|
|
64302
64605
|
ts: Date.now(),
|
|
64303
64606
|
label: result2.blockedByLabel,
|
|
64304
|
-
...result2.exposureKeys?.length ? { exposureKeys: result2.exposureKeys } : {}
|
|
64607
|
+
...result2.exposureKeys?.length ? { exposureKeys: result2.exposureKeys } : {},
|
|
64608
|
+
...result2.exposureFromEid ? { exposureFromEid: result2.exposureFromEid } : {}
|
|
64305
64609
|
});
|
|
64306
64610
|
}
|
|
64307
64611
|
} catch {
|
|
@@ -64764,9 +65068,14 @@ function buildArgsField(rawInput) {
|
|
|
64764
65068
|
const hit = scanArgs(rawInput);
|
|
64765
65069
|
if (hit) return { ...hashed, dlpPattern: hit.patternName, dlpSample: hit.redactedSample };
|
|
64766
65070
|
if (!scanCoveredEverything(rawInput)) return { ...hashed, argsUnscanned: true };
|
|
64767
|
-
const
|
|
64768
|
-
if (
|
|
64769
|
-
return {
|
|
65071
|
+
const inspected = inspectArgumentSecrets(rawInput);
|
|
65072
|
+
if (inspected.match)
|
|
65073
|
+
return {
|
|
65074
|
+
...hashed,
|
|
65075
|
+
dlpPattern: inspected.match.patternName,
|
|
65076
|
+
dlpSample: inspected.match.redactedSample
|
|
65077
|
+
};
|
|
65078
|
+
if (inspected.coverage === "incomplete") return { ...hashed, argsUnscanned: true };
|
|
64770
65079
|
return { args: JSON.parse(redactSecrets(JSON.stringify(rawInput))) };
|
|
64771
65080
|
} catch {
|
|
64772
65081
|
return hashed;
|
|
@@ -64877,6 +65186,7 @@ function registerLogCommand(program2) {
|
|
|
64877
65186
|
{
|
|
64878
65187
|
agent,
|
|
64879
65188
|
ruleName: reviewLabel,
|
|
65189
|
+
...resolvedReview.exposureFromEid ? { exposureFromEid: resolvedReview.exposureFromEid } : {},
|
|
64880
65190
|
...rawToolName !== tool ? { agentToolName: rawToolName } : {},
|
|
64881
65191
|
...typeof payloadSessionId === "string" ? { sessionId: payloadSessionId } : {},
|
|
64882
65192
|
...safeCwd ? { workingDir: safeCwd } : {}
|
|
@@ -64914,17 +65224,21 @@ function registerLogCommand(program2) {
|
|
|
64914
65224
|
if (redactOutputMode) {
|
|
64915
65225
|
const { result, found } = redactText(toolOutput);
|
|
64916
65226
|
let out = result;
|
|
65227
|
+
const ucA = config.policy.untrustedContent;
|
|
65228
|
+
const shellA = ucA?.enabled ? shellFetch(tool, rawInput) : null;
|
|
65229
|
+
const sourceA = !!ucA?.enabled && (toolRole(tool, ucA).source || !!shellA?.fetches);
|
|
64917
65230
|
let injection = null;
|
|
64918
65231
|
if (injectionOn) {
|
|
64919
|
-
const m = scanInjection(result, {
|
|
65232
|
+
const m = scanInjection(result, {
|
|
65233
|
+
tool: rawToolName,
|
|
65234
|
+
...sourceA ? { untrusted: true } : {}
|
|
65235
|
+
});
|
|
64920
65236
|
if (m && atLeastConfidence(m.confidence, inj.minConfidence)) {
|
|
64921
65237
|
injection = m;
|
|
64922
65238
|
out = frameUntrusted(result);
|
|
64923
65239
|
}
|
|
64924
65240
|
}
|
|
64925
|
-
|
|
64926
|
-
const shellA = ucA?.enabled ? shellFetch(tool, rawInput) : null;
|
|
64927
|
-
if (ucA?.enabled && (toolRole(tool, ucA).source || shellA?.fetches)) {
|
|
65241
|
+
if (sourceA) {
|
|
64928
65242
|
const wordA = shellA?.fetches ? shellSourceWord(shellA.fetchKinds) : sourceWord(tool);
|
|
64929
65243
|
markSessionExposed(payloadSessionId ?? "");
|
|
64930
65244
|
await notifySessionExposure(
|
|
@@ -64966,15 +65280,16 @@ function registerLogCommand(program2) {
|
|
|
64966
65280
|
);
|
|
64967
65281
|
}
|
|
64968
65282
|
if (injectionOn) {
|
|
64969
|
-
const
|
|
65283
|
+
const injCtx = { tool: rawToolName, ...sourceRead ? { untrusted: true } : {} };
|
|
65284
|
+
const m = scanInjection(toolOutput, injCtx);
|
|
64970
65285
|
if (m && atLeastConfidence(m.confidence, inj.minConfidence)) {
|
|
64971
65286
|
if (spotlightApplies) {
|
|
64972
|
-
injectedLeaf = (leaf) => scanInjection(leaf,
|
|
65287
|
+
injectedLeaf = (leaf) => scanInjection(leaf, injCtx) !== null;
|
|
64973
65288
|
}
|
|
64974
65289
|
if (agent === "Claude Code" && !spotlightApplies) {
|
|
64975
65290
|
updatedToolOutput = frameToolOutputLeaves(
|
|
64976
65291
|
updatedToolOutput ?? payload.tool_response,
|
|
64977
|
-
(leaf) => scanInjection(leaf,
|
|
65292
|
+
(leaf) => scanInjection(leaf, injCtx) !== null,
|
|
64978
65293
|
newUntrustedFrame,
|
|
64979
65294
|
neutralizeMarkers
|
|
64980
65295
|
);
|
|
@@ -64991,22 +65306,29 @@ function registerLogCommand(program2) {
|
|
|
64991
65306
|
if (sourceRead) {
|
|
64992
65307
|
const destinations = extractContentDestinations(toolOutput);
|
|
64993
65308
|
markSessionExposed(payloadSessionId ?? "");
|
|
65309
|
+
const sourceEid = appendLocalAudit(
|
|
65310
|
+
tool,
|
|
65311
|
+
rawInput,
|
|
65312
|
+
"allow",
|
|
65313
|
+
"exposure-source",
|
|
65314
|
+
{
|
|
65315
|
+
agent,
|
|
65316
|
+
...payloadSessionId ? { sessionId: payloadSessionId } : {},
|
|
65317
|
+
...rawToolName !== tool ? { agentToolName: rawToolName } : {},
|
|
65318
|
+
exposure: { kind: "source", destinations: destinations.length }
|
|
65319
|
+
},
|
|
65320
|
+
config.settings.auditHashArgs === true
|
|
65321
|
+
);
|
|
64994
65322
|
await notifySessionExposure(
|
|
64995
65323
|
payloadSessionId ?? "",
|
|
64996
65324
|
{
|
|
64997
65325
|
tool,
|
|
64998
65326
|
server: splitMcpToolName(tool)?.server,
|
|
64999
|
-
...shellExposure?.fetches ? { word: shellSourceWord(shellExposure.fetchKinds) } : {}
|
|
65327
|
+
...shellExposure?.fetches ? { word: shellSourceWord(shellExposure.fetchKinds) } : {},
|
|
65328
|
+
eid: sourceEid
|
|
65000
65329
|
},
|
|
65001
65330
|
destinations
|
|
65002
65331
|
);
|
|
65003
|
-
appendToLog(LOCAL_AUDIT_LOG, {
|
|
65004
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
65005
|
-
tool,
|
|
65006
|
-
source: "exposure-source",
|
|
65007
|
-
destinations: destinations.length,
|
|
65008
|
-
...payloadSessionId ? { sessionId: payloadSessionId } : {}
|
|
65009
|
-
});
|
|
65010
65332
|
if (spotlightApplies) {
|
|
65011
65333
|
const word = shellExposure?.fetches ? shellSourceWord(shellExposure.fetchKinds) : sourceWord(tool);
|
|
65012
65334
|
if (agent === "Claude Code") {
|
|
@@ -66536,7 +66858,9 @@ function dimensionOfBlock(checkedBy, ruleName) {
|
|
|
66536
66858
|
if (checkedBy.includes("egress")) return "network";
|
|
66537
66859
|
if (ruleName.startsWith("ssrf:")) return "network";
|
|
66538
66860
|
if (checkedBy.includes("pii") || checkedBy.includes("dlp")) return "data";
|
|
66539
|
-
if (checkedBy === "loop-detected" || checkedBy === "mcp-pin-mismatch" || checkedBy.startsWith("injection")
|
|
66861
|
+
if (checkedBy === "loop-detected" || checkedBy === "mcp-pin-mismatch" || checkedBy.startsWith("injection") || // fw#318: a send held after the session read outside content, and the
|
|
66862
|
+
// read rows themselves. Mirrors node9Firewall canon-taxonomy.ts.
|
|
66863
|
+
ruleName.startsWith("exposure") || checkedBy.startsWith("exposure"))
|
|
66540
66864
|
return "detection";
|
|
66541
66865
|
if (checkedBy === "app-permission-block" || ruleName.startsWith("app-permission:")) return "apps";
|
|
66542
66866
|
if (ruleName.startsWith("shield:project-jail")) return "files";
|
|
@@ -67860,7 +68184,10 @@ function scanToolResult(line, parsed, tool, cfg) {
|
|
|
67860
68184
|
let injection = null;
|
|
67861
68185
|
const inj = cfg.injection;
|
|
67862
68186
|
if (inj.enabled && !inj.allow.includes(tool) && texts.length > 0) {
|
|
67863
|
-
const m = scanInjection(texts.join("\n"), {
|
|
68187
|
+
const m = scanInjection(texts.join("\n"), {
|
|
68188
|
+
tool,
|
|
68189
|
+
...cfg.untrusted ? { untrusted: true } : {}
|
|
68190
|
+
});
|
|
67864
68191
|
if (m && CONFIDENCE_RANK2[m.confidence] >= CONFIDENCE_RANK2[inj.minConfidence]) {
|
|
67865
68192
|
injection = m;
|
|
67866
68193
|
mutated = true;
|
|
@@ -68376,7 +68703,8 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
68376
68703
|
const scan = scanToolResult(line, parsed, exec.toolName, {
|
|
68377
68704
|
dlpEnabled: cfg.policy.dlp.enabled,
|
|
68378
68705
|
injection: cfg.policy.injectionScan,
|
|
68379
|
-
spotlight: isSource && uc.spotlight ? sourceWord(nsName) : void 0
|
|
68706
|
+
spotlight: isSource && uc.spotlight ? sourceWord(nsName) : void 0,
|
|
68707
|
+
untrusted: isSource
|
|
68380
68708
|
});
|
|
68381
68709
|
if (scan.changed) outLine = scan.line;
|
|
68382
68710
|
if (isSource) {
|