@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.js
CHANGED
|
@@ -2168,32 +2168,109 @@ function evaluateSmartConditions(args, rule) {
|
|
|
2168
2168
|
function isBashTool(tool) {
|
|
2169
2169
|
return BASH_TOOL_NAMES.has(tool.toLowerCase());
|
|
2170
2170
|
}
|
|
2171
|
-
function
|
|
2171
|
+
function inspectionLayers(policy) {
|
|
2172
|
+
return { trusted: policy.toolInspection, project: policy.projectToolInspection };
|
|
2173
|
+
}
|
|
2174
|
+
function isShellEntry(value) {
|
|
2175
|
+
return typeof value === "object" && value !== null && value.kind === "shell" && typeof value.field === "string" && Object.keys(value).length === 2;
|
|
2176
|
+
}
|
|
2177
|
+
function isInspectionLayers(input) {
|
|
2178
|
+
const trusted = input.trusted;
|
|
2179
|
+
return typeof trusted === "object" && trusted !== null && !isShellEntry(trusted);
|
|
2180
|
+
}
|
|
2181
|
+
function layersOf(input) {
|
|
2182
|
+
return isInspectionLayers(input) ? { trusted: input.trusted, project: input.project ?? {} } : { trusted: input, project: {} };
|
|
2183
|
+
}
|
|
2184
|
+
function entryOf(tool, inspection) {
|
|
2172
2185
|
const pattern = Object.keys(inspection).find((p) => matchesPattern(tool, p));
|
|
2173
2186
|
return pattern === void 0 ? void 0 : inspection[pattern];
|
|
2174
2187
|
}
|
|
2188
|
+
function inspectionEntry(tool, inspection = {}) {
|
|
2189
|
+
const { trusted, project } = layersOf(inspection);
|
|
2190
|
+
return entryOf(tool, trusted) ?? entryOf(tool, project);
|
|
2191
|
+
}
|
|
2192
|
+
function executorName(tool) {
|
|
2193
|
+
const name = tool.toLowerCase();
|
|
2194
|
+
if (!name.startsWith("mcp__")) return name;
|
|
2195
|
+
const at = name.lastIndexOf("__");
|
|
2196
|
+
return at >= "mcp__".length ? name.slice(at + 2) : name;
|
|
2197
|
+
}
|
|
2198
|
+
function entryFields(entry) {
|
|
2199
|
+
if (typeof entry === "object") return [entry.field];
|
|
2200
|
+
return entry === "command" ? ["command"] : null;
|
|
2201
|
+
}
|
|
2175
2202
|
function shellFields(tool, inspection) {
|
|
2176
2203
|
if (isBashTool(tool)) return ["command"];
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
if (
|
|
2180
|
-
const
|
|
2181
|
-
|
|
2204
|
+
const { trusted, project } = layersOf(inspection);
|
|
2205
|
+
const trustedEntry = entryOf(tool, trusted);
|
|
2206
|
+
if (trustedEntry !== void 0) return entryFields(trustedEntry);
|
|
2207
|
+
const builtin = MCP_EXECUTORS.has(executorName(tool)) ? BUILTIN_FIELDS : null;
|
|
2208
|
+
const projectEntry = entryOf(tool, project);
|
|
2209
|
+
if (projectEntry === void 0) return builtin;
|
|
2210
|
+
const added = entryFields(projectEntry);
|
|
2211
|
+
if (!builtin) return added;
|
|
2212
|
+
if (!added) return builtin;
|
|
2213
|
+
return [.../* @__PURE__ */ new Set([...builtin, ...added])];
|
|
2182
2214
|
}
|
|
2183
2215
|
function isShellShapedTool(tool, inspection = {}) {
|
|
2184
2216
|
return shellFields(tool, inspection) !== null;
|
|
2185
2217
|
}
|
|
2218
|
+
function shellWord(word) {
|
|
2219
|
+
if (BARE_WORD.test(word)) return word;
|
|
2220
|
+
return word === "" ? "''" : `'${word.replace(/'/g, `'\\''`)}'`;
|
|
2221
|
+
}
|
|
2222
|
+
function isStringArray(value) {
|
|
2223
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
2224
|
+
}
|
|
2225
|
+
function commandLine(args, fieldPath, value, native) {
|
|
2226
|
+
const missing = { reason: "Shell command field is missing or is not a string" };
|
|
2227
|
+
const unreadable = { reason: "Shell command arguments could not be read" };
|
|
2228
|
+
if (typeof value !== "string" && (native || !Array.isArray(value))) return missing;
|
|
2229
|
+
const dot = fieldPath.lastIndexOf(".");
|
|
2230
|
+
const parent = dot === -1 ? args : getNestedValue(args, fieldPath.slice(0, dot));
|
|
2231
|
+
const siblings = ARGV_FIELDS.map((key) => ({
|
|
2232
|
+
key,
|
|
2233
|
+
value: parent && typeof parent === "object" ? parent[key] : void 0
|
|
2234
|
+
})).filter(({ value: value2 }) => value2 !== void 0 && value2 !== null);
|
|
2235
|
+
let argv = siblings[0];
|
|
2236
|
+
if (native) argv = siblings.find(({ value: v }) => isStringArray(v));
|
|
2237
|
+
else if (siblings.length === 2 && JSON.stringify(siblings[0].value) !== JSON.stringify(siblings[1].value))
|
|
2238
|
+
return { reason: "Shell command arguments disagree" };
|
|
2239
|
+
let command;
|
|
2240
|
+
let source = fieldPath;
|
|
2241
|
+
if (Array.isArray(value)) {
|
|
2242
|
+
if (argv || !value.length || !isStringArray(value)) return unreadable;
|
|
2243
|
+
command = value.map(shellWord).join(" ");
|
|
2244
|
+
} else if (!argv) {
|
|
2245
|
+
return { command: value, fieldPath };
|
|
2246
|
+
} else {
|
|
2247
|
+
if (!isStringArray(argv.value)) return unreadable;
|
|
2248
|
+
if (!argv.value.length) return { command: value, fieldPath };
|
|
2249
|
+
command = `${value} ${argv.value.map(shellWord).join(" ")}`;
|
|
2250
|
+
source = `${fieldPath}+${argv.key}`;
|
|
2251
|
+
}
|
|
2252
|
+
if (command.length > MAX_ARGV_CHARACTERS)
|
|
2253
|
+
return { reason: "Shell command arguments exceed the inspectable size" };
|
|
2254
|
+
return { command, fieldPath: source };
|
|
2255
|
+
}
|
|
2186
2256
|
function resolveShellInput(tool, args, inspection = {}) {
|
|
2187
2257
|
const fields = shellFields(tool, inspection);
|
|
2188
2258
|
if (!fields) return { kind: "not-shell" };
|
|
2189
2259
|
const values = fields.map((fieldPath) => ({ fieldPath, value: getNestedValue(args, fieldPath) })).filter(({ value }) => value !== void 0 && value !== null && value !== "");
|
|
2190
|
-
if (!values.length
|
|
2260
|
+
if (!values.length) {
|
|
2191
2261
|
return { kind: "uninspectable", reason: "Shell command field is missing or is not a string" };
|
|
2192
2262
|
}
|
|
2193
|
-
|
|
2263
|
+
const native = isBashTool(tool);
|
|
2264
|
+
const lines = values.map(({ fieldPath, value }) => commandLine(args, fieldPath, value, native));
|
|
2265
|
+
const failed = lines.find((line) => "reason" in line);
|
|
2266
|
+
if (failed) return { kind: "uninspectable", reason: failed.reason };
|
|
2267
|
+
const commands = lines.filter(
|
|
2268
|
+
(line) => "command" in line
|
|
2269
|
+
);
|
|
2270
|
+
if (new Set(commands.map(({ command }) => command)).size !== 1) {
|
|
2194
2271
|
return { kind: "uninspectable", reason: "Multiple shell command fields disagree" };
|
|
2195
2272
|
}
|
|
2196
|
-
return { kind: "shell",
|
|
2273
|
+
return { kind: "shell", ...commands[0] };
|
|
2197
2274
|
}
|
|
2198
2275
|
function shellRuleArgs(tool, args, ruleTool, inspection = {}) {
|
|
2199
2276
|
if (!ruleTool || !matchesPattern("bash", ruleTool) || matchesPattern(tool, ruleTool)) return args;
|
|
@@ -2934,20 +3011,25 @@ function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
|
2934
3011
|
}
|
|
2935
3012
|
return best;
|
|
2936
3013
|
}
|
|
2937
|
-
function findMnemonic(text, budget = {
|
|
2938
|
-
const words = [...text.matchAll(/[a-zA-Z]+/g)];
|
|
3014
|
+
function findMnemonic(text, budget = { hashes: 0, reasons: /* @__PURE__ */ new Set() }) {
|
|
2939
3015
|
let run = [];
|
|
3016
|
+
let runIndex = [];
|
|
2940
3017
|
let previousEnd = 0;
|
|
2941
3018
|
const rejected = /* @__PURE__ */ new Set();
|
|
2942
3019
|
const inspect = () => {
|
|
3020
|
+
if (run.length < 12) return false;
|
|
3021
|
+
const ascending = new Array(run.length).fill(0);
|
|
3022
|
+
for (let i = 1; i < run.length; i++)
|
|
3023
|
+
ascending[i] = ascending[i - 1] + (runIndex[i] > runIndex[i - 1] ? 1 : 0);
|
|
2943
3024
|
for (let start = 0; start + 12 <= run.length; start++) {
|
|
2944
3025
|
for (const count of lengths) {
|
|
2945
3026
|
if (start + count > run.length) continue;
|
|
3027
|
+
if (ascending[start + count - 1] - ascending[start] === count - 1) continue;
|
|
2946
3028
|
const candidate = run.slice(start, start + count);
|
|
2947
3029
|
const sentence2 = candidate.join(" ");
|
|
2948
3030
|
if (rejected.has(sentence2)) continue;
|
|
2949
|
-
if (++budget.
|
|
2950
|
-
budget.reasons.add("mnemonic-
|
|
3031
|
+
if (++budget.hashes > MAX_MNEMONIC_HASHES) {
|
|
3032
|
+
budget.reasons.add("mnemonic-budget");
|
|
2951
3033
|
return false;
|
|
2952
3034
|
}
|
|
2953
3035
|
if (BIP39_PUBLIC_VECTORS.has(hash(sentence2).toString("hex"))) {
|
|
@@ -2965,13 +3047,18 @@ function findMnemonic(text, budget = { candidates: 0, reasons: /* @__PURE__ */ n
|
|
|
2965
3047
|
}
|
|
2966
3048
|
return false;
|
|
2967
3049
|
};
|
|
2968
|
-
for (const word of
|
|
2969
|
-
|
|
3050
|
+
for (const word of text.matchAll(/[a-zA-Z]+/g)) {
|
|
3051
|
+
const index = indices.get(word[0]);
|
|
3052
|
+
if (index === void 0 || run.length && !/^\s+$/.test(text.slice(previousEnd, word.index))) {
|
|
2970
3053
|
if (inspect()) return true;
|
|
2971
|
-
if (budget.reasons.has("mnemonic-
|
|
3054
|
+
if (budget.reasons.has("mnemonic-budget")) return false;
|
|
2972
3055
|
run = [];
|
|
3056
|
+
runIndex = [];
|
|
3057
|
+
}
|
|
3058
|
+
if (index !== void 0) {
|
|
3059
|
+
run.push(word[0]);
|
|
3060
|
+
runIndex.push(index);
|
|
2973
3061
|
}
|
|
2974
|
-
if (indices.has(word[0])) run.push(word[0]);
|
|
2975
3062
|
previousEnd = word.index + word[0].length;
|
|
2976
3063
|
}
|
|
2977
3064
|
return inspect();
|
|
@@ -3102,6 +3189,9 @@ function validateCask(raw) {
|
|
|
3102
3189
|
const second = b64urlIndex(s[tsAt + 5]);
|
|
3103
3190
|
return month <= 11 && day <= 30 && hour <= 23 && minute <= 59 && second <= 59;
|
|
3104
3191
|
}
|
|
3192
|
+
function isInvisibleChar(char) {
|
|
3193
|
+
return INVISIBLE_ONE.test(char);
|
|
3194
|
+
}
|
|
3105
3195
|
function stripInvisible(text) {
|
|
3106
3196
|
return text.replace(INVISIBLE_RE, "");
|
|
3107
3197
|
}
|
|
@@ -3167,7 +3257,7 @@ function scanInjection(text, ctx = {}) {
|
|
|
3167
3257
|
}
|
|
3168
3258
|
}
|
|
3169
3259
|
if (matched.length === 0) return null;
|
|
3170
|
-
const untrusted = !!ctx.tool && UNTRUSTED_TOOLS.test(ctx.tool);
|
|
3260
|
+
const untrusted = ctx.untrusted ?? (!!ctx.tool && UNTRUSTED_TOOLS.test(ctx.tool));
|
|
3171
3261
|
const score = matched.length + (untrusted ? 1 : 0);
|
|
3172
3262
|
const confidence = score >= 3 ? "high" : score === 2 ? "medium" : "low";
|
|
3173
3263
|
return { signals: untrusted ? [...matched, "untrusted-origin"] : matched, confidence };
|
|
@@ -3355,6 +3445,45 @@ function matchArgumentText(text, fieldPath, budget) {
|
|
|
3355
3445
|
}
|
|
3356
3446
|
return best;
|
|
3357
3447
|
}
|
|
3448
|
+
function matchArgumentBatch(fragments, fieldPath, budget) {
|
|
3449
|
+
if (!fragments.length) return null;
|
|
3450
|
+
const text = fragments.join(BATCH_SEPARATOR);
|
|
3451
|
+
const lower2 = text.toLowerCase();
|
|
3452
|
+
let best = null;
|
|
3453
|
+
for (const pattern of DLP_PATTERNS) {
|
|
3454
|
+
if (pattern.keywords && !pattern.keywords.some((kw) => lower2.includes(kw.toLowerCase())))
|
|
3455
|
+
continue;
|
|
3456
|
+
const flags = pattern.regex.flags.replace(/[gmy]/g, "");
|
|
3457
|
+
if (!new RegExp(pattern.regex.source, flags + "m").test(text)) continue;
|
|
3458
|
+
for (const fragment of fragments) {
|
|
3459
|
+
if (firstAcceptedMatch(pattern, fragment) === null) continue;
|
|
3460
|
+
const severity = pattern.contextBoost && isAssignmentContext(fragment) ? "block" : pattern.severity;
|
|
3461
|
+
if (severity === "block")
|
|
3462
|
+
return {
|
|
3463
|
+
patternName: pattern.name,
|
|
3464
|
+
fieldPath,
|
|
3465
|
+
redactedSample: "[encoded secret]",
|
|
3466
|
+
severity
|
|
3467
|
+
};
|
|
3468
|
+
best ??= {
|
|
3469
|
+
patternName: pattern.name,
|
|
3470
|
+
fieldPath,
|
|
3471
|
+
redactedSample: "[encoded secret]",
|
|
3472
|
+
severity
|
|
3473
|
+
};
|
|
3474
|
+
if (!pattern.contextBoost) break;
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
if (findMnemonic(text, budget)) {
|
|
3478
|
+
return {
|
|
3479
|
+
patternName: "BIP-39 Recovery Phrase",
|
|
3480
|
+
fieldPath,
|
|
3481
|
+
redactedSample: "[recovery phrase]",
|
|
3482
|
+
severity: "block"
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
return best;
|
|
3486
|
+
}
|
|
3358
3487
|
function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
3359
3488
|
if (depth > MAX_DEPTH2 || args === null || args === void 0) return null;
|
|
3360
3489
|
if (Array.isArray(args)) {
|
|
@@ -4323,6 +4452,9 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
4323
4452
|
return null;
|
|
4324
4453
|
}
|
|
4325
4454
|
}
|
|
4455
|
+
function newSecretViewBudget() {
|
|
4456
|
+
return { hashes: 0, nestedBytes: 0, joinedLabelBytes: 0, reasons: /* @__PURE__ */ new Set() };
|
|
4457
|
+
}
|
|
4326
4458
|
function textBytes(buf) {
|
|
4327
4459
|
if (buf.length < 16) return null;
|
|
4328
4460
|
const text = buf.toString("utf8");
|
|
@@ -4341,13 +4473,41 @@ function percentOnce(text) {
|
|
|
4341
4473
|
}
|
|
4342
4474
|
});
|
|
4343
4475
|
}
|
|
4476
|
+
function* wrappedBase64(text) {
|
|
4477
|
+
if (!text.includes("\n")) return;
|
|
4478
|
+
const word = /^[A-Za-z0-9+/_-]+={0,2}$/;
|
|
4479
|
+
let run = [];
|
|
4480
|
+
let width = 0;
|
|
4481
|
+
const flush = function* () {
|
|
4482
|
+
if (run.length >= 2) yield run.join("");
|
|
4483
|
+
run = [];
|
|
4484
|
+
width = 0;
|
|
4485
|
+
};
|
|
4486
|
+
for (const raw of text.split("\n")) {
|
|
4487
|
+
const line = raw.trim();
|
|
4488
|
+
const full = line.length >= 16 && word.test(line) && !line.includes("=");
|
|
4489
|
+
if (full && (!run.length || line.length === width)) {
|
|
4490
|
+
run.push(line);
|
|
4491
|
+
width = line.length;
|
|
4492
|
+
continue;
|
|
4493
|
+
}
|
|
4494
|
+
if (run.length && word.test(line) && line.length >= 2 && line.length <= width) {
|
|
4495
|
+
run.push(line);
|
|
4496
|
+
yield* flush();
|
|
4497
|
+
continue;
|
|
4498
|
+
}
|
|
4499
|
+
yield* flush();
|
|
4500
|
+
if (full) {
|
|
4501
|
+
run.push(line);
|
|
4502
|
+
width = line.length;
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
yield* flush();
|
|
4506
|
+
}
|
|
4344
4507
|
function* encodedRegions(text, budget) {
|
|
4345
4508
|
yield [text, false];
|
|
4509
|
+
for (const block of wrappedBase64(text)) yield [block, false];
|
|
4346
4510
|
for (const match of text.matchAll(/(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,30}:\/\/[^\s<>"']+/gi)) {
|
|
4347
|
-
if (++budget.candidates > SECRET_VIEW_LIMITS.maxCandidates) {
|
|
4348
|
-
budget.reasons.add("decode-candidates");
|
|
4349
|
-
return;
|
|
4350
|
-
}
|
|
4351
4511
|
try {
|
|
4352
4512
|
new URL(match[0]);
|
|
4353
4513
|
} catch {
|
|
@@ -4367,6 +4527,8 @@ function* encodedRegions(text, budget) {
|
|
|
4367
4527
|
];
|
|
4368
4528
|
for (const region of regions) if (region) yield [region, false];
|
|
4369
4529
|
const labels = host2.split(".");
|
|
4530
|
+
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))
|
|
4531
|
+
continue;
|
|
4370
4532
|
for (let start = 0; start < labels.length; start++) {
|
|
4371
4533
|
let joined = labels[start];
|
|
4372
4534
|
for (let end = start + 1; end < Math.min(labels.length, start + 8); end++) {
|
|
@@ -4374,11 +4536,13 @@ function* encodedRegions(text, budget) {
|
|
|
4374
4536
|
if (!run.every((s) => /^[a-f0-9]{2,}$/i.test(s)) && !run.every((s) => /^[A-Z2-7]{4,}$/i.test(s)))
|
|
4375
4537
|
break;
|
|
4376
4538
|
joined += labels[end];
|
|
4377
|
-
if (
|
|
4378
|
-
|
|
4539
|
+
if (joined.length < 16) continue;
|
|
4540
|
+
budget.joinedLabelBytes += joined.length;
|
|
4541
|
+
if (budget.joinedLabelBytes > SECRET_VIEW_LIMITS.maxJoinedLabelBytes) {
|
|
4542
|
+
budget.reasons.add("decode-labels");
|
|
4379
4543
|
return;
|
|
4380
4544
|
}
|
|
4381
|
-
|
|
4545
|
+
yield [joined, true];
|
|
4382
4546
|
}
|
|
4383
4547
|
}
|
|
4384
4548
|
}
|
|
@@ -4415,10 +4579,6 @@ function* decodedCandidates(text, budget) {
|
|
|
4415
4579
|
if (joined) yield ["dns-labels", region];
|
|
4416
4580
|
for (const [kind, re] of families) {
|
|
4417
4581
|
for (const match of region.matchAll(re)) {
|
|
4418
|
-
if (++budget.candidates > SECRET_VIEW_LIMITS.maxCandidates) {
|
|
4419
|
-
budget.reasons.add("decode-candidates");
|
|
4420
|
-
return;
|
|
4421
|
-
}
|
|
4422
4582
|
const raw = match[0];
|
|
4423
4583
|
let buf;
|
|
4424
4584
|
if (kind === "base64") {
|
|
@@ -4441,42 +4601,76 @@ function* decodedCandidates(text, budget) {
|
|
|
4441
4601
|
}
|
|
4442
4602
|
}
|
|
4443
4603
|
}
|
|
4444
|
-
function
|
|
4604
|
+
function secretViews(text, budget) {
|
|
4445
4605
|
const seen = /* @__PURE__ */ new Set([text]);
|
|
4446
|
-
const
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
for (const
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4606
|
+
const out = [];
|
|
4607
|
+
let frontier = [{ text, transforms: [] }];
|
|
4608
|
+
for (let depth = 1; depth <= SECRET_VIEW_LIMITS.maxDepth; depth++) {
|
|
4609
|
+
const next = [];
|
|
4610
|
+
for (const parent of frontier) {
|
|
4611
|
+
let perLeaf = 0;
|
|
4612
|
+
for (const [kind, decoded] of decodedCandidates(parent.text, budget)) {
|
|
4613
|
+
if (seen.has(decoded)) continue;
|
|
4614
|
+
seen.add(decoded);
|
|
4615
|
+
const normalizing = kind === "invisibles" || kind === "percent";
|
|
4616
|
+
if (depth >= 2 && !normalizing) {
|
|
4617
|
+
budget.nestedBytes += Buffer.byteLength(decoded);
|
|
4618
|
+
if (++perLeaf > SECRET_VIEW_LIMITS.maxNestedViewsPerLeaf || budget.nestedBytes > SECRET_VIEW_LIMITS.maxNestedBytes) {
|
|
4619
|
+
budget.reasons.add("decode-nested");
|
|
4620
|
+
return out;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
const view = { text: decoded, transforms: [...parent.transforms, kind] };
|
|
4624
|
+
out.push(view);
|
|
4625
|
+
next.push(view);
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
frontier = next;
|
|
4629
|
+
if (!frontier.length) return out;
|
|
4630
|
+
}
|
|
4631
|
+
for (const view of frontier) {
|
|
4632
|
+
if (!decodedCandidates(view.text, budget).next().done) {
|
|
4633
|
+
budget.reasons.add("decode-nested");
|
|
4634
|
+
break;
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
return out;
|
|
4638
|
+
}
|
|
4639
|
+
function batches(views) {
|
|
4640
|
+
const out = [];
|
|
4641
|
+
let current = [];
|
|
4642
|
+
let size = 0;
|
|
4643
|
+
for (const { text } of views) {
|
|
4644
|
+
if (current.length && size + text.length > SECRET_VIEW_LIMITS.batchCharacters) {
|
|
4645
|
+
out.push(current);
|
|
4646
|
+
current = [];
|
|
4647
|
+
size = 0;
|
|
4465
4648
|
}
|
|
4466
|
-
|
|
4649
|
+
current.push(text);
|
|
4650
|
+
size += text.length;
|
|
4467
4651
|
}
|
|
4652
|
+
if (current.length) out.push(current);
|
|
4653
|
+
return out;
|
|
4468
4654
|
}
|
|
4469
4655
|
function inspectArgumentSecrets(args) {
|
|
4470
|
-
const budget =
|
|
4656
|
+
const budget = newSecretViewBudget();
|
|
4471
4657
|
const ancestors = /* @__PURE__ */ new Set();
|
|
4472
4658
|
let nodes = 0;
|
|
4473
4659
|
let sourceBytes = 0;
|
|
4474
4660
|
let tailBytes = 0;
|
|
4475
4661
|
let best = null;
|
|
4662
|
+
const blocked = () => best?.severity === "block";
|
|
4476
4663
|
const accept = (match) => {
|
|
4477
4664
|
if (match && (!best || best.severity !== "block" && match.severity === "block")) best = match;
|
|
4478
4665
|
};
|
|
4666
|
+
const decodeSegment = (segment, fieldPath) => {
|
|
4667
|
+
for (const batch of batches(secretViews(segment, budget))) {
|
|
4668
|
+
accept(matchArgumentBatch(batch, fieldPath, budget));
|
|
4669
|
+
if (blocked()) return;
|
|
4670
|
+
}
|
|
4671
|
+
};
|
|
4479
4672
|
const visit2 = (value, depth, fieldPath) => {
|
|
4673
|
+
if (blocked()) return;
|
|
4480
4674
|
if (++nodes > ARGUMENT_SCAN_LIMITS.maxNodes) {
|
|
4481
4675
|
budget.reasons.add("argument-nodes");
|
|
4482
4676
|
return;
|
|
@@ -4518,14 +4712,11 @@ function inspectArgumentSecrets(args) {
|
|
|
4518
4712
|
}
|
|
4519
4713
|
sourceBytes += Math.min(bytes, Math.max(0, remaining));
|
|
4520
4714
|
if (!text) return;
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
if (hit)
|
|
4524
|
-
accept(view.transforms.length ? { ...hit, redactedSample: "[encoded secret]" } : hit);
|
|
4525
|
-
}
|
|
4715
|
+
accept(matchArgumentText(text, fieldPath, budget));
|
|
4716
|
+
let scannedEnd = text.length;
|
|
4526
4717
|
if (!truncated && value.length > window) {
|
|
4527
|
-
budget.reasons.add("decode-window");
|
|
4528
4718
|
for (let start = window - overlap; start < value.length; start += window - overlap) {
|
|
4719
|
+
if (blocked()) return;
|
|
4529
4720
|
const chunk2 = value.slice(start, start + window);
|
|
4530
4721
|
const chunkBytes = Buffer.byteLength(chunk2);
|
|
4531
4722
|
if (tailBytes + chunkBytes > ARGUMENT_SCAN_LIMITS.maxTailBytes) {
|
|
@@ -4534,9 +4725,16 @@ function inspectArgumentSecrets(args) {
|
|
|
4534
4725
|
}
|
|
4535
4726
|
tailBytes += chunkBytes;
|
|
4536
4727
|
accept(matchArgumentText(chunk2, fieldPath, budget));
|
|
4728
|
+
scannedEnd = start + chunk2.length;
|
|
4537
4729
|
if (start + window >= value.length) break;
|
|
4538
4730
|
}
|
|
4539
4731
|
}
|
|
4732
|
+
for (let start = 0; start < scannedEnd && !blocked(); ) {
|
|
4733
|
+
let end = Math.min(start + window, scannedEnd);
|
|
4734
|
+
while (end < scannedEnd && runChar(value, end - 1) && runChar(value, end)) end++;
|
|
4735
|
+
decodeSegment(value.slice(start, end), fieldPath);
|
|
4736
|
+
start = end;
|
|
4737
|
+
}
|
|
4540
4738
|
const trimmed = text.trim();
|
|
4541
4739
|
if (value.length < ARGUMENT_SCAN_LIMITS.maxJsonCharacters && (trimmed.startsWith("{") || trimmed.startsWith("["))) {
|
|
4542
4740
|
try {
|
|
@@ -4555,7 +4753,7 @@ function inspectArgumentSecrets(args) {
|
|
|
4555
4753
|
function scanArgumentSecrets(args) {
|
|
4556
4754
|
const result = inspectArgumentSecrets(args);
|
|
4557
4755
|
if (result.match) return result.match;
|
|
4558
|
-
return result.
|
|
4756
|
+
return result.coverage === "incomplete" ? {
|
|
4559
4757
|
patternName: "Argument inspection incomplete",
|
|
4560
4758
|
fieldPath: "args",
|
|
4561
4759
|
redactedSample: "[uninspected content]",
|
|
@@ -4583,18 +4781,13 @@ function tokenize2(toolName) {
|
|
|
4583
4781
|
return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
|
|
4584
4782
|
}
|
|
4585
4783
|
function extractShellCommand(toolName, args, toolInspection) {
|
|
4586
|
-
const
|
|
4587
|
-
|
|
4588
|
-
if (!matchingPattern) return null;
|
|
4589
|
-
const fieldPath = toolInspection[matchingPattern];
|
|
4784
|
+
const fieldPath = inspectionEntry(toolName, toolInspection);
|
|
4785
|
+
if (fieldPath === void 0) return null;
|
|
4590
4786
|
const value = getNestedValue(args, typeof fieldPath === "string" ? fieldPath : fieldPath.field);
|
|
4591
4787
|
return typeof value === "string" ? value : null;
|
|
4592
4788
|
}
|
|
4593
4789
|
function isSqlTool(toolName, toolInspection) {
|
|
4594
|
-
const
|
|
4595
|
-
const matchingPattern = patterns.find((p) => matchesPattern(toolName, p));
|
|
4596
|
-
if (!matchingPattern) return false;
|
|
4597
|
-
const fieldName = toolInspection[matchingPattern];
|
|
4790
|
+
const fieldName = inspectionEntry(toolName, toolInspection);
|
|
4598
4791
|
return fieldName === "sql" || fieldName === "query";
|
|
4599
4792
|
}
|
|
4600
4793
|
function pipeChainVerdict(command, isTrustedHost2, highAction = "review") {
|
|
@@ -4661,9 +4854,10 @@ function egressPolicyVerdict(eg) {
|
|
|
4661
4854
|
}
|
|
4662
4855
|
async function evaluatePolicy(config, toolName, args, context = {}, hooks = {}) {
|
|
4663
4856
|
let protectedVerdict = null;
|
|
4857
|
+
const layers = inspectionLayers(config.policy);
|
|
4664
4858
|
if (context.agent !== "Terminal") {
|
|
4665
4859
|
try {
|
|
4666
|
-
const accesses = context.accesses ?? collectFileAccesses(toolName, args, context,
|
|
4860
|
+
const accesses = context.accesses ?? collectFileAccesses(toolName, args, context, layers);
|
|
4667
4861
|
protectedVerdict = evaluateProtectedFileAccess(accesses, context, catalogSettings(config));
|
|
4668
4862
|
} catch {
|
|
4669
4863
|
protectedVerdict = incompleteFileVerdict();
|
|
@@ -4698,7 +4892,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4698
4892
|
hooks
|
|
4699
4893
|
);
|
|
4700
4894
|
if (result.decision === "block") return result;
|
|
4701
|
-
if (context.agent !== "Terminal" && resolveShellInput(toolName, args,
|
|
4895
|
+
if (context.agent !== "Terminal" && resolveShellInput(toolName, args, layers).kind === "uninspectable") {
|
|
4702
4896
|
return {
|
|
4703
4897
|
decision: "review",
|
|
4704
4898
|
ruleName: "shell-inspection-incomplete",
|
|
@@ -4714,13 +4908,14 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4714
4908
|
const { agent, cwd, activeEnvironment } = context;
|
|
4715
4909
|
const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2 } = hooks;
|
|
4716
4910
|
const settingsView = catalogSettings(config);
|
|
4911
|
+
const layers = inspectionLayers(config.policy);
|
|
4717
4912
|
const cv = (id) => checkValue(settingsView, id);
|
|
4718
4913
|
let loggedFinding;
|
|
4719
4914
|
const noteLogged = (v) => {
|
|
4720
4915
|
if (v?.logged && !loggedFinding) loggedFinding = v;
|
|
4721
4916
|
};
|
|
4722
4917
|
const allowed = () => loggedFinding ?? { decision: "allow" };
|
|
4723
|
-
const shellInput = resolveShellInput(toolName, args,
|
|
4918
|
+
const shellInput = resolveShellInput(toolName, args, layers);
|
|
4724
4919
|
const wouldBeIgnored = matchesPattern(toolName, config.policy.ignoredTools);
|
|
4725
4920
|
{
|
|
4726
4921
|
const dest = ssrfDestinationFloor(toolName, args, {
|
|
@@ -4825,13 +5020,10 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4825
5020
|
return { id, value: cv(id) };
|
|
4826
5021
|
};
|
|
4827
5022
|
const matches = config.policy.smartRules.filter((rule) => {
|
|
4828
|
-
if (!toolMatchesRule(toolName, rule.tool,
|
|
5023
|
+
if (!toolMatchesRule(toolName, rule.tool, layers)) return false;
|
|
4829
5024
|
if (astSuppressed(rule)) return false;
|
|
4830
5025
|
if (rmCleanupWaiver && rule.name === "review-rm" && rule.verdict === "review") return false;
|
|
4831
|
-
if (!evaluateSmartConditions(
|
|
4832
|
-
shellRuleArgs(toolName, args, rule.tool, config.policy.toolInspection),
|
|
4833
|
-
rule
|
|
4834
|
-
))
|
|
5026
|
+
if (!evaluateSmartConditions(shellRuleArgs(toolName, args, rule.tool, layers), rule))
|
|
4835
5027
|
return false;
|
|
4836
5028
|
const check = ruleCheck(rule);
|
|
4837
5029
|
if (check?.value === "off") return false;
|
|
@@ -4878,7 +5070,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4878
5070
|
let allTokens = [];
|
|
4879
5071
|
let pathTokens = [];
|
|
4880
5072
|
if (pendingToolEgress) return pendingToolEgress;
|
|
4881
|
-
const shellCommand = shellInput.kind === "shell" ? shellInput.command : shellInput.kind === "not-shell" ? extractShellCommand(toolName, args,
|
|
5073
|
+
const shellCommand = shellInput.kind === "shell" ? shellInput.command : shellInput.kind === "not-shell" ? extractShellCommand(toolName, args, layers) : null;
|
|
4882
5074
|
if (shellCommand) {
|
|
4883
5075
|
const analyzed = analyzeShellCommand(shellCommand);
|
|
4884
5076
|
allTokens = analyzed.allTokens;
|
|
@@ -4955,7 +5147,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4955
5147
|
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
4956
5148
|
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
4957
5149
|
const sshHosts = extractAllSshHosts(rawTokens.slice(1));
|
|
4958
|
-
allTokens.
|
|
5150
|
+
allTokens = allTokens.concat(sshHosts);
|
|
4959
5151
|
}
|
|
4960
5152
|
if (firstToken && firstToken.startsWith("/") && checkProvenance2) {
|
|
4961
5153
|
const prov = checkProvenance2(firstToken, cwd);
|
|
@@ -4984,7 +5176,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4984
5176
|
};
|
|
4985
5177
|
}
|
|
4986
5178
|
}
|
|
4987
|
-
if (isSqlTool(toolName,
|
|
5179
|
+
if (isSqlTool(toolName, layers)) {
|
|
4988
5180
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
|
|
4989
5181
|
}
|
|
4990
5182
|
} else {
|
|
@@ -4992,7 +5184,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4992
5184
|
if (args && typeof args === "object") {
|
|
4993
5185
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
4994
5186
|
const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
|
|
4995
|
-
allTokens.
|
|
5187
|
+
allTokens = allTokens.concat(extraTokens);
|
|
4996
5188
|
}
|
|
4997
5189
|
}
|
|
4998
5190
|
const isManual = agent === "Terminal";
|
|
@@ -6287,7 +6479,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
6287
6479
|
}
|
|
6288
6480
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
6289
6481
|
}
|
|
6290
|
-
var import_picomatch, import_safe_regex2, import_picomatch2, import_mvdan_sh, import_safe_regex22, import_mvdan_sh2, import_safe_regex23, import_crypto2, import_crypto3, import_mvdan_sh3, import_mvdan_sh4, import_crypto4, 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;
|
|
6482
|
+
var import_picomatch, import_safe_regex2, import_picomatch2, import_mvdan_sh, import_safe_regex22, import_mvdan_sh2, import_safe_regex23, import_crypto2, import_crypto3, import_mvdan_sh3, import_mvdan_sh4, import_crypto4, 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;
|
|
6291
6483
|
var init_dist = __esm({
|
|
6292
6484
|
"packages/policy-engine/dist/index.mjs"() {
|
|
6293
6485
|
"use strict";
|
|
@@ -8672,7 +8864,23 @@ var init_dist = __esm({
|
|
|
8672
8864
|
"shell",
|
|
8673
8865
|
"exec_command"
|
|
8674
8866
|
]);
|
|
8675
|
-
MCP_EXECUTORS = /* @__PURE__ */ new Set([
|
|
8867
|
+
MCP_EXECUTORS = /* @__PURE__ */ new Set([
|
|
8868
|
+
...BASH_TOOL_NAMES,
|
|
8869
|
+
"exec",
|
|
8870
|
+
"run_command",
|
|
8871
|
+
"terminal",
|
|
8872
|
+
"execute_command",
|
|
8873
|
+
"execute_shell",
|
|
8874
|
+
"execute_shell_command",
|
|
8875
|
+
"shell_execute",
|
|
8876
|
+
"run_shell",
|
|
8877
|
+
"run_terminal_command",
|
|
8878
|
+
"start_process"
|
|
8879
|
+
]);
|
|
8880
|
+
BUILTIN_FIELDS = ["command", "cmd", "script"];
|
|
8881
|
+
ARGV_FIELDS = ["args", "argv"];
|
|
8882
|
+
BARE_WORD = /^[A-Za-z0-9_./+:@%,-]+$/;
|
|
8883
|
+
MAX_ARGV_CHARACTERS = 262144;
|
|
8676
8884
|
({ syntax: syntax2 } = import_mvdan_sh2.default);
|
|
8677
8885
|
MAX_ALTERNATIVES = 64;
|
|
8678
8886
|
MAX_DEPTH = 64;
|
|
@@ -10798,6 +11006,7 @@ zoo`.split("\n");
|
|
|
10798
11006
|
indices = new Map(BIP39_ENGLISH.map((word, i) => [word, i]));
|
|
10799
11007
|
lengths = [24, 21, 18, 15, 12];
|
|
10800
11008
|
hash = (value) => (0, import_crypto2.createHash)("sha256").update(value).digest();
|
|
11009
|
+
MAX_MNEMONIC_HASHES = 25e3;
|
|
10801
11010
|
IBAN_LENGTH = {
|
|
10802
11011
|
AD: 24,
|
|
10803
11012
|
AE: 23,
|
|
@@ -10905,6 +11114,7 @@ zoo`.split("\n");
|
|
|
10905
11114
|
CASK_TWO_ZERO_SUFFIX = /* @__PURE__ */ new Set([..."AEIMQUYcgkosw048"]);
|
|
10906
11115
|
CASK_FOUR_ZERO_SUFFIX = /* @__PURE__ */ new Set([..."AQgw"]);
|
|
10907
11116
|
INVISIBLE_RE = /[͏----︀-️]|[\u{E0000}-\u{E007F}]/gu;
|
|
11117
|
+
INVISIBLE_ONE = new RegExp(`^(?:${INVISIBLE_RE.source.split("|")[0]})$`, "u");
|
|
10908
11118
|
HOMOGLYPHS = {
|
|
10909
11119
|
// Cyrillic lowercase
|
|
10910
11120
|
\u0430: "a",
|
|
@@ -11611,6 +11821,7 @@ zoo`.split("\n");
|
|
|
11611
11821
|
/** Max nesting depth walked; anything deeper is NOT scanned. */
|
|
11612
11822
|
maxDepth: MAX_DEPTH2
|
|
11613
11823
|
};
|
|
11824
|
+
BATCH_SEPARATOR = "\n";
|
|
11614
11825
|
({ syntax: syntax3 } = import_mvdan_sh3.default);
|
|
11615
11826
|
NPM_INSTALL_VERBS = /* @__PURE__ */ new Set([
|
|
11616
11827
|
"install",
|
|
@@ -12080,16 +12291,24 @@ zoo`.split("\n");
|
|
|
12080
12291
|
["browser_batch", ["actions[].input.url"]]
|
|
12081
12292
|
]);
|
|
12082
12293
|
SECRET_VIEW_LIMITS = {
|
|
12294
|
+
/** A decoded text that itself decodes is followed this many layers; a deeper layer is reviewed. */
|
|
12083
12295
|
maxDepth: 4,
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12296
|
+
/** Nested views per parent view, and nested text per call; past either the call is reviewed. */
|
|
12297
|
+
maxNestedViewsPerLeaf: 64,
|
|
12298
|
+
maxNestedBytes: 1048576,
|
|
12299
|
+
/** Joined hostname-label text per call; past it the call is reviewed. */
|
|
12300
|
+
maxJoinedLabelBytes: 1048576,
|
|
12301
|
+
/** Decoded fragments are matched in batches of about this many characters. */
|
|
12302
|
+
batchCharacters: 65536,
|
|
12303
|
+
/** Only a valid DNS name has its labels joined. */
|
|
12304
|
+
maxHostLength: 253,
|
|
12305
|
+
maxLabelLength: 63,
|
|
12306
|
+
maxLabels: 127
|
|
12088
12307
|
};
|
|
12089
12308
|
ARGUMENT_SCAN_LIMITS = {
|
|
12090
12309
|
maxDepth: 12,
|
|
12091
12310
|
maxNodes: 16384,
|
|
12092
|
-
/**
|
|
12311
|
+
/** Text is matched and decoded in windows of this size. */
|
|
12093
12312
|
maxStringCharacters: 1e5,
|
|
12094
12313
|
/** Windows overlap by this much so a token on a window edge is still seen whole. */
|
|
12095
12314
|
windowOverlap: 4096,
|
|
@@ -12099,6 +12318,11 @@ zoo`.split("\n");
|
|
|
12099
12318
|
maxTailBytes: 4194304,
|
|
12100
12319
|
maxJsonCharacters: 1e4
|
|
12101
12320
|
};
|
|
12321
|
+
RUN_CHAR = /[A-Za-z0-9+/=_%\\.-]/;
|
|
12322
|
+
runChar = (value, index) => {
|
|
12323
|
+
const char = value[index] ?? "";
|
|
12324
|
+
return RUN_CHAR.test(char) || char !== "" && isInvisibleChar(char);
|
|
12325
|
+
};
|
|
12102
12326
|
VERDICT_RANK = {
|
|
12103
12327
|
allow: 0,
|
|
12104
12328
|
review: 1,
|
|
@@ -12358,7 +12582,7 @@ zoo`.split("\n");
|
|
|
12358
12582
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
12359
12583
|
];
|
|
12360
12584
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
12361
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
12585
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v26";
|
|
12362
12586
|
DEDUPE_PREVIEW_LEN = 120;
|
|
12363
12587
|
ENGINE_VERSION = "1.4.0";
|
|
12364
12588
|
}
|
|
@@ -12484,6 +12708,10 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
12484
12708
|
taintFromEid: meta.taintFromEid,
|
|
12485
12709
|
...meta.taintSource && { taintSource: meta.taintSource }
|
|
12486
12710
|
} : {};
|
|
12711
|
+
const exposureFields = {
|
|
12712
|
+
...meta?.exposure ? { exposure: meta.exposure } : {},
|
|
12713
|
+
...meta?.exposureFromEid ? { exposureFromEid: meta.exposureFromEid } : {}
|
|
12714
|
+
};
|
|
12487
12715
|
const eid = generateEventId();
|
|
12488
12716
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
12489
12717
|
// eid first: the outbox shipper dedups on it, and a fixed leading field
|
|
@@ -12506,6 +12734,7 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
12506
12734
|
...loopCountField,
|
|
12507
12735
|
...transcriptPathField,
|
|
12508
12736
|
...taintFields,
|
|
12737
|
+
...exposureFields,
|
|
12509
12738
|
...testRun,
|
|
12510
12739
|
agent: meta?.agent,
|
|
12511
12740
|
mcpServer: meta?.mcpServer,
|
|
@@ -12538,6 +12767,82 @@ var init_audit = __esm({
|
|
|
12538
12767
|
}
|
|
12539
12768
|
});
|
|
12540
12769
|
|
|
12770
|
+
// src/auth/trusted-recipients.ts
|
|
12771
|
+
function getTrustedRecipientsPath() {
|
|
12772
|
+
return import_path2.default.join(import_os2.default.homedir(), ".node9", "trusted-recipients.json");
|
|
12773
|
+
}
|
|
12774
|
+
function readTrustedRecipients() {
|
|
12775
|
+
try {
|
|
12776
|
+
const raw = import_fs2.default.readFileSync(getTrustedRecipientsPath(), "utf8");
|
|
12777
|
+
const parsed = JSON.parse(raw);
|
|
12778
|
+
return Array.isArray(parsed.entries) ? parsed.entries.filter((e) => e && typeof e.value === "string") : [];
|
|
12779
|
+
} catch {
|
|
12780
|
+
return [];
|
|
12781
|
+
}
|
|
12782
|
+
}
|
|
12783
|
+
function writeTrustedRecipients(entries) {
|
|
12784
|
+
const filePath = getTrustedRecipientsPath();
|
|
12785
|
+
import_fs2.default.mkdirSync(import_path2.default.dirname(filePath), { recursive: true });
|
|
12786
|
+
const tmp = filePath + ".node9-tmp";
|
|
12787
|
+
import_fs2.default.writeFileSync(tmp, JSON.stringify({ entries }, null, 2), { mode: 384 });
|
|
12788
|
+
import_fs2.default.renameSync(tmp, filePath);
|
|
12789
|
+
}
|
|
12790
|
+
function normalizeRecipient(raw) {
|
|
12791
|
+
const v = raw.trim();
|
|
12792
|
+
if (!v) return null;
|
|
12793
|
+
if (/^(email|host|iban):/i.test(v)) {
|
|
12794
|
+
const i = v.indexOf(":");
|
|
12795
|
+
const kind = v.slice(0, i).toLowerCase();
|
|
12796
|
+
const inner = normalizeRecipient(v.slice(i + 1));
|
|
12797
|
+
return inner && inner.startsWith(`${kind}:`) ? inner : null;
|
|
12798
|
+
}
|
|
12799
|
+
const found = extractArgDestinations({ to: v });
|
|
12800
|
+
return found.length === 1 ? found[0] : null;
|
|
12801
|
+
}
|
|
12802
|
+
function addTrustedRecipient(raw, addedBy = "user") {
|
|
12803
|
+
const value = normalizeRecipient(raw);
|
|
12804
|
+
if (!value) return null;
|
|
12805
|
+
const entries = readTrustedRecipients();
|
|
12806
|
+
if (!entries.some((e) => e.value === value)) {
|
|
12807
|
+
entries.push({ value, addedAt: Date.now(), addedBy });
|
|
12808
|
+
writeTrustedRecipients(entries);
|
|
12809
|
+
}
|
|
12810
|
+
return value;
|
|
12811
|
+
}
|
|
12812
|
+
function removeTrustedRecipient(raw) {
|
|
12813
|
+
const value = normalizeRecipient(raw) ?? raw.trim();
|
|
12814
|
+
const entries = readTrustedRecipients();
|
|
12815
|
+
const next = entries.filter((e) => e.value !== value);
|
|
12816
|
+
if (next.length === entries.length) return false;
|
|
12817
|
+
writeTrustedRecipients(next);
|
|
12818
|
+
return true;
|
|
12819
|
+
}
|
|
12820
|
+
function mtimeOf() {
|
|
12821
|
+
try {
|
|
12822
|
+
return import_fs2.default.statSync(getTrustedRecipientsPath()).mtimeMs;
|
|
12823
|
+
} catch {
|
|
12824
|
+
return 0;
|
|
12825
|
+
}
|
|
12826
|
+
}
|
|
12827
|
+
function isTrustedRecipient(value) {
|
|
12828
|
+
const mtime = mtimeOf();
|
|
12829
|
+
if (!cache || cache.mtime !== mtime) {
|
|
12830
|
+
cache = { mtime, values: new Set(readTrustedRecipients().map((e) => e.value)) };
|
|
12831
|
+
}
|
|
12832
|
+
return cache.values.has(value);
|
|
12833
|
+
}
|
|
12834
|
+
var import_fs2, import_path2, import_os2, cache;
|
|
12835
|
+
var init_trusted_recipients = __esm({
|
|
12836
|
+
"src/auth/trusted-recipients.ts"() {
|
|
12837
|
+
"use strict";
|
|
12838
|
+
import_fs2 = __toESM(require("fs"));
|
|
12839
|
+
import_path2 = __toESM(require("path"));
|
|
12840
|
+
import_os2 = __toESM(require("os"));
|
|
12841
|
+
init_dist();
|
|
12842
|
+
cache = null;
|
|
12843
|
+
}
|
|
12844
|
+
});
|
|
12845
|
+
|
|
12541
12846
|
// src/config-schema.ts
|
|
12542
12847
|
function formatIssues(issues) {
|
|
12543
12848
|
const lines = issues.map((issue) => {
|
|
@@ -12815,7 +13120,7 @@ function loadUserShields() {
|
|
|
12815
13120
|
const result = {};
|
|
12816
13121
|
let entries;
|
|
12817
13122
|
try {
|
|
12818
|
-
entries =
|
|
13123
|
+
entries = import_fs3.default.readdirSync(USER_SHIELDS_DIR).filter((f) => f.endsWith(".json"));
|
|
12819
13124
|
} catch (err2) {
|
|
12820
13125
|
if (err2.code !== "ENOENT") {
|
|
12821
13126
|
process.stderr.write(
|
|
@@ -12826,9 +13131,9 @@ function loadUserShields() {
|
|
|
12826
13131
|
return result;
|
|
12827
13132
|
}
|
|
12828
13133
|
for (const file2 of entries) {
|
|
12829
|
-
const filePath =
|
|
13134
|
+
const filePath = import_path3.default.join(USER_SHIELDS_DIR, file2);
|
|
12830
13135
|
try {
|
|
12831
|
-
const raw = JSON.parse(
|
|
13136
|
+
const raw = JSON.parse(import_fs3.default.readFileSync(filePath, "utf-8"));
|
|
12832
13137
|
const v = validateShieldDefinition(raw);
|
|
12833
13138
|
if ("error" in v) {
|
|
12834
13139
|
process.stderr.write(`[node9] ${v.error}: ${filePath}
|
|
@@ -12863,7 +13168,7 @@ function listShields() {
|
|
|
12863
13168
|
}
|
|
12864
13169
|
function readShieldsFile() {
|
|
12865
13170
|
try {
|
|
12866
|
-
const raw =
|
|
13171
|
+
const raw = import_fs3.default.readFileSync(SHIELDS_STATE_FILE, "utf-8");
|
|
12867
13172
|
if (!raw.trim()) return { active: [] };
|
|
12868
13173
|
const parsed = JSON.parse(raw);
|
|
12869
13174
|
const active = Array.isArray(parsed.active) ? parsed.active.filter(
|
|
@@ -12884,12 +13189,12 @@ function readShieldsFile() {
|
|
|
12884
13189
|
}
|
|
12885
13190
|
}
|
|
12886
13191
|
function writeShieldsFile(data) {
|
|
12887
|
-
|
|
13192
|
+
import_fs3.default.mkdirSync(import_path3.default.dirname(SHIELDS_STATE_FILE), { recursive: true });
|
|
12888
13193
|
const tmp = `${SHIELDS_STATE_FILE}.${import_crypto6.default.randomBytes(6).toString("hex")}.tmp`;
|
|
12889
13194
|
const toWrite = { active: data.active };
|
|
12890
13195
|
if (data.overrides && Object.keys(data.overrides).length > 0) toWrite.overrides = data.overrides;
|
|
12891
|
-
|
|
12892
|
-
|
|
13196
|
+
import_fs3.default.writeFileSync(tmp, JSON.stringify(toWrite, null, 2), { mode: 384 });
|
|
13197
|
+
import_fs3.default.renameSync(tmp, SHIELDS_STATE_FILE);
|
|
12893
13198
|
}
|
|
12894
13199
|
function readActiveShields() {
|
|
12895
13200
|
return readShieldsFile().active;
|
|
@@ -12973,25 +13278,25 @@ function installShield(name, shieldJson) {
|
|
|
12973
13278
|
if (v.ok.name !== name) {
|
|
12974
13279
|
throw new Error(`Shield name mismatch: file declares '${v.ok.name}' but expected '${name}'`);
|
|
12975
13280
|
}
|
|
12976
|
-
|
|
12977
|
-
const filePath =
|
|
13281
|
+
import_fs3.default.mkdirSync(USER_SHIELDS_DIR, { recursive: true });
|
|
13282
|
+
const filePath = import_path3.default.join(USER_SHIELDS_DIR, `${name}.json`);
|
|
12978
13283
|
const tmp = `${filePath}.${import_crypto6.default.randomBytes(6).toString("hex")}.tmp`;
|
|
12979
|
-
|
|
12980
|
-
|
|
13284
|
+
import_fs3.default.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
|
|
13285
|
+
import_fs3.default.renameSync(tmp, filePath);
|
|
12981
13286
|
}
|
|
12982
|
-
var
|
|
13287
|
+
var import_fs3, import_path3, import_os3, import_crypto6, USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS, USER_SHIELDS_DIR_PATH;
|
|
12983
13288
|
var init_shields = __esm({
|
|
12984
13289
|
"src/shields.ts"() {
|
|
12985
13290
|
"use strict";
|
|
12986
|
-
|
|
12987
|
-
|
|
12988
|
-
|
|
13291
|
+
import_fs3 = __toESM(require("fs"));
|
|
13292
|
+
import_path3 = __toESM(require("path"));
|
|
13293
|
+
import_os3 = __toESM(require("os"));
|
|
12989
13294
|
import_crypto6 = __toESM(require("crypto"));
|
|
12990
13295
|
init_dist();
|
|
12991
13296
|
init_dist();
|
|
12992
|
-
USER_SHIELDS_DIR =
|
|
13297
|
+
USER_SHIELDS_DIR = import_path3.default.join(import_os3.default.homedir(), ".node9", "shields");
|
|
12993
13298
|
SHIELDS = buildSHIELDS();
|
|
12994
|
-
SHIELDS_STATE_FILE =
|
|
13299
|
+
SHIELDS_STATE_FILE = import_path3.default.join(import_os3.default.homedir(), ".node9", "shields.json");
|
|
12995
13300
|
RULE_KEY_MIGRATIONS = [
|
|
12996
13301
|
// 2026-05-21 — project-jail .env reads promoted review → block. Renamed
|
|
12997
13302
|
// so the key honestly describes the verdict. Credential-file rules
|
|
@@ -13226,11 +13531,11 @@ var init_build = __esm({
|
|
|
13226
13531
|
|
|
13227
13532
|
// src/auth/trusted-hosts.ts
|
|
13228
13533
|
function getTrustedHostsPath() {
|
|
13229
|
-
return
|
|
13534
|
+
return import_path4.default.join(import_os4.default.homedir(), ".node9", "trusted-hosts.json");
|
|
13230
13535
|
}
|
|
13231
13536
|
function readTrustedHosts() {
|
|
13232
13537
|
try {
|
|
13233
|
-
const raw =
|
|
13538
|
+
const raw = import_fs4.default.readFileSync(getTrustedHostsPath(), "utf8");
|
|
13234
13539
|
const parsed = JSON.parse(raw);
|
|
13235
13540
|
return Array.isArray(parsed.hosts) ? parsed.hosts : [];
|
|
13236
13541
|
} catch {
|
|
@@ -13239,7 +13544,7 @@ function readTrustedHosts() {
|
|
|
13239
13544
|
}
|
|
13240
13545
|
function getFileMtime() {
|
|
13241
13546
|
try {
|
|
13242
|
-
return
|
|
13547
|
+
return import_fs4.default.statSync(getTrustedHostsPath()).mtimeMs;
|
|
13243
13548
|
} catch {
|
|
13244
13549
|
return 0;
|
|
13245
13550
|
}
|
|
@@ -13256,10 +13561,10 @@ function getCachedHosts() {
|
|
|
13256
13561
|
}
|
|
13257
13562
|
function writeTrustedHosts(hosts) {
|
|
13258
13563
|
const filePath = getTrustedHostsPath();
|
|
13259
|
-
|
|
13564
|
+
import_fs4.default.mkdirSync(import_path4.default.dirname(filePath), { recursive: true });
|
|
13260
13565
|
const tmp = filePath + ".node9-tmp";
|
|
13261
|
-
|
|
13262
|
-
|
|
13566
|
+
import_fs4.default.writeFileSync(tmp, JSON.stringify({ hosts }, null, 2), { mode: 384 });
|
|
13567
|
+
import_fs4.default.renameSync(tmp, filePath);
|
|
13263
13568
|
_cache = { hosts, expiry: Date.now() + CACHE_TTL_MS, mtime: getFileMtime() };
|
|
13264
13569
|
}
|
|
13265
13570
|
function addTrustedHost(host2) {
|
|
@@ -13304,13 +13609,13 @@ function isTrustedHost(host2) {
|
|
|
13304
13609
|
getCachedHosts().map((entry) => entry.host)
|
|
13305
13610
|
);
|
|
13306
13611
|
}
|
|
13307
|
-
var
|
|
13612
|
+
var import_fs4, import_path4, import_os4, _cache, CACHE_TTL_MS;
|
|
13308
13613
|
var init_trusted_hosts = __esm({
|
|
13309
13614
|
"src/auth/trusted-hosts.ts"() {
|
|
13310
13615
|
"use strict";
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13616
|
+
import_fs4 = __toESM(require("fs"));
|
|
13617
|
+
import_path4 = __toESM(require("path"));
|
|
13618
|
+
import_os4 = __toESM(require("os"));
|
|
13314
13619
|
_cache = null;
|
|
13315
13620
|
CACHE_TTL_MS = 5e3;
|
|
13316
13621
|
}
|
|
@@ -13589,9 +13894,9 @@ function _resetConfigCache() {
|
|
|
13589
13894
|
}
|
|
13590
13895
|
function getGlobalSettings() {
|
|
13591
13896
|
try {
|
|
13592
|
-
const globalConfigPath2 =
|
|
13593
|
-
if (
|
|
13594
|
-
const raw = JSON.parse(
|
|
13897
|
+
const globalConfigPath2 = import_path5.default.join(import_os5.default.homedir(), ".node9", "config.json");
|
|
13898
|
+
if (import_fs5.default.existsSync(globalConfigPath2)) {
|
|
13899
|
+
const raw = JSON.parse(import_fs5.default.readFileSync(globalConfigPath2, "utf-8"));
|
|
13595
13900
|
const parsed = isV2File(raw) ? v2ToLegacy(raw).legacy : raw;
|
|
13596
13901
|
const settings = parsed.settings || {};
|
|
13597
13902
|
return {
|
|
@@ -13620,8 +13925,8 @@ function noteRejectedApiUrl(raw) {
|
|
|
13620
13925
|
if (rejectedApiUrlsSeen.has(key)) return;
|
|
13621
13926
|
rejectedApiUrlsSeen.add(key);
|
|
13622
13927
|
try {
|
|
13623
|
-
|
|
13624
|
-
|
|
13928
|
+
import_fs5.default.appendFileSync(
|
|
13929
|
+
import_path5.default.join(import_os5.default.homedir(), ".node9", "hook-debug.log"),
|
|
13625
13930
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] REFUSED apiUrl, using the default instead: ${String(raw).slice(0, 200).replace(/[\r\n]+/g, " ")}
|
|
13626
13931
|
`
|
|
13627
13932
|
);
|
|
@@ -13638,9 +13943,9 @@ function getCredentials() {
|
|
|
13638
13943
|
};
|
|
13639
13944
|
}
|
|
13640
13945
|
try {
|
|
13641
|
-
const credPath =
|
|
13642
|
-
if (
|
|
13643
|
-
const creds = JSON.parse(
|
|
13946
|
+
const credPath = import_path5.default.join(import_os5.default.homedir(), ".node9", "credentials.json");
|
|
13947
|
+
if (import_fs5.default.existsSync(credPath)) {
|
|
13948
|
+
const creds = JSON.parse(import_fs5.default.readFileSync(credPath, "utf-8"));
|
|
13644
13949
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
13645
13950
|
const profile = creds[profileName];
|
|
13646
13951
|
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
@@ -13672,7 +13977,7 @@ function readRulesCacheResilient(cacheFile) {
|
|
|
13672
13977
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
13673
13978
|
let content;
|
|
13674
13979
|
try {
|
|
13675
|
-
content =
|
|
13980
|
+
content = import_fs5.default.readFileSync(cacheFile, "utf-8");
|
|
13676
13981
|
existed = true;
|
|
13677
13982
|
} catch (err2) {
|
|
13678
13983
|
if (err2.code === "ENOENT") return {};
|
|
@@ -13687,10 +13992,10 @@ function readRulesCacheResilient(cacheFile) {
|
|
|
13687
13992
|
}
|
|
13688
13993
|
}
|
|
13689
13994
|
if (existed || sawReadError) {
|
|
13690
|
-
const backup =
|
|
13995
|
+
const backup = import_path5.default.join(import_path5.default.dirname(cacheFile), "rules-cache.last-good.json");
|
|
13691
13996
|
if (backup !== cacheFile) {
|
|
13692
13997
|
try {
|
|
13693
|
-
const raw = JSON.parse(
|
|
13998
|
+
const raw = JSON.parse(import_fs5.default.readFileSync(backup, "utf-8"));
|
|
13694
13999
|
logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
|
|
13695
14000
|
lastParsedRulesCache = raw;
|
|
13696
14001
|
return raw;
|
|
@@ -13710,8 +14015,8 @@ function logCacheReadIssue(cacheFile, kind) {
|
|
|
13710
14015
|
if (now - cacheReadLastLoggedAt < CACHE_LOG_REARM_MS) return;
|
|
13711
14016
|
cacheReadLastLoggedAt = now;
|
|
13712
14017
|
try {
|
|
13713
|
-
|
|
13714
|
-
|
|
14018
|
+
import_fs5.default.appendFileSync(
|
|
14019
|
+
import_path5.default.join(import_os5.default.homedir(), ".node9", "hook-debug.log"),
|
|
13715
14020
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
|
|
13716
14021
|
`
|
|
13717
14022
|
);
|
|
@@ -13723,8 +14028,8 @@ function catalogSettingsFrom(settings, policy) {
|
|
|
13723
14028
|
}
|
|
13724
14029
|
function getConfig(cwd) {
|
|
13725
14030
|
if (!cwd && cachedConfig) return cachedConfig;
|
|
13726
|
-
const globalPath =
|
|
13727
|
-
const projectPath =
|
|
14031
|
+
const globalPath = import_path5.default.join(import_os5.default.homedir(), ".node9", "config.json");
|
|
14032
|
+
const projectPath = import_path5.default.join(cwd ?? process.cwd(), "node9.config.json");
|
|
13728
14033
|
const globalConfig = tryLoadConfig(globalPath);
|
|
13729
14034
|
const projectConfig = tryLoadConfig(projectPath, { project: true });
|
|
13730
14035
|
const mergedSettings = {
|
|
@@ -13737,6 +14042,7 @@ function getConfig(cwd) {
|
|
|
13737
14042
|
dangerousWords: [...DEFAULT_CONFIG.policy.dangerousWords],
|
|
13738
14043
|
ignoredTools: [...DEFAULT_CONFIG.policy.ignoredTools],
|
|
13739
14044
|
toolInspection: { ...DEFAULT_CONFIG.policy.toolInspection },
|
|
14045
|
+
projectToolInspection: {},
|
|
13740
14046
|
// Shipped rules carry `builtin`: the catalog's per-check value governs
|
|
13741
14047
|
// them and only them (a user rule that reuses a name keeps its verdict).
|
|
13742
14048
|
smartRules: DEFAULT_CONFIG.policy.smartRules.map((r) => ({ ...r, builtin: true })),
|
|
@@ -13763,7 +14069,8 @@ function getConfig(cwd) {
|
|
|
13763
14069
|
untrustedContent: {
|
|
13764
14070
|
...DEFAULT_CONFIG.policy.untrustedContent,
|
|
13765
14071
|
orgDomains: [...DEFAULT_CONFIG.policy.untrustedContent.orgDomains],
|
|
13766
|
-
roles: {}
|
|
14072
|
+
roles: {},
|
|
14073
|
+
trustedRecipients: []
|
|
13767
14074
|
},
|
|
13768
14075
|
// Left empty on purpose: the local file is read fresh at policy-eval time
|
|
13769
14076
|
// (getCachedHosts via isTrustedHost), NOT snapshotted into the frozen config
|
|
@@ -13809,8 +14116,14 @@ function getConfig(cwd) {
|
|
|
13809
14116
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
13810
14117
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
13811
14118
|
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
13812
|
-
if (p.toolInspection)
|
|
13813
|
-
|
|
14119
|
+
if (p.toolInspection) {
|
|
14120
|
+
if (isProject)
|
|
14121
|
+
mergedPolicy.projectToolInspection = {
|
|
14122
|
+
...mergedPolicy.projectToolInspection,
|
|
14123
|
+
...p.toolInspection
|
|
14124
|
+
};
|
|
14125
|
+
else mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
|
|
14126
|
+
}
|
|
13814
14127
|
if (p.smartRules) {
|
|
13815
14128
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
13816
14129
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
@@ -13958,7 +14271,7 @@ function getConfig(cwd) {
|
|
|
13958
14271
|
let cloudMandatesEnforcement = false;
|
|
13959
14272
|
let cloudMandatesAppPerm = false;
|
|
13960
14273
|
{
|
|
13961
|
-
const cacheFile =
|
|
14274
|
+
const cacheFile = import_path5.default.join(import_os5.default.homedir(), ".node9", "rules-cache.json");
|
|
13962
14275
|
try {
|
|
13963
14276
|
const raw = readRulesCacheResilient(cacheFile);
|
|
13964
14277
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
@@ -14351,10 +14664,10 @@ function getConfig(cwd) {
|
|
|
14351
14664
|
}
|
|
14352
14665
|
function tryLoadConfig(filePath, options = {}) {
|
|
14353
14666
|
v2ChecksByPath.delete(filePath);
|
|
14354
|
-
if (!
|
|
14667
|
+
if (!import_fs5.default.existsSync(filePath)) return null;
|
|
14355
14668
|
let raw;
|
|
14356
14669
|
try {
|
|
14357
|
-
raw = JSON.parse(
|
|
14670
|
+
raw = JSON.parse(import_fs5.default.readFileSync(filePath, "utf-8"));
|
|
14358
14671
|
} catch (err2) {
|
|
14359
14672
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
14360
14673
|
process.stderr.write(
|
|
@@ -14429,6 +14742,13 @@ function applyUntrustedContent(cur, raw, replaceLists = false, isProject = false
|
|
|
14429
14742
|
if (replaceLists) cur.orgDomains = domains;
|
|
14430
14743
|
else for (const d of domains) if (!cur.orgDomains.includes(d)) cur.orgDomains.push(d);
|
|
14431
14744
|
}
|
|
14745
|
+
if (replaceLists && Array.isArray(uc.trustedRecipients)) {
|
|
14746
|
+
cur.trustedRecipients = [
|
|
14747
|
+
...new Set(
|
|
14748
|
+
uc.trustedRecipients.filter((v) => typeof v === "string").map((v) => normalizeRecipient(v)).filter((v) => v !== null)
|
|
14749
|
+
)
|
|
14750
|
+
].slice(0, 200);
|
|
14751
|
+
}
|
|
14432
14752
|
if (uc.roles && typeof uc.roles === "object" && !Array.isArray(uc.roles)) {
|
|
14433
14753
|
const next = replaceLists ? {} : cur.roles;
|
|
14434
14754
|
for (const [server, tools] of Object.entries(uc.roles)) {
|
|
@@ -14445,13 +14765,14 @@ function applyUntrustedContent(cur, raw, replaceLists = false, isProject = false
|
|
|
14445
14765
|
cur.roles = next;
|
|
14446
14766
|
}
|
|
14447
14767
|
}
|
|
14448
|
-
var
|
|
14768
|
+
var import_fs5, import_path5, import_os5, DANGEROUS_WORDS, RUNTIME_ONLY_CONFIG_KEYS, DEFAULT_CONFIG, RM_SAFE_PATH_PATTERN, VERDICT_ORDER, ADVISORY_SMART_RULES, cachedConfig, rejectedApiUrlsSeen, lastParsedRulesCache, CACHE_LOG_REARM_MS, cacheReadLastLoggedAt, v2ChecksByPath, KNOB_TO_CHECK;
|
|
14449
14769
|
var init_config = __esm({
|
|
14450
14770
|
"src/config/index.ts"() {
|
|
14451
14771
|
"use strict";
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14772
|
+
init_trusted_recipients();
|
|
14773
|
+
import_fs5 = __toESM(require("fs"));
|
|
14774
|
+
import_path5 = __toESM(require("path"));
|
|
14775
|
+
import_os5 = __toESM(require("os"));
|
|
14455
14776
|
init_config_schema();
|
|
14456
14777
|
init_shields();
|
|
14457
14778
|
init_managed();
|
|
@@ -14706,7 +15027,8 @@ var init_config = __esm({
|
|
|
14706
15027
|
spotlight: true,
|
|
14707
15028
|
afterExposure: "ask-once",
|
|
14708
15029
|
orgDomains: [],
|
|
14709
|
-
roles: {}
|
|
15030
|
+
roles: {},
|
|
15031
|
+
trustedRecipients: []
|
|
14710
15032
|
},
|
|
14711
15033
|
trustedHosts: [],
|
|
14712
15034
|
trustedHostsManaged: false,
|
|
@@ -14803,24 +15125,24 @@ function scanFilePath(filePath, cwd = process.cwd(), options = {}) {
|
|
|
14803
15125
|
if (!filePath) return null;
|
|
14804
15126
|
let resolved;
|
|
14805
15127
|
try {
|
|
14806
|
-
const absolute =
|
|
14807
|
-
resolved =
|
|
15128
|
+
const absolute = import_path6.default.resolve(cwd, filePath);
|
|
15129
|
+
resolved = import_fs6.default.realpathSync.native(absolute);
|
|
14808
15130
|
} catch (err2) {
|
|
14809
15131
|
const code2 = err2.code;
|
|
14810
15132
|
if (code2 === "ENOENT" || code2 === "ENOTDIR") {
|
|
14811
|
-
resolved =
|
|
15133
|
+
resolved = import_path6.default.resolve(cwd, filePath);
|
|
14812
15134
|
} else {
|
|
14813
15135
|
return sensitivePathMatch(filePath);
|
|
14814
15136
|
}
|
|
14815
15137
|
}
|
|
14816
15138
|
return matchSensitivePath2(resolved, filePath, options);
|
|
14817
15139
|
}
|
|
14818
|
-
var
|
|
15140
|
+
var import_fs6, import_path6;
|
|
14819
15141
|
var init_dlp = __esm({
|
|
14820
15142
|
"src/dlp.ts"() {
|
|
14821
15143
|
"use strict";
|
|
14822
|
-
|
|
14823
|
-
|
|
15144
|
+
import_fs6 = __toESM(require("fs"));
|
|
15145
|
+
import_path6 = __toESM(require("path"));
|
|
14824
15146
|
init_dist();
|
|
14825
15147
|
init_dist();
|
|
14826
15148
|
}
|
|
@@ -14829,24 +15151,24 @@ var init_dlp = __esm({
|
|
|
14829
15151
|
// src/policy/file-access.ts
|
|
14830
15152
|
function absoluteSpelling(p, cwd, home) {
|
|
14831
15153
|
if (p === "~" || p.startsWith("~/")) p = home + p.slice(1);
|
|
14832
|
-
return
|
|
15154
|
+
return import_path7.default.isAbsolute(p) ? p : `${cwd}/${p}`;
|
|
14833
15155
|
}
|
|
14834
15156
|
function physical(p, followFinal, links = 0) {
|
|
14835
15157
|
if (links > 40) throw new Error("Too many symbolic links");
|
|
14836
15158
|
if (!followFinal) {
|
|
14837
|
-
const parent = physical(
|
|
14838
|
-
return { path: `${parent.path}/${
|
|
15159
|
+
const parent = physical(import_path7.default.dirname(p), true, links);
|
|
15160
|
+
return { path: `${parent.path}/${import_path7.default.basename(p)}`, missing: parent.missing };
|
|
14839
15161
|
}
|
|
14840
15162
|
try {
|
|
14841
|
-
return { path:
|
|
15163
|
+
return { path: import_fs7.default.realpathSync.native(p), missing: false };
|
|
14842
15164
|
} catch (error) {
|
|
14843
15165
|
const code2 = error.code;
|
|
14844
15166
|
if (!["ENOENT", "ENOTDIR"].includes(code2 ?? "")) throw error;
|
|
14845
15167
|
try {
|
|
14846
|
-
if (
|
|
14847
|
-
const target =
|
|
15168
|
+
if (import_fs7.default.lstatSync(p).isSymbolicLink()) {
|
|
15169
|
+
const target = import_fs7.default.readlinkSync(p);
|
|
14848
15170
|
return physical(
|
|
14849
|
-
|
|
15171
|
+
import_path7.default.isAbsolute(target) ? target : `${import_path7.default.dirname(p)}/${target}`,
|
|
14850
15172
|
true,
|
|
14851
15173
|
links + 1
|
|
14852
15174
|
);
|
|
@@ -14855,13 +15177,13 @@ function physical(p, followFinal, links = 0) {
|
|
|
14855
15177
|
if (!["ENOENT", "ENOTDIR"].includes(linkError.code ?? ""))
|
|
14856
15178
|
throw linkError;
|
|
14857
15179
|
}
|
|
14858
|
-
const parent =
|
|
15180
|
+
const parent = import_path7.default.dirname(p);
|
|
14859
15181
|
if (parent === p) return { path: p, missing: true };
|
|
14860
15182
|
const resolved = physical(parent, true, links);
|
|
14861
|
-
return { path: `${resolved.path}/${
|
|
15183
|
+
return { path: `${resolved.path}/${import_path7.default.basename(p)}`, missing: true };
|
|
14862
15184
|
}
|
|
14863
15185
|
}
|
|
14864
|
-
function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, home =
|
|
15186
|
+
function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, home = import_os6.default.homedir()) {
|
|
14865
15187
|
const context = { cwd, homeDirs: [home, "/root"] };
|
|
14866
15188
|
const accesses = collectFileAccesses(tool, args, context, inspection);
|
|
14867
15189
|
const handledPaths = /* @__PURE__ */ new Set();
|
|
@@ -14884,14 +15206,14 @@ function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, h
|
|
|
14884
15206
|
const raw = absoluteSpelling(access.path, access.cwd ?? cwd, home);
|
|
14885
15207
|
if (access.directoryTarget) {
|
|
14886
15208
|
try {
|
|
14887
|
-
if (!
|
|
15209
|
+
if (!import_fs7.default.statSync(absoluteSpelling(access.directoryTarget, access.cwd ?? cwd, home)).isDirectory())
|
|
14888
15210
|
continue;
|
|
14889
15211
|
} catch {
|
|
14890
15212
|
}
|
|
14891
15213
|
}
|
|
14892
15214
|
if (access.operation === "create") {
|
|
14893
15215
|
try {
|
|
14894
|
-
|
|
15216
|
+
import_fs7.default.statSync(raw);
|
|
14895
15217
|
continue;
|
|
14896
15218
|
} catch {
|
|
14897
15219
|
}
|
|
@@ -14938,26 +15260,26 @@ function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, h
|
|
|
14938
15260
|
}
|
|
14939
15261
|
return { ...context, accesses: prepared, handledPaths };
|
|
14940
15262
|
}
|
|
14941
|
-
var
|
|
15263
|
+
var import_fs7, import_os6, import_path7;
|
|
14942
15264
|
var init_file_access = __esm({
|
|
14943
15265
|
"src/policy/file-access.ts"() {
|
|
14944
15266
|
"use strict";
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
15267
|
+
import_fs7 = __toESM(require("fs"));
|
|
15268
|
+
import_os6 = __toESM(require("os"));
|
|
15269
|
+
import_path7 = __toESM(require("path"));
|
|
14948
15270
|
init_dist();
|
|
14949
15271
|
}
|
|
14950
15272
|
});
|
|
14951
15273
|
|
|
14952
15274
|
// src/utils/provenance.ts
|
|
14953
15275
|
function findInPath(cmd) {
|
|
14954
|
-
if (
|
|
15276
|
+
if (import_path8.default.posix.isAbsolute(cmd)) return cmd;
|
|
14955
15277
|
const pathEnv = process.env.PATH ?? "";
|
|
14956
|
-
for (const dir2 of pathEnv.split(
|
|
15278
|
+
for (const dir2 of pathEnv.split(import_path8.default.delimiter)) {
|
|
14957
15279
|
if (!dir2) continue;
|
|
14958
|
-
const full =
|
|
15280
|
+
const full = import_path8.default.join(dir2, cmd);
|
|
14959
15281
|
try {
|
|
14960
|
-
|
|
15282
|
+
import_fs8.default.accessSync(full, import_fs8.default.constants.X_OK);
|
|
14961
15283
|
return full;
|
|
14962
15284
|
} catch {
|
|
14963
15285
|
}
|
|
@@ -14968,7 +15290,7 @@ function _classifyPath(resolved, cwd) {
|
|
|
14968
15290
|
if (cwd && resolved.startsWith(cwd + "/")) {
|
|
14969
15291
|
return { trustLevel: "user", reason: "binary in project directory" };
|
|
14970
15292
|
}
|
|
14971
|
-
const osTmp =
|
|
15293
|
+
const osTmp = import_os7.default.tmpdir();
|
|
14972
15294
|
const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
|
|
14973
15295
|
if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
14974
15296
|
return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
|
|
@@ -14986,7 +15308,7 @@ function _classifyPath(resolved, cwd) {
|
|
|
14986
15308
|
}
|
|
14987
15309
|
function checkProvenance(cmd, cwd) {
|
|
14988
15310
|
const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
|
|
14989
|
-
if (
|
|
15311
|
+
if (import_path8.default.posix.isAbsolute(bare)) {
|
|
14990
15312
|
const early = _classifyPath(bare, cwd);
|
|
14991
15313
|
if (early.trustLevel === "suspect") {
|
|
14992
15314
|
return { resolvedPath: bare, ...early };
|
|
@@ -15002,7 +15324,7 @@ function checkProvenance(cmd, cwd) {
|
|
|
15002
15324
|
reason: "binary not found in PATH"
|
|
15003
15325
|
};
|
|
15004
15326
|
}
|
|
15005
|
-
resolved =
|
|
15327
|
+
resolved = import_fs8.default.realpathSync(found);
|
|
15006
15328
|
} catch {
|
|
15007
15329
|
return {
|
|
15008
15330
|
resolvedPath: cmd,
|
|
@@ -15011,7 +15333,7 @@ function checkProvenance(cmd, cwd) {
|
|
|
15011
15333
|
};
|
|
15012
15334
|
}
|
|
15013
15335
|
try {
|
|
15014
|
-
const stat =
|
|
15336
|
+
const stat = import_fs8.default.statSync(resolved);
|
|
15015
15337
|
if (stat.mode & 2) {
|
|
15016
15338
|
return {
|
|
15017
15339
|
resolvedPath: resolved,
|
|
@@ -15029,21 +15351,21 @@ function checkProvenance(cmd, cwd) {
|
|
|
15029
15351
|
const classify = _classifyPath(resolved, cwd);
|
|
15030
15352
|
return { resolvedPath: resolved, ...classify };
|
|
15031
15353
|
}
|
|
15032
|
-
var
|
|
15354
|
+
var import_fs8, import_path8, import_os7, SYSTEM_PREFIXES, MANAGED_PREFIXES, USER_PREFIXES, SUSPECT_PREFIXES;
|
|
15033
15355
|
var init_provenance = __esm({
|
|
15034
15356
|
"src/utils/provenance.ts"() {
|
|
15035
15357
|
"use strict";
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15358
|
+
import_fs8 = __toESM(require("fs"));
|
|
15359
|
+
import_path8 = __toESM(require("path"));
|
|
15360
|
+
import_os7 = __toESM(require("os"));
|
|
15039
15361
|
SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
|
|
15040
15362
|
MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
|
|
15041
15363
|
USER_PREFIXES = [
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15364
|
+
import_path8.default.join(import_os7.default.homedir(), "bin"),
|
|
15365
|
+
import_path8.default.join(import_os7.default.homedir(), ".local", "bin"),
|
|
15366
|
+
import_path8.default.join(import_os7.default.homedir(), ".cargo", "bin"),
|
|
15367
|
+
import_path8.default.join(import_os7.default.homedir(), ".npm-global", "bin"),
|
|
15368
|
+
import_path8.default.join(import_os7.default.homedir(), ".volta", "bin")
|
|
15047
15369
|
];
|
|
15048
15370
|
SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
|
|
15049
15371
|
}
|
|
@@ -15052,7 +15374,7 @@ var init_provenance = __esm({
|
|
|
15052
15374
|
// src/policy/index.ts
|
|
15053
15375
|
async function evaluatePolicy2(toolName, args, agent, cwd, opts) {
|
|
15054
15376
|
const config = opts?.config ?? getConfig(cwd);
|
|
15055
|
-
const prepared = opts?.fileAccess ?? prepareFileAccesses(toolName, args, cwd, config.policy
|
|
15377
|
+
const prepared = opts?.fileAccess ?? prepareFileAccesses(toolName, args, cwd, inspectionLayers(config.policy));
|
|
15056
15378
|
const activeEnvironment = getActiveEnvironment(config) ?? void 0;
|
|
15057
15379
|
return evaluatePolicy(
|
|
15058
15380
|
config,
|
|
@@ -15084,8 +15406,11 @@ function tokenize3(toolName) {
|
|
|
15084
15406
|
}
|
|
15085
15407
|
function explainExtractShellCommand(toolName, args, toolInspection) {
|
|
15086
15408
|
const shell = resolveShellInput(toolName, args, toolInspection);
|
|
15087
|
-
const matchingPattern =
|
|
15088
|
-
|
|
15409
|
+
const matchingPattern = [
|
|
15410
|
+
...Object.keys(toolInspection.trusted),
|
|
15411
|
+
...Object.keys(toolInspection.project ?? {})
|
|
15412
|
+
].find((p) => matchesPattern(toolName, p));
|
|
15413
|
+
const entry = inspectionEntry(toolName, toolInspection);
|
|
15089
15414
|
const legacyValue = shell.kind === "not-shell" && typeof entry === "string" ? getNestedValue(args, entry) : null;
|
|
15090
15415
|
return {
|
|
15091
15416
|
command: shell.kind === "shell" ? shell.command : typeof legacyValue === "string" ? legacyValue : null,
|
|
@@ -15094,15 +15419,17 @@ function explainExtractShellCommand(toolName, args, toolInspection) {
|
|
|
15094
15419
|
};
|
|
15095
15420
|
}
|
|
15096
15421
|
function explainIsSqlTool(toolName, toolInspection) {
|
|
15097
|
-
const
|
|
15098
|
-
const matchingPattern = patterns.find((p) => matchesPattern(toolName, p));
|
|
15099
|
-
if (!matchingPattern) return false;
|
|
15100
|
-
const fieldName = toolInspection[matchingPattern];
|
|
15422
|
+
const fieldName = inspectionEntry(toolName, toolInspection);
|
|
15101
15423
|
return fieldName === "sql" || fieldName === "query";
|
|
15102
15424
|
}
|
|
15103
15425
|
async function explainPolicy(toolName, args, agent = EXPLAIN_AGENT) {
|
|
15104
15426
|
const config = getConfig();
|
|
15105
|
-
const prepared = prepareFileAccesses(
|
|
15427
|
+
const prepared = prepareFileAccesses(
|
|
15428
|
+
toolName,
|
|
15429
|
+
args,
|
|
15430
|
+
process.cwd(),
|
|
15431
|
+
inspectionLayers(config.policy)
|
|
15432
|
+
);
|
|
15106
15433
|
const derived = await deriveExplainTrace(toolName, args, prepared);
|
|
15107
15434
|
const engine = await evaluatePolicy2(toolName, args, agent, prepared.cwd, {
|
|
15108
15435
|
fileAccess: prepared,
|
|
@@ -15131,10 +15458,11 @@ async function explainPolicy(toolName, args, agent = EXPLAIN_AGENT) {
|
|
|
15131
15458
|
}
|
|
15132
15459
|
async function deriveExplainTrace(toolName, args, prepared) {
|
|
15133
15460
|
const steps = [];
|
|
15134
|
-
const globalPath =
|
|
15135
|
-
const projectPath =
|
|
15136
|
-
const credsPath =
|
|
15461
|
+
const globalPath = import_path9.default.join(import_os8.default.homedir(), ".node9", "config.json");
|
|
15462
|
+
const projectPath = import_path9.default.join(process.cwd(), "node9.config.json");
|
|
15463
|
+
const credsPath = import_path9.default.join(import_os8.default.homedir(), ".node9", "credentials.json");
|
|
15137
15464
|
const config = getConfig();
|
|
15465
|
+
const layers = inspectionLayers(config.policy);
|
|
15138
15466
|
const wsGoverned = config.policySource === "workspace";
|
|
15139
15467
|
const waterfall = [
|
|
15140
15468
|
{
|
|
@@ -15146,22 +15474,22 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15146
15474
|
{
|
|
15147
15475
|
tier: 2,
|
|
15148
15476
|
label: wsGoverned ? "Workspace config" : "Cloud policy",
|
|
15149
|
-
status:
|
|
15150
|
-
note:
|
|
15477
|
+
status: import_fs9.default.existsSync(credsPath) ? "active" : "missing",
|
|
15478
|
+
note: import_fs9.default.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"
|
|
15151
15479
|
},
|
|
15152
15480
|
{
|
|
15153
15481
|
tier: 3,
|
|
15154
15482
|
label: "Project config",
|
|
15155
|
-
status:
|
|
15483
|
+
status: import_fs9.default.existsSync(projectPath) ? "active" : "missing",
|
|
15156
15484
|
path: projectPath,
|
|
15157
|
-
...wsGoverned &&
|
|
15485
|
+
...wsGoverned && import_fs9.default.existsSync(projectPath) ? { note: "present \u2014 ignored (workspace config governs policy)" } : {}
|
|
15158
15486
|
},
|
|
15159
15487
|
{
|
|
15160
15488
|
tier: 4,
|
|
15161
15489
|
label: "Global config",
|
|
15162
|
-
status:
|
|
15490
|
+
status: import_fs9.default.existsSync(globalPath) ? "active" : "missing",
|
|
15163
15491
|
path: globalPath,
|
|
15164
|
-
...wsGoverned &&
|
|
15492
|
+
...wsGoverned && import_fs9.default.existsSync(globalPath) ? { note: "present \u2014 ignored (workspace config governs policy)" } : {}
|
|
15165
15493
|
},
|
|
15166
15494
|
{
|
|
15167
15495
|
tier: 5,
|
|
@@ -15212,10 +15540,7 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15212
15540
|
});
|
|
15213
15541
|
if (config.policy.smartRules.length > 0) {
|
|
15214
15542
|
const matchedRule = config.policy.smartRules.find(
|
|
15215
|
-
(rule) => toolMatchesRule(toolName, rule.tool,
|
|
15216
|
-
shellRuleArgs(toolName, args, rule.tool, config.policy.toolInspection),
|
|
15217
|
-
rule
|
|
15218
|
-
)
|
|
15543
|
+
(rule) => toolMatchesRule(toolName, rule.tool, layers) && evaluateSmartConditions(shellRuleArgs(toolName, args, rule.tool, layers), rule)
|
|
15219
15544
|
);
|
|
15220
15545
|
if (matchedRule) {
|
|
15221
15546
|
const label3 = `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`;
|
|
@@ -15257,7 +15582,7 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15257
15582
|
command: shellCommand,
|
|
15258
15583
|
matchingPattern,
|
|
15259
15584
|
fieldName
|
|
15260
|
-
} = explainExtractShellCommand(toolName, args,
|
|
15585
|
+
} = explainExtractShellCommand(toolName, args, layers);
|
|
15261
15586
|
if (shellCommand) {
|
|
15262
15587
|
const analyzed = analyzeShellCommand(shellCommand);
|
|
15263
15588
|
allTokens = analyzed.allTokens;
|
|
@@ -15308,7 +15633,7 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15308
15633
|
outcome: "checked",
|
|
15309
15634
|
detail: "No dangerous eval detected"
|
|
15310
15635
|
});
|
|
15311
|
-
if (explainIsSqlTool(toolName,
|
|
15636
|
+
if (explainIsSqlTool(toolName, layers)) {
|
|
15312
15637
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS2.has(t.toLowerCase()));
|
|
15313
15638
|
steps.push({
|
|
15314
15639
|
name: "SQL token stripping",
|
|
@@ -15419,13 +15744,13 @@ async function deriveExplainTrace(toolName, args, prepared) {
|
|
|
15419
15744
|
});
|
|
15420
15745
|
return { tool: toolName, args, waterfall, steps, decision: "allow" };
|
|
15421
15746
|
}
|
|
15422
|
-
var
|
|
15747
|
+
var import_fs9, import_path9, import_os8, SQL_DML_KEYWORDS2, EXPLAIN_AGENT;
|
|
15423
15748
|
var init_policy = __esm({
|
|
15424
15749
|
"src/policy/index.ts"() {
|
|
15425
15750
|
"use strict";
|
|
15426
|
-
|
|
15427
|
-
|
|
15428
|
-
|
|
15751
|
+
import_fs9 = __toESM(require("fs"));
|
|
15752
|
+
import_path9 = __toESM(require("path"));
|
|
15753
|
+
import_os8 = __toESM(require("os"));
|
|
15429
15754
|
init_dlp();
|
|
15430
15755
|
init_file_access();
|
|
15431
15756
|
init_config();
|
|
@@ -15441,34 +15766,34 @@ var init_policy = __esm({
|
|
|
15441
15766
|
|
|
15442
15767
|
// src/utils/atomic-write.ts
|
|
15443
15768
|
function atomicWriteSync(filePath, data, options) {
|
|
15444
|
-
const dir2 =
|
|
15445
|
-
if (!
|
|
15769
|
+
const dir2 = import_path10.default.dirname(filePath);
|
|
15770
|
+
if (!import_fs10.default.existsSync(dir2)) import_fs10.default.mkdirSync(dir2, { recursive: true });
|
|
15446
15771
|
const tmpPath = `${filePath}.${(0, import_crypto7.randomUUID)()}.tmp`;
|
|
15447
15772
|
try {
|
|
15448
|
-
|
|
15773
|
+
import_fs10.default.writeFileSync(tmpPath, data, options);
|
|
15449
15774
|
} catch (err2) {
|
|
15450
15775
|
try {
|
|
15451
|
-
|
|
15776
|
+
import_fs10.default.unlinkSync(tmpPath);
|
|
15452
15777
|
} catch {
|
|
15453
15778
|
}
|
|
15454
15779
|
throw err2;
|
|
15455
15780
|
}
|
|
15456
15781
|
try {
|
|
15457
|
-
|
|
15782
|
+
import_fs10.default.renameSync(tmpPath, filePath);
|
|
15458
15783
|
} catch (err2) {
|
|
15459
15784
|
try {
|
|
15460
|
-
|
|
15785
|
+
import_fs10.default.unlinkSync(tmpPath);
|
|
15461
15786
|
} catch {
|
|
15462
15787
|
}
|
|
15463
15788
|
throw err2;
|
|
15464
15789
|
}
|
|
15465
15790
|
}
|
|
15466
|
-
var
|
|
15791
|
+
var import_fs10, import_path10, import_crypto7;
|
|
15467
15792
|
var init_atomic_write = __esm({
|
|
15468
15793
|
"src/utils/atomic-write.ts"() {
|
|
15469
15794
|
"use strict";
|
|
15470
|
-
|
|
15471
|
-
|
|
15795
|
+
import_fs10 = __toESM(require("fs"));
|
|
15796
|
+
import_path10 = __toESM(require("path"));
|
|
15472
15797
|
import_crypto7 = require("crypto");
|
|
15473
15798
|
}
|
|
15474
15799
|
});
|
|
@@ -15485,11 +15810,11 @@ function extractCommandPattern(toolName, args) {
|
|
|
15485
15810
|
}
|
|
15486
15811
|
function checkPause() {
|
|
15487
15812
|
try {
|
|
15488
|
-
if (!
|
|
15489
|
-
const state = JSON.parse(
|
|
15813
|
+
if (!import_fs11.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
15814
|
+
const state = JSON.parse(import_fs11.default.readFileSync(PAUSED_FILE, "utf-8"));
|
|
15490
15815
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
15491
15816
|
try {
|
|
15492
|
-
|
|
15817
|
+
import_fs11.default.unlinkSync(PAUSED_FILE);
|
|
15493
15818
|
} catch {
|
|
15494
15819
|
}
|
|
15495
15820
|
return { paused: false };
|
|
@@ -15505,14 +15830,14 @@ function pauseNode9(durationMs, durationStr) {
|
|
|
15505
15830
|
}
|
|
15506
15831
|
function resumeNode9() {
|
|
15507
15832
|
try {
|
|
15508
|
-
if (
|
|
15833
|
+
if (import_fs11.default.existsSync(PAUSED_FILE)) import_fs11.default.unlinkSync(PAUSED_FILE);
|
|
15509
15834
|
} catch {
|
|
15510
15835
|
}
|
|
15511
15836
|
}
|
|
15512
15837
|
function getActiveTrustSession(toolName, args) {
|
|
15513
15838
|
try {
|
|
15514
|
-
if (!
|
|
15515
|
-
const trust = JSON.parse(
|
|
15839
|
+
if (!import_fs11.default.existsSync(TRUST_FILE)) return false;
|
|
15840
|
+
const trust = JSON.parse(import_fs11.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
15516
15841
|
const now = Date.now();
|
|
15517
15842
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
15518
15843
|
if (active.length !== trust.entries.length) {
|
|
@@ -15535,8 +15860,8 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
15535
15860
|
try {
|
|
15536
15861
|
let trust = { entries: [] };
|
|
15537
15862
|
try {
|
|
15538
|
-
if (
|
|
15539
|
-
trust = JSON.parse(
|
|
15863
|
+
if (import_fs11.default.existsSync(TRUST_FILE)) {
|
|
15864
|
+
trust = JSON.parse(import_fs11.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
15540
15865
|
}
|
|
15541
15866
|
} catch {
|
|
15542
15867
|
}
|
|
@@ -15558,26 +15883,26 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
15558
15883
|
}
|
|
15559
15884
|
function getPersistentDecision(toolName) {
|
|
15560
15885
|
try {
|
|
15561
|
-
const file2 =
|
|
15562
|
-
if (!
|
|
15563
|
-
const decisions = JSON.parse(
|
|
15886
|
+
const file2 = import_path11.default.join(import_os9.default.homedir(), ".node9", "decisions.json");
|
|
15887
|
+
if (!import_fs11.default.existsSync(file2)) return null;
|
|
15888
|
+
const decisions = JSON.parse(import_fs11.default.readFileSync(file2, "utf-8"));
|
|
15564
15889
|
const d = decisions[toolName];
|
|
15565
15890
|
if (d === "allow" || d === "deny") return d;
|
|
15566
15891
|
} catch {
|
|
15567
15892
|
}
|
|
15568
15893
|
return null;
|
|
15569
15894
|
}
|
|
15570
|
-
var
|
|
15895
|
+
var import_fs11, import_path11, import_os9, PAUSED_FILE, TRUST_FILE;
|
|
15571
15896
|
var init_state = __esm({
|
|
15572
15897
|
"src/auth/state.ts"() {
|
|
15573
15898
|
"use strict";
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15899
|
+
import_fs11 = __toESM(require("fs"));
|
|
15900
|
+
import_path11 = __toESM(require("path"));
|
|
15901
|
+
import_os9 = __toESM(require("os"));
|
|
15577
15902
|
init_policy();
|
|
15578
15903
|
init_atomic_write();
|
|
15579
|
-
PAUSED_FILE =
|
|
15580
|
-
TRUST_FILE =
|
|
15904
|
+
PAUSED_FILE = import_path11.default.join(import_os9.default.homedir(), ".node9", "PAUSED");
|
|
15905
|
+
TRUST_FILE = import_path11.default.join(import_os9.default.homedir(), ".node9", "trust.json");
|
|
15581
15906
|
}
|
|
15582
15907
|
});
|
|
15583
15908
|
|
|
@@ -15620,9 +15945,9 @@ function resolveDaemonPort(env = process.env) {
|
|
|
15620
15945
|
}
|
|
15621
15946
|
function getInternalToken() {
|
|
15622
15947
|
try {
|
|
15623
|
-
const pidFile =
|
|
15624
|
-
if (!
|
|
15625
|
-
const data = JSON.parse(
|
|
15948
|
+
const pidFile = import_path12.default.join(import_os10.default.homedir(), ".node9", "daemon.pid");
|
|
15949
|
+
if (!import_fs12.default.existsSync(pidFile)) return null;
|
|
15950
|
+
const data = JSON.parse(import_fs12.default.readFileSync(pidFile, "utf-8"));
|
|
15626
15951
|
process.kill(data.pid, 0);
|
|
15627
15952
|
return data.internalToken ?? null;
|
|
15628
15953
|
} catch {
|
|
@@ -15630,12 +15955,12 @@ function getInternalToken() {
|
|
|
15630
15955
|
}
|
|
15631
15956
|
}
|
|
15632
15957
|
function isDaemonRunning() {
|
|
15633
|
-
const pidFile =
|
|
15634
|
-
if (
|
|
15958
|
+
const pidFile = import_path12.default.join(import_os10.default.homedir(), ".node9", "daemon.pid");
|
|
15959
|
+
if (import_fs12.default.existsSync(pidFile)) {
|
|
15635
15960
|
let pid;
|
|
15636
15961
|
let port;
|
|
15637
15962
|
try {
|
|
15638
|
-
const data = JSON.parse(
|
|
15963
|
+
const data = JSON.parse(import_fs12.default.readFileSync(pidFile, "utf-8"));
|
|
15639
15964
|
pid = data.pid;
|
|
15640
15965
|
port = data.port;
|
|
15641
15966
|
} catch {
|
|
@@ -15653,7 +15978,7 @@ function isDaemonRunning() {
|
|
|
15653
15978
|
} catch (err2) {
|
|
15654
15979
|
if (err2 instanceof Error && "code" in err2 && err2.code === "ESRCH") {
|
|
15655
15980
|
try {
|
|
15656
|
-
|
|
15981
|
+
import_fs12.default.unlinkSync(pidFile);
|
|
15657
15982
|
} catch {
|
|
15658
15983
|
}
|
|
15659
15984
|
}
|
|
@@ -15967,15 +16292,15 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
|
15967
16292
|
signal: AbortSignal.timeout(3e3)
|
|
15968
16293
|
});
|
|
15969
16294
|
}
|
|
15970
|
-
var
|
|
16295
|
+
var import_fs12, import_net, import_path12, import_os10, ACTIVITY_SOCKET_PATH, DEFAULT_DAEMON_PORT, DAEMON_PORT, DAEMON_HOST;
|
|
15971
16296
|
var init_daemon = __esm({
|
|
15972
16297
|
"src/auth/daemon.ts"() {
|
|
15973
16298
|
"use strict";
|
|
15974
|
-
|
|
16299
|
+
import_fs12 = __toESM(require("fs"));
|
|
15975
16300
|
import_net = __toESM(require("net"));
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
16301
|
+
import_path12 = __toESM(require("path"));
|
|
16302
|
+
import_os10 = __toESM(require("os"));
|
|
16303
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path12.default.join(import_os10.default.tmpdir(), "node9-activity.sock");
|
|
15979
16304
|
DEFAULT_DAEMON_PORT = 7391;
|
|
15980
16305
|
DAEMON_PORT = resolveDaemonPort();
|
|
15981
16306
|
DAEMON_HOST = "127.0.0.1";
|
|
@@ -16034,7 +16359,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
16034
16359
|
intent = "EDIT";
|
|
16035
16360
|
if (obj.file_path) {
|
|
16036
16361
|
editFilePath = String(obj.file_path);
|
|
16037
|
-
editFileName =
|
|
16362
|
+
editFileName = import_path13.default.basename(editFilePath);
|
|
16038
16363
|
}
|
|
16039
16364
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
16040
16365
|
contextSnippet = result.snippet;
|
|
@@ -16066,11 +16391,11 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
16066
16391
|
...ruleName && { ruleName }
|
|
16067
16392
|
};
|
|
16068
16393
|
}
|
|
16069
|
-
var
|
|
16394
|
+
var import_path13, CODE_KEYS;
|
|
16070
16395
|
var init_context_sniper = __esm({
|
|
16071
16396
|
"src/context-sniper.ts"() {
|
|
16072
16397
|
"use strict";
|
|
16073
|
-
|
|
16398
|
+
import_path13 = __toESM(require("path"));
|
|
16074
16399
|
CODE_KEYS = [
|
|
16075
16400
|
"command",
|
|
16076
16401
|
"cmd",
|
|
@@ -16126,7 +16451,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
16126
16451
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
16127
16452
|
const obj = parsed;
|
|
16128
16453
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
16129
|
-
const file2 = obj.file_path ?
|
|
16454
|
+
const file2 = obj.file_path ? import_path14.default.basename(String(obj.file_path)) : "file";
|
|
16130
16455
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
16131
16456
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
16132
16457
|
return {
|
|
@@ -16343,12 +16668,12 @@ end run`;
|
|
|
16343
16668
|
}
|
|
16344
16669
|
});
|
|
16345
16670
|
}
|
|
16346
|
-
var import_child_process,
|
|
16671
|
+
var import_child_process, import_path14, isTestEnv, MIN_INTERACTION_MS;
|
|
16347
16672
|
var init_native = __esm({
|
|
16348
16673
|
"src/ui/native.ts"() {
|
|
16349
16674
|
"use strict";
|
|
16350
16675
|
import_child_process = require("child_process");
|
|
16351
|
-
|
|
16676
|
+
import_path14 = __toESM(require("path"));
|
|
16352
16677
|
init_context_sniper();
|
|
16353
16678
|
init_safe_text();
|
|
16354
16679
|
isTestEnv = () => {
|
|
@@ -16360,12 +16685,12 @@ var init_native = __esm({
|
|
|
16360
16685
|
|
|
16361
16686
|
// src/shields/jail.ts
|
|
16362
16687
|
function jailStorePath() {
|
|
16363
|
-
return
|
|
16688
|
+
return import_path15.default.join(import_os11.default.homedir(), ".node9", "jail-paths.json");
|
|
16364
16689
|
}
|
|
16365
16690
|
function readJailPaths() {
|
|
16366
16691
|
let text;
|
|
16367
16692
|
try {
|
|
16368
|
-
text =
|
|
16693
|
+
text = import_fs13.default.readFileSync(jailStorePath(), "utf8");
|
|
16369
16694
|
} catch (err2) {
|
|
16370
16695
|
if (err2.code === "ENOENT") return [];
|
|
16371
16696
|
throw err2;
|
|
@@ -16383,8 +16708,8 @@ function readJailPaths() {
|
|
|
16383
16708
|
}
|
|
16384
16709
|
function writeJailPaths(paths) {
|
|
16385
16710
|
const p = jailStorePath();
|
|
16386
|
-
|
|
16387
|
-
|
|
16711
|
+
import_fs13.default.mkdirSync(import_path15.default.dirname(p), { recursive: true });
|
|
16712
|
+
import_fs13.default.writeFileSync(p, JSON.stringify({ paths }, null, 2) + "\n", { mode: 384 });
|
|
16388
16713
|
}
|
|
16389
16714
|
function addJailPath(rawPath, verdict) {
|
|
16390
16715
|
const norm = rawPath.trim();
|
|
@@ -16423,14 +16748,14 @@ function removeJailPath(rawPath) {
|
|
|
16423
16748
|
return { removed, paths: after };
|
|
16424
16749
|
}
|
|
16425
16750
|
function regenerateUserJail(paths) {
|
|
16426
|
-
const file2 =
|
|
16751
|
+
const file2 = import_path15.default.join(USER_SHIELDS_DIR_PATH, `${USER_JAIL_SHIELD}.json`);
|
|
16427
16752
|
if (paths.length === 0) {
|
|
16428
16753
|
const active2 = readActiveShields();
|
|
16429
16754
|
if (active2.includes(USER_JAIL_SHIELD)) {
|
|
16430
16755
|
writeActiveShields(active2.filter((s) => s !== USER_JAIL_SHIELD));
|
|
16431
16756
|
}
|
|
16432
16757
|
try {
|
|
16433
|
-
|
|
16758
|
+
import_fs13.default.rmSync(file2, { force: true });
|
|
16434
16759
|
} catch {
|
|
16435
16760
|
}
|
|
16436
16761
|
return;
|
|
@@ -16445,13 +16770,13 @@ function regenerateUserJail(paths) {
|
|
|
16445
16770
|
const active = readActiveShields();
|
|
16446
16771
|
if (!active.includes(USER_JAIL_SHIELD)) writeActiveShields([...active, USER_JAIL_SHIELD]);
|
|
16447
16772
|
}
|
|
16448
|
-
var
|
|
16773
|
+
var import_fs13, import_os11, import_path15, USER_JAIL_SHIELD;
|
|
16449
16774
|
var init_jail = __esm({
|
|
16450
16775
|
"src/shields/jail.ts"() {
|
|
16451
16776
|
"use strict";
|
|
16452
|
-
|
|
16453
|
-
|
|
16454
|
-
|
|
16777
|
+
import_fs13 = __toESM(require("fs"));
|
|
16778
|
+
import_os11 = __toESM(require("os"));
|
|
16779
|
+
import_path15 = __toESM(require("path"));
|
|
16455
16780
|
init_build();
|
|
16456
16781
|
init_dist();
|
|
16457
16782
|
init_shields();
|
|
@@ -16461,7 +16786,7 @@ var init_jail = __esm({
|
|
|
16461
16786
|
|
|
16462
16787
|
// src/canary/registry.ts
|
|
16463
16788
|
function canaryStorePath() {
|
|
16464
|
-
return
|
|
16789
|
+
return import_path16.default.join(import_os12.default.homedir(), ".node9", "canaries.json");
|
|
16465
16790
|
}
|
|
16466
16791
|
function isRecord(x) {
|
|
16467
16792
|
if (!x || typeof x !== "object") return false;
|
|
@@ -16472,7 +16797,7 @@ function loadCanaries(opts) {
|
|
|
16472
16797
|
const p = canaryStorePath();
|
|
16473
16798
|
let raw;
|
|
16474
16799
|
try {
|
|
16475
|
-
raw =
|
|
16800
|
+
raw = import_fs14.default.readFileSync(p, "utf-8");
|
|
16476
16801
|
} catch (e) {
|
|
16477
16802
|
if (e.code === "ENOENT") return [];
|
|
16478
16803
|
throw e;
|
|
@@ -16494,19 +16819,19 @@ function saveCanaries(records) {
|
|
|
16494
16819
|
const p = canaryStorePath();
|
|
16495
16820
|
const paths = addJailPath(p, "block");
|
|
16496
16821
|
regenerateUserJail(paths);
|
|
16497
|
-
|
|
16822
|
+
import_fs14.default.mkdirSync(import_path16.default.dirname(p), { recursive: true });
|
|
16498
16823
|
const tmp = `${p}.${(0, import_crypto8.randomBytes)(8).toString("hex")}.tmp`;
|
|
16499
16824
|
try {
|
|
16500
|
-
|
|
16825
|
+
import_fs14.default.writeFileSync(tmp, JSON.stringify({ version: 1, records }, null, 2) + "\n", {
|
|
16501
16826
|
mode: 384,
|
|
16502
16827
|
flag: "wx"
|
|
16503
16828
|
});
|
|
16504
|
-
|
|
16505
|
-
|
|
16829
|
+
import_fs14.default.chmodSync(tmp, 384);
|
|
16830
|
+
import_fs14.default.renameSync(tmp, p);
|
|
16506
16831
|
} finally {
|
|
16507
|
-
if (
|
|
16832
|
+
if (import_fs14.default.existsSync(tmp)) {
|
|
16508
16833
|
try {
|
|
16509
|
-
|
|
16834
|
+
import_fs14.default.unlinkSync(tmp);
|
|
16510
16835
|
} catch {
|
|
16511
16836
|
}
|
|
16512
16837
|
}
|
|
@@ -16545,13 +16870,13 @@ function canaryCtxValues() {
|
|
|
16545
16870
|
path: r.path
|
|
16546
16871
|
}));
|
|
16547
16872
|
}
|
|
16548
|
-
var
|
|
16873
|
+
var import_fs14, import_os12, import_path16, import_crypto8, sha256Hex;
|
|
16549
16874
|
var init_registry = __esm({
|
|
16550
16875
|
"src/canary/registry.ts"() {
|
|
16551
16876
|
"use strict";
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16877
|
+
import_fs14 = __toESM(require("fs"));
|
|
16878
|
+
import_os12 = __toESM(require("os"));
|
|
16879
|
+
import_path16 = __toESM(require("path"));
|
|
16555
16880
|
import_crypto8 = require("crypto");
|
|
16556
16881
|
init_dist();
|
|
16557
16882
|
init_jail();
|
|
@@ -16563,16 +16888,16 @@ var init_registry = __esm({
|
|
|
16563
16888
|
function readSmallFile(p, max) {
|
|
16564
16889
|
let fd;
|
|
16565
16890
|
try {
|
|
16566
|
-
fd =
|
|
16567
|
-
const st =
|
|
16891
|
+
fd = import_fs15.default.openSync(p, import_fs15.default.constants.O_RDONLY | (import_fs15.default.constants.O_NOFOLLOW ?? 0));
|
|
16892
|
+
const st = import_fs15.default.fstatSync(fd);
|
|
16568
16893
|
if (!st.isFile() || st.size > max) return null;
|
|
16569
16894
|
const buf = Buffer.alloc(st.size);
|
|
16570
|
-
const n =
|
|
16895
|
+
const n = import_fs15.default.readSync(fd, buf, 0, st.size, 0);
|
|
16571
16896
|
return buf.subarray(0, n).toString("utf8");
|
|
16572
16897
|
} catch {
|
|
16573
16898
|
return null;
|
|
16574
16899
|
} finally {
|
|
16575
|
-
if (fd !== void 0)
|
|
16900
|
+
if (fd !== void 0) import_fs15.default.closeSync(fd);
|
|
16576
16901
|
}
|
|
16577
16902
|
}
|
|
16578
16903
|
function gitRemoteHost(url) {
|
|
@@ -16591,20 +16916,20 @@ function gitRemoteHost(url) {
|
|
|
16591
16916
|
return null;
|
|
16592
16917
|
}
|
|
16593
16918
|
function findGitConfig(dir2, walkUp = true) {
|
|
16594
|
-
let cur =
|
|
16919
|
+
let cur = import_path17.default.resolve(dir2);
|
|
16595
16920
|
for (let i = 0; i < (walkUp ? 64 : 1); i++) {
|
|
16596
|
-
const dotGit =
|
|
16597
|
-
const direct = readSmallFile(
|
|
16921
|
+
const dotGit = import_path17.default.join(cur, ".git");
|
|
16922
|
+
const direct = readSmallFile(import_path17.default.join(dotGit, "config"), 1e6);
|
|
16598
16923
|
if (direct !== null) return direct;
|
|
16599
16924
|
const pointer = readSmallFile(dotGit, 4096);
|
|
16600
16925
|
const m = pointer && /^gitdir:\s*(.+?)\s*$/m.exec(pointer);
|
|
16601
16926
|
if (m) {
|
|
16602
|
-
let gitDir =
|
|
16603
|
-
const common = readSmallFile(
|
|
16604
|
-
if (common !== null) gitDir =
|
|
16605
|
-
return readSmallFile(
|
|
16927
|
+
let gitDir = import_path17.default.resolve(cur, m[1]);
|
|
16928
|
+
const common = readSmallFile(import_path17.default.join(gitDir, "commondir"), 4096);
|
|
16929
|
+
if (common !== null) gitDir = import_path17.default.resolve(gitDir, common.trim());
|
|
16930
|
+
return readSmallFile(import_path17.default.join(gitDir, "config"), 1e6);
|
|
16606
16931
|
}
|
|
16607
|
-
const parent =
|
|
16932
|
+
const parent = import_path17.default.dirname(cur);
|
|
16608
16933
|
if (parent === cur) return null;
|
|
16609
16934
|
cur = parent;
|
|
16610
16935
|
}
|
|
@@ -16613,12 +16938,12 @@ function findGitConfig(dir2, walkUp = true) {
|
|
|
16613
16938
|
function readGitRemotes(dir2, opts = {}) {
|
|
16614
16939
|
if (!dir2) return [];
|
|
16615
16940
|
const walkUp = opts.walkUp !== false;
|
|
16616
|
-
const key = `${walkUp ? "up" : "at"}:${
|
|
16617
|
-
const hit =
|
|
16941
|
+
const key = `${walkUp ? "up" : "at"}:${import_path17.default.resolve(dir2)}`;
|
|
16942
|
+
const hit = cache2.get(key);
|
|
16618
16943
|
if (hit) return hit;
|
|
16619
16944
|
const out = [];
|
|
16620
16945
|
try {
|
|
16621
|
-
const cfg = findGitConfig(
|
|
16946
|
+
const cfg = findGitConfig(import_path17.default.resolve(dir2), walkUp);
|
|
16622
16947
|
if (cfg !== null) {
|
|
16623
16948
|
let current = null;
|
|
16624
16949
|
for (const line of cfg.split(/\r?\n/)) {
|
|
@@ -16640,31 +16965,31 @@ function readGitRemotes(dir2, opts = {}) {
|
|
|
16640
16965
|
}
|
|
16641
16966
|
} catch {
|
|
16642
16967
|
}
|
|
16643
|
-
|
|
16968
|
+
cache2.set(key, out);
|
|
16644
16969
|
return out;
|
|
16645
16970
|
}
|
|
16646
|
-
var
|
|
16971
|
+
var import_fs15, import_path17, cache2;
|
|
16647
16972
|
var init_git_remotes = __esm({
|
|
16648
16973
|
"src/utils/git-remotes.ts"() {
|
|
16649
16974
|
"use strict";
|
|
16650
|
-
|
|
16651
|
-
|
|
16975
|
+
import_fs15 = __toESM(require("fs"));
|
|
16976
|
+
import_path17 = __toESM(require("path"));
|
|
16652
16977
|
init_dist();
|
|
16653
|
-
|
|
16978
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
16654
16979
|
}
|
|
16655
16980
|
});
|
|
16656
16981
|
|
|
16657
16982
|
// src/exposure/known-hosts.ts
|
|
16658
16983
|
function manifestRoot(dir2) {
|
|
16659
|
-
let cur =
|
|
16984
|
+
let cur = import_path18.default.resolve(dir2);
|
|
16660
16985
|
for (let i = 0; i < 64; i++) {
|
|
16661
16986
|
for (const [m] of ECOSYSTEMS) {
|
|
16662
16987
|
try {
|
|
16663
|
-
if (
|
|
16988
|
+
if (import_fs16.default.existsSync(import_path18.default.join(cur, m))) return cur;
|
|
16664
16989
|
} catch {
|
|
16665
16990
|
}
|
|
16666
16991
|
}
|
|
16667
|
-
const parent =
|
|
16992
|
+
const parent = import_path18.default.dirname(cur);
|
|
16668
16993
|
if (parent === cur) return null;
|
|
16669
16994
|
cur = parent;
|
|
16670
16995
|
}
|
|
@@ -16673,8 +16998,8 @@ function manifestRoot(dir2) {
|
|
|
16673
16998
|
function knownProjectHosts(dir2) {
|
|
16674
16999
|
const empty = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
|
|
16675
17000
|
if (!dir2) return empty;
|
|
16676
|
-
const key =
|
|
16677
|
-
const hit =
|
|
17001
|
+
const key = import_path18.default.resolve(dir2);
|
|
17002
|
+
const hit = cache3.get(key);
|
|
16678
17003
|
if (hit) return hit;
|
|
16679
17004
|
const out = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
|
|
16680
17005
|
try {
|
|
@@ -16686,25 +17011,25 @@ function knownProjectHosts(dir2) {
|
|
|
16686
17011
|
const root = manifestRoot(key);
|
|
16687
17012
|
if (root !== null) {
|
|
16688
17013
|
for (const [m, registry] of ECOSYSTEMS) {
|
|
16689
|
-
if (
|
|
17014
|
+
if (import_fs16.default.existsSync(import_path18.default.join(root, m))) out.hosts.add(registry);
|
|
16690
17015
|
}
|
|
16691
17016
|
try {
|
|
16692
|
-
if (
|
|
17017
|
+
if (import_fs16.default.readdirSync(root).some((f) => f.endsWith(".gemspec")))
|
|
16693
17018
|
out.hosts.add("host:rubygems.org");
|
|
16694
17019
|
} catch {
|
|
16695
17020
|
}
|
|
16696
17021
|
}
|
|
16697
17022
|
} catch {
|
|
16698
17023
|
}
|
|
16699
|
-
|
|
17024
|
+
cache3.set(key, out);
|
|
16700
17025
|
return out;
|
|
16701
17026
|
}
|
|
16702
|
-
var
|
|
17027
|
+
var import_fs16, import_path18, ECOSYSTEMS, cache3;
|
|
16703
17028
|
var init_known_hosts = __esm({
|
|
16704
17029
|
"src/exposure/known-hosts.ts"() {
|
|
16705
17030
|
"use strict";
|
|
16706
|
-
|
|
16707
|
-
|
|
17031
|
+
import_fs16 = __toESM(require("fs"));
|
|
17032
|
+
import_path18 = __toESM(require("path"));
|
|
16708
17033
|
init_git_remotes();
|
|
16709
17034
|
ECOSYSTEMS = [
|
|
16710
17035
|
["package.json", "host:registry.npmjs.org"],
|
|
@@ -16713,7 +17038,7 @@ var init_known_hosts = __esm({
|
|
|
16713
17038
|
["Cargo.toml", "host:crates.io"],
|
|
16714
17039
|
["Gemfile", "host:rubygems.org"]
|
|
16715
17040
|
];
|
|
16716
|
-
|
|
17041
|
+
cache3 = /* @__PURE__ */ new Map();
|
|
16717
17042
|
}
|
|
16718
17043
|
});
|
|
16719
17044
|
|
|
@@ -16744,7 +17069,7 @@ function packageScriptsTouched(args, absPath, shellCommand) {
|
|
|
16744
17069
|
if (typeof a.content === "string") {
|
|
16745
17070
|
try {
|
|
16746
17071
|
const next = JSON.parse(a.content);
|
|
16747
|
-
const prev = JSON.parse(
|
|
17072
|
+
const prev = JSON.parse(import_fs17.default.readFileSync(absPath, "utf8"));
|
|
16748
17073
|
return JSON.stringify(next.scripts ?? null) !== JSON.stringify(prev.scripts ?? null);
|
|
16749
17074
|
} catch {
|
|
16750
17075
|
return LIFECYCLE_RE.test(a.content);
|
|
@@ -16754,8 +17079,8 @@ function packageScriptsTouched(args, absPath, shellCommand) {
|
|
|
16754
17079
|
}
|
|
16755
17080
|
function judgeWritesAfterExposure(opts) {
|
|
16756
17081
|
const holds = [];
|
|
16757
|
-
const home = opts.home ??
|
|
16758
|
-
const tmp = normalizeFilePath(
|
|
17082
|
+
const home = opts.home ?? import_os13.default.homedir();
|
|
17083
|
+
const tmp = normalizeFilePath(import_os13.default.tmpdir());
|
|
16759
17084
|
const root = opts.cwd ? normalizeFilePath(opts.cwd, "", home) : null;
|
|
16760
17085
|
const seen = /* @__PURE__ */ new Set();
|
|
16761
17086
|
for (const access of opts.accesses) {
|
|
@@ -16765,7 +17090,7 @@ function judgeWritesAfterExposure(opts) {
|
|
|
16765
17090
|
seen.add(p);
|
|
16766
17091
|
if (p === "/dev" || p.startsWith("/dev/")) continue;
|
|
16767
17092
|
if (root !== null && isInside(root, p)) {
|
|
16768
|
-
if (isAgentInstructionPath(p, root) && (
|
|
17093
|
+
if (isAgentInstructionPath(p, root) && (import_path19.default.basename(p) !== "package.json" || packageScriptsTouched(opts.args, p, opts.shellCommand)))
|
|
16769
17094
|
holds.push({ path: p, why: "agent-instruction" });
|
|
16770
17095
|
continue;
|
|
16771
17096
|
}
|
|
@@ -16784,13 +17109,13 @@ function judgeWritesAfterExposure(opts) {
|
|
|
16784
17109
|
}
|
|
16785
17110
|
return holds;
|
|
16786
17111
|
}
|
|
16787
|
-
var
|
|
17112
|
+
var import_fs17, import_os13, import_path19, WRITE_OPS, LIFECYCLE_RE;
|
|
16788
17113
|
var init_writes = __esm({
|
|
16789
17114
|
"src/exposure/writes.ts"() {
|
|
16790
17115
|
"use strict";
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
17116
|
+
import_fs17 = __toESM(require("fs"));
|
|
17117
|
+
import_os13 = __toESM(require("os"));
|
|
17118
|
+
import_path19 = __toESM(require("path"));
|
|
16794
17119
|
init_dist();
|
|
16795
17120
|
WRITE_OPS = /* @__PURE__ */ new Set(["write", "create", "remove"]);
|
|
16796
17121
|
LIFECYCLE_RE = /"(scripts|preinstall|install|postinstall|prepare|prepublish|prepublishOnly|prepack|postpack|publish|postpublish)"\s*:/;
|
|
@@ -16868,6 +17193,7 @@ var init_exposure_store = __esm({
|
|
|
16868
17193
|
tool: source.tool,
|
|
16869
17194
|
server: source.server,
|
|
16870
17195
|
...source.word ? { word: source.word } : {},
|
|
17196
|
+
...source.eid ? { eid: source.eid } : {},
|
|
16871
17197
|
at: source.at || Date.now()
|
|
16872
17198
|
});
|
|
16873
17199
|
if (e.sources.length > MAX_SOURCES) e.sources.splice(0, e.sources.length - MAX_SOURCES);
|
|
@@ -16915,29 +17241,29 @@ var init_exposure_store = __esm({
|
|
|
16915
17241
|
|
|
16916
17242
|
// src/exposure/marker.ts
|
|
16917
17243
|
function dir() {
|
|
16918
|
-
return
|
|
17244
|
+
return import_path20.default.join(import_os14.default.homedir(), ".node9", "exposure-sessions");
|
|
16919
17245
|
}
|
|
16920
17246
|
function file(sessionId) {
|
|
16921
17247
|
const id = import_crypto9.default.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
16922
|
-
return
|
|
17248
|
+
return import_path20.default.join(dir(), id);
|
|
16923
17249
|
}
|
|
16924
17250
|
function markSessionExposed(sessionId) {
|
|
16925
17251
|
if (!sessionId) return;
|
|
16926
17252
|
try {
|
|
16927
|
-
|
|
17253
|
+
import_fs18.default.mkdirSync(dir(), { recursive: true, mode: 448 });
|
|
16928
17254
|
const f = file(sessionId);
|
|
16929
17255
|
const now = /* @__PURE__ */ new Date();
|
|
16930
17256
|
try {
|
|
16931
|
-
|
|
17257
|
+
import_fs18.default.utimesSync(f, now, now);
|
|
16932
17258
|
} catch {
|
|
16933
|
-
|
|
17259
|
+
import_fs18.default.writeFileSync(f, "", { mode: 384 });
|
|
16934
17260
|
}
|
|
16935
17261
|
if (Math.random() < 0.05) {
|
|
16936
17262
|
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
16937
|
-
for (const name of
|
|
17263
|
+
for (const name of import_fs18.default.readdirSync(dir())) {
|
|
16938
17264
|
try {
|
|
16939
|
-
const p =
|
|
16940
|
-
if (
|
|
17265
|
+
const p = import_path20.default.join(dir(), name);
|
|
17266
|
+
if (import_fs18.default.statSync(p).mtimeMs < cutoff) import_fs18.default.unlinkSync(p);
|
|
16941
17267
|
} catch {
|
|
16942
17268
|
}
|
|
16943
17269
|
}
|
|
@@ -16948,7 +17274,7 @@ function markSessionExposed(sessionId) {
|
|
|
16948
17274
|
function maybeSessionExposed(sessionId) {
|
|
16949
17275
|
if (!sessionId) return false;
|
|
16950
17276
|
try {
|
|
16951
|
-
const st =
|
|
17277
|
+
const st = import_fs18.default.statSync(file(sessionId));
|
|
16952
17278
|
return Date.now() - st.mtimeMs < SESSION_EXPOSURE_TTL_MS;
|
|
16953
17279
|
} catch (e) {
|
|
16954
17280
|
return e.code !== "ENOENT";
|
|
@@ -16956,19 +17282,19 @@ function maybeSessionExposed(sessionId) {
|
|
|
16956
17282
|
}
|
|
16957
17283
|
function clearSessionMarkers(sessionId) {
|
|
16958
17284
|
try {
|
|
16959
|
-
if (sessionId)
|
|
16960
|
-
else
|
|
17285
|
+
if (sessionId) import_fs18.default.unlinkSync(file(sessionId));
|
|
17286
|
+
else import_fs18.default.rmSync(dir(), { recursive: true, force: true });
|
|
16961
17287
|
} catch {
|
|
16962
17288
|
}
|
|
16963
17289
|
}
|
|
16964
|
-
var import_crypto9,
|
|
17290
|
+
var import_crypto9, import_fs18, import_os14, import_path20;
|
|
16965
17291
|
var init_marker = __esm({
|
|
16966
17292
|
"src/exposure/marker.ts"() {
|
|
16967
17293
|
"use strict";
|
|
16968
17294
|
import_crypto9 = __toESM(require("crypto"));
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
17295
|
+
import_fs18 = __toESM(require("fs"));
|
|
17296
|
+
import_os14 = __toESM(require("os"));
|
|
17297
|
+
import_path20 = __toESM(require("path"));
|
|
16972
17298
|
init_exposure_store();
|
|
16973
17299
|
}
|
|
16974
17300
|
});
|
|
@@ -17050,13 +17376,13 @@ function deriveServerName(cmd) {
|
|
|
17050
17376
|
return strip(base2) || "MCP Server";
|
|
17051
17377
|
}
|
|
17052
17378
|
function getMcpToolsFile() {
|
|
17053
|
-
return
|
|
17379
|
+
return import_path21.default.join(import_os15.default.homedir(), ".node9", "mcp-tools.json");
|
|
17054
17380
|
}
|
|
17055
17381
|
function readMcpToolsConfig() {
|
|
17056
17382
|
try {
|
|
17057
17383
|
const file2 = getMcpToolsFile();
|
|
17058
|
-
if (!
|
|
17059
|
-
const raw =
|
|
17384
|
+
if (!import_fs19.default.existsSync(file2)) return {};
|
|
17385
|
+
const raw = import_fs19.default.readFileSync(file2, "utf-8");
|
|
17060
17386
|
return JSON.parse(raw);
|
|
17061
17387
|
} catch {
|
|
17062
17388
|
return {};
|
|
@@ -17065,11 +17391,11 @@ function readMcpToolsConfig() {
|
|
|
17065
17391
|
function writeMcpToolsConfig(config) {
|
|
17066
17392
|
try {
|
|
17067
17393
|
const file2 = getMcpToolsFile();
|
|
17068
|
-
const dir2 =
|
|
17069
|
-
if (!
|
|
17070
|
-
const tmpPath = `${file2}.${
|
|
17071
|
-
|
|
17072
|
-
|
|
17394
|
+
const dir2 = import_path21.default.dirname(file2);
|
|
17395
|
+
if (!import_fs19.default.existsSync(dir2)) import_fs19.default.mkdirSync(dir2, { recursive: true });
|
|
17396
|
+
const tmpPath = `${file2}.${import_os15.default.hostname()}.${process.pid}.tmp`;
|
|
17397
|
+
import_fs19.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
|
|
17398
|
+
import_fs19.default.renameSync(tmpPath, file2);
|
|
17073
17399
|
} catch (e) {
|
|
17074
17400
|
console.error("Failed to write mcp-tools.json", e);
|
|
17075
17401
|
}
|
|
@@ -17124,13 +17450,13 @@ function approveServer(serverKey, disabledTools) {
|
|
|
17124
17450
|
writeMcpToolsConfig(config);
|
|
17125
17451
|
}
|
|
17126
17452
|
}
|
|
17127
|
-
var
|
|
17453
|
+
var import_fs19, import_path21, import_os15;
|
|
17128
17454
|
var init_mcp_tools = __esm({
|
|
17129
17455
|
"src/daemon/mcp-tools.ts"() {
|
|
17130
17456
|
"use strict";
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17457
|
+
import_fs19 = __toESM(require("fs"));
|
|
17458
|
+
import_path21 = __toESM(require("path"));
|
|
17459
|
+
import_os15 = __toESM(require("os"));
|
|
17134
17460
|
init_mcp_cmd();
|
|
17135
17461
|
}
|
|
17136
17462
|
});
|
|
@@ -17141,7 +17467,7 @@ function foldServerName(name) {
|
|
|
17141
17467
|
}
|
|
17142
17468
|
function inventoryFileStamp() {
|
|
17143
17469
|
try {
|
|
17144
|
-
const st =
|
|
17470
|
+
const st = import_fs20.default.statSync(getMcpToolsFile());
|
|
17145
17471
|
return `${st.mtimeMs}:${st.size}`;
|
|
17146
17472
|
} catch {
|
|
17147
17473
|
return "";
|
|
@@ -17205,11 +17531,11 @@ function sourceWord(toolName) {
|
|
|
17205
17531
|
if (BUILTIN_SOURCES.has(bare) || /browser|web|http|search/.test(server)) return "web page";
|
|
17206
17532
|
return "tool result";
|
|
17207
17533
|
}
|
|
17208
|
-
var
|
|
17534
|
+
var import_fs20, EXPOSURE_LABEL, AGENT_INTERNAL_PREFIXES, inventoryCache, inventoryStamp, BUILTIN_SOURCES, MCP_RE;
|
|
17209
17535
|
var init_roles = __esm({
|
|
17210
17536
|
"src/exposure/roles.ts"() {
|
|
17211
17537
|
"use strict";
|
|
17212
|
-
|
|
17538
|
+
import_fs20 = __toESM(require("fs"));
|
|
17213
17539
|
init_mcp_tools();
|
|
17214
17540
|
EXPOSURE_LABEL = "\u{1F6E1}\uFE0F Node9 Exposure";
|
|
17215
17541
|
AGENT_INTERNAL_PREFIXES = ["ccd_", "visualize"];
|
|
@@ -17237,82 +17563,6 @@ var init_roles = __esm({
|
|
|
17237
17563
|
}
|
|
17238
17564
|
});
|
|
17239
17565
|
|
|
17240
|
-
// src/auth/trusted-recipients.ts
|
|
17241
|
-
function getTrustedRecipientsPath() {
|
|
17242
|
-
return import_path21.default.join(import_os15.default.homedir(), ".node9", "trusted-recipients.json");
|
|
17243
|
-
}
|
|
17244
|
-
function readTrustedRecipients() {
|
|
17245
|
-
try {
|
|
17246
|
-
const raw = import_fs20.default.readFileSync(getTrustedRecipientsPath(), "utf8");
|
|
17247
|
-
const parsed = JSON.parse(raw);
|
|
17248
|
-
return Array.isArray(parsed.entries) ? parsed.entries.filter((e) => e && typeof e.value === "string") : [];
|
|
17249
|
-
} catch {
|
|
17250
|
-
return [];
|
|
17251
|
-
}
|
|
17252
|
-
}
|
|
17253
|
-
function writeTrustedRecipients(entries) {
|
|
17254
|
-
const filePath = getTrustedRecipientsPath();
|
|
17255
|
-
import_fs20.default.mkdirSync(import_path21.default.dirname(filePath), { recursive: true });
|
|
17256
|
-
const tmp = filePath + ".node9-tmp";
|
|
17257
|
-
import_fs20.default.writeFileSync(tmp, JSON.stringify({ entries }, null, 2), { mode: 384 });
|
|
17258
|
-
import_fs20.default.renameSync(tmp, filePath);
|
|
17259
|
-
}
|
|
17260
|
-
function normalizeRecipient(raw) {
|
|
17261
|
-
const v = raw.trim();
|
|
17262
|
-
if (!v) return null;
|
|
17263
|
-
if (/^(email|host|iban):/i.test(v)) {
|
|
17264
|
-
const i = v.indexOf(":");
|
|
17265
|
-
const kind = v.slice(0, i).toLowerCase();
|
|
17266
|
-
const inner = normalizeRecipient(v.slice(i + 1));
|
|
17267
|
-
return inner && inner.startsWith(`${kind}:`) ? inner : null;
|
|
17268
|
-
}
|
|
17269
|
-
const found = extractArgDestinations({ to: v });
|
|
17270
|
-
return found.length === 1 ? found[0] : null;
|
|
17271
|
-
}
|
|
17272
|
-
function addTrustedRecipient(raw, addedBy = "user") {
|
|
17273
|
-
const value = normalizeRecipient(raw);
|
|
17274
|
-
if (!value) return null;
|
|
17275
|
-
const entries = readTrustedRecipients();
|
|
17276
|
-
if (!entries.some((e) => e.value === value)) {
|
|
17277
|
-
entries.push({ value, addedAt: Date.now(), addedBy });
|
|
17278
|
-
writeTrustedRecipients(entries);
|
|
17279
|
-
}
|
|
17280
|
-
return value;
|
|
17281
|
-
}
|
|
17282
|
-
function removeTrustedRecipient(raw) {
|
|
17283
|
-
const value = normalizeRecipient(raw) ?? raw.trim();
|
|
17284
|
-
const entries = readTrustedRecipients();
|
|
17285
|
-
const next = entries.filter((e) => e.value !== value);
|
|
17286
|
-
if (next.length === entries.length) return false;
|
|
17287
|
-
writeTrustedRecipients(next);
|
|
17288
|
-
return true;
|
|
17289
|
-
}
|
|
17290
|
-
function mtimeOf() {
|
|
17291
|
-
try {
|
|
17292
|
-
return import_fs20.default.statSync(getTrustedRecipientsPath()).mtimeMs;
|
|
17293
|
-
} catch {
|
|
17294
|
-
return 0;
|
|
17295
|
-
}
|
|
17296
|
-
}
|
|
17297
|
-
function isTrustedRecipient(value) {
|
|
17298
|
-
const mtime = mtimeOf();
|
|
17299
|
-
if (!cache3 || cache3.mtime !== mtime) {
|
|
17300
|
-
cache3 = { mtime, values: new Set(readTrustedRecipients().map((e) => e.value)) };
|
|
17301
|
-
}
|
|
17302
|
-
return cache3.values.has(value);
|
|
17303
|
-
}
|
|
17304
|
-
var import_fs20, import_path21, import_os15, cache3;
|
|
17305
|
-
var init_trusted_recipients = __esm({
|
|
17306
|
-
"src/auth/trusted-recipients.ts"() {
|
|
17307
|
-
"use strict";
|
|
17308
|
-
import_fs20 = __toESM(require("fs"));
|
|
17309
|
-
import_path21 = __toESM(require("path"));
|
|
17310
|
-
import_os15 = __toESM(require("os"));
|
|
17311
|
-
init_dist();
|
|
17312
|
-
cache3 = null;
|
|
17313
|
-
}
|
|
17314
|
-
});
|
|
17315
|
-
|
|
17316
17566
|
// src/utils/read-capped.ts
|
|
17317
17567
|
function readCapped(file2, maxBytes) {
|
|
17318
17568
|
if (maxBytes < 0) return null;
|
|
@@ -17378,7 +17628,8 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, contai
|
|
|
17378
17628
|
const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
|
|
17379
17629
|
const cleanedRiskMetadata = riskMetadata ? Object.fromEntries(
|
|
17380
17630
|
Object.entries(riskMetadata).filter(
|
|
17381
|
-
([, v]) => typeof v === "string" && v.length > 0 || typeof v === "number" && Number.isFinite(v)
|
|
17631
|
+
([k, v]) => typeof v === "string" && v.length > 0 || typeof v === "number" && Number.isFinite(v) || // fw#318 2b: the one array the SaaS schema declares.
|
|
17632
|
+
k === "exposureDestinations" && Array.isArray(v) && v.length > 0
|
|
17382
17633
|
)
|
|
17383
17634
|
) : void 0;
|
|
17384
17635
|
const hasRiskMetadata = cleanedRiskMetadata && Object.keys(cleanedRiskMetadata).length > 0;
|
|
@@ -17458,11 +17709,15 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
|
|
|
17458
17709
|
cwd: process.cwd(),
|
|
17459
17710
|
platform: import_os16.default.platform()
|
|
17460
17711
|
},
|
|
17461
|
-
//
|
|
17462
|
-
//
|
|
17712
|
+
// The SaaS validates riskMetadata strictly and would drop it whole on
|
|
17713
|
+
// an unknown key, so only fields its schema declares go on the wire.
|
|
17714
|
+
// exposureDestinations ships since the dashboard's 2b contract
|
|
17715
|
+
// (fw#318): "Approve and trust" needs them.
|
|
17463
17716
|
...riskMetadata && {
|
|
17464
17717
|
riskMetadata: Object.fromEntries(
|
|
17465
|
-
Object.entries(riskMetadata).filter(
|
|
17718
|
+
Object.entries(riskMetadata).filter(
|
|
17719
|
+
([k, v]) => !Array.isArray(v) || k === "exposureDestinations"
|
|
17720
|
+
)
|
|
17466
17721
|
)
|
|
17467
17722
|
},
|
|
17468
17723
|
...ciContext && { ciContext },
|
|
@@ -18324,7 +18579,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18324
18579
|
let fileAccess;
|
|
18325
18580
|
let fileFinding;
|
|
18326
18581
|
try {
|
|
18327
|
-
fileAccess = prepareFileAccesses(toolName, args, options?.cwd, config.policy
|
|
18582
|
+
fileAccess = prepareFileAccesses(toolName, args, options?.cwd, inspectionLayers(config.policy));
|
|
18328
18583
|
fileFinding = isManual ? null : evaluateProtectedFileAccess(
|
|
18329
18584
|
fileAccess.accesses,
|
|
18330
18585
|
fileAccess,
|
|
@@ -18343,7 +18598,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18343
18598
|
fileAccess = { cwd: options?.cwd, homeDirs: [], accesses: [], handledPaths: /* @__PURE__ */ new Set() };
|
|
18344
18599
|
fileFinding = isManual ? null : incompleteFileVerdict();
|
|
18345
18600
|
}
|
|
18346
|
-
const shellInput = resolveShellInput(toolName, args, config.policy
|
|
18601
|
+
const shellInput = resolveShellInput(toolName, args, inspectionLayers(config.policy));
|
|
18347
18602
|
const networkShell = shellInput.kind === "shell" && NETWORK_COMMAND_RE.test(shellInput.command);
|
|
18348
18603
|
const shellReview = !isManual && shellInput.kind === "uninspectable";
|
|
18349
18604
|
if (shellReview) {
|
|
@@ -18453,6 +18708,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18453
18708
|
let exposureReview = null;
|
|
18454
18709
|
let exposureKeys = [];
|
|
18455
18710
|
let exposureDestinations = [];
|
|
18711
|
+
let exposureFromEid;
|
|
18456
18712
|
const ucPolicy = config.policy.untrustedContent;
|
|
18457
18713
|
if (ucPolicy?.enabled && !taintWarning && meta?.sessionId && !isManual) {
|
|
18458
18714
|
const role = toolRole(toolName, ucPolicy);
|
|
@@ -18460,7 +18716,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18460
18716
|
const shellExp = shellInput.kind === "shell" ? classifyShellExposure(shellInput.command) : null;
|
|
18461
18717
|
const bareName = splitMcpToolName(toolName)?.bare;
|
|
18462
18718
|
const mcpFileWrite = bareName !== void 0 && fileToolOperation(bareName, args) === "write";
|
|
18463
|
-
const writes = (mcpFileWrite ? prepareFileAccesses(bareName, args, options?.cwd, config.policy
|
|
18719
|
+
const writes = (mcpFileWrite ? prepareFileAccesses(bareName, args, options?.cwd, inspectionLayers(config.policy)).accesses : fileAccess.accesses).filter((a) => ["write", "create", "remove"].includes(a.operation));
|
|
18464
18720
|
const sendsSomething = acts && !mcpFileWrite || !!shellExp?.sends;
|
|
18465
18721
|
const mayBeExposed = daemonSaysExposed ?? (maybeSessionExposed(meta.sessionId) || writes.length > 0 && judgeWritesAfterExposure({
|
|
18466
18722
|
accesses: writes,
|
|
@@ -18504,7 +18760,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18504
18760
|
const isKnown = (d) => {
|
|
18505
18761
|
const domain = destinationDomain(d);
|
|
18506
18762
|
const value = destinationValue(d);
|
|
18507
|
-
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));
|
|
18763
|
+
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));
|
|
18508
18764
|
};
|
|
18509
18765
|
const approvedOnce = (key) => ucPolicy.afterExposure === "ask-once" && approved.has(key);
|
|
18510
18766
|
const repos = (shellExp?.sends ? shellExp.repos : []).filter(
|
|
@@ -18568,6 +18824,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18568
18824
|
);
|
|
18569
18825
|
}
|
|
18570
18826
|
exposureReview = `\u{1F6E1}\uFE0F Node9: this session read ${read2}. This call ${parts.join("; and ")}.`;
|
|
18827
|
+
exposureFromEid = last?.eid;
|
|
18828
|
+
if (exposureFromEid) meta = { ...meta, exposureFromEid };
|
|
18571
18829
|
exposureKeys = [
|
|
18572
18830
|
...held.filter((h) => h.dest || h.why === "unresolved" || h.why === "repository").map(
|
|
18573
18831
|
(h) => h.why === "repository" ? `${toolName}|repo:${h.repo}` : `${toolName}|${h.dest || "unresolved"}`
|
|
@@ -18581,6 +18839,11 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18581
18839
|
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("; ") + ".";
|
|
18582
18840
|
}
|
|
18583
18841
|
} else if (held.length > 0 || writeHolds.length > 0 || logged.length > 0) {
|
|
18842
|
+
meta = {
|
|
18843
|
+
...meta,
|
|
18844
|
+
exposure: { kind: "logged", destinations: logged.length },
|
|
18845
|
+
...last?.eid ? { exposureFromEid: last.eid } : {}
|
|
18846
|
+
};
|
|
18584
18847
|
appendToLog(HOOK_DEBUG_LOG, {
|
|
18585
18848
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18586
18849
|
event: "exposure-logged",
|
|
@@ -19134,6 +19397,7 @@ ${packageReview}`;
|
|
|
19134
19397
|
// deferReview and serverKey never co-occur in production — see above.)
|
|
19135
19398
|
reason: taintWarning || packageReview || exposureReview || explainableLabel || "Node9 flagged this action for review.",
|
|
19136
19399
|
...exposureReview && exposureKeys.length > 0 ? { exposureKeys } : {},
|
|
19400
|
+
...exposureReview && exposureFromEid ? { exposureFromEid } : {},
|
|
19137
19401
|
ruleDescription: policyRuleDescription,
|
|
19138
19402
|
blockedByLabel: explainableLabel
|
|
19139
19403
|
};
|
|
@@ -30909,7 +31173,7 @@ var init_ship = __esm({
|
|
|
30909
31173
|
});
|
|
30910
31174
|
|
|
30911
31175
|
// src/policy-snapshot/build.ts
|
|
30912
|
-
function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, statusEntries = [], syncHealth, identity) {
|
|
31176
|
+
function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, statusEntries = [], syncHealth, identity, trustedRecipients = []) {
|
|
30913
31177
|
const p = config.policy;
|
|
30914
31178
|
const statusByKey = /* @__PURE__ */ new Map();
|
|
30915
31179
|
for (const s of statusEntries) {
|
|
@@ -30919,7 +31183,7 @@ function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, st
|
|
|
30919
31183
|
const connectedRows = Object.entries(mcpTools).slice(0, MAX_MCP_SERVERS).map(([key, cfg]) => ({
|
|
30920
31184
|
key,
|
|
30921
31185
|
...cfg.name && { name: cfg.name },
|
|
30922
|
-
tools: cfg.tools.map((t) => t.name
|
|
31186
|
+
tools: cfg.tools.slice(0, MAX_MCP_TOOLS).map((t) => ({ name: t.name, ...t.annotations ? { annotations: t.annotations } : {} })),
|
|
30923
31187
|
toolCount: cfg.tools.length,
|
|
30924
31188
|
status: cfg.status,
|
|
30925
31189
|
// In mcp-tools.json ⇒ it launched ⇒ connected, unless the resolver says stale.
|
|
@@ -30969,6 +31233,15 @@ function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, st
|
|
|
30969
31233
|
engineVersion: ENGINE_VERSION,
|
|
30970
31234
|
// Connected rows first so they win the cap; non-connected SEE rows fill the rest.
|
|
30971
31235
|
mcpServers: [...connectedRows, ...extraRows].slice(0, MAX_MCP_SERVERS),
|
|
31236
|
+
// fw#318 2b: the developer's personal trusted recipients (A1: read-only
|
|
31237
|
+
// on the dashboard). Omitted when empty, so an old SaaS sees no change.
|
|
31238
|
+
...trustedRecipients.length > 0 && {
|
|
31239
|
+
trustedRecipients: trustedRecipients.slice(0, 200).map((e) => ({
|
|
31240
|
+
value: e.value,
|
|
31241
|
+
addedBy: e.addedBy,
|
|
31242
|
+
...e.addedAt ? { addedAt: e.addedAt } : {}
|
|
31243
|
+
}))
|
|
31244
|
+
},
|
|
30972
31245
|
// fleet-ship Step 2: ship the proxy's own sync health so the dashboard can
|
|
30973
31246
|
// derive a "sync failing" badge. Only included when the caller passes it.
|
|
30974
31247
|
...syncHealth && {
|
|
@@ -31486,6 +31759,10 @@ function extractManagedConfig(body) {
|
|
|
31486
31759
|
out3.afterExposure = u.afterExposure;
|
|
31487
31760
|
if (Array.isArray(u.orgDomains))
|
|
31488
31761
|
out3.orgDomains = u.orgDomains.filter((x) => typeof x === "string");
|
|
31762
|
+
if (Array.isArray(u.trustedRecipients))
|
|
31763
|
+
out3.trustedRecipients = u.trustedRecipients.filter(
|
|
31764
|
+
(x) => typeof x === "string"
|
|
31765
|
+
);
|
|
31489
31766
|
if (u.roles && typeof u.roles === "object" && !Array.isArray(u.roles)) {
|
|
31490
31767
|
const roles = {};
|
|
31491
31768
|
for (const [server, tools] of Object.entries(u.roles)) {
|
|
@@ -31662,6 +31939,17 @@ async function pushPostureSnapshot(creds) {
|
|
|
31662
31939
|
} catch {
|
|
31663
31940
|
}
|
|
31664
31941
|
}
|
|
31942
|
+
function personalTrustedRecipients() {
|
|
31943
|
+
try {
|
|
31944
|
+
return readTrustedRecipients().map((e) => ({
|
|
31945
|
+
value: e.value,
|
|
31946
|
+
addedBy: e.addedBy === "approval" ? "approval" : "user",
|
|
31947
|
+
...Number.isFinite(e.addedAt) ? { addedAt: new Date(e.addedAt).toISOString() } : {}
|
|
31948
|
+
}));
|
|
31949
|
+
} catch {
|
|
31950
|
+
return [];
|
|
31951
|
+
}
|
|
31952
|
+
}
|
|
31665
31953
|
async function pushPolicySnapshot(creds) {
|
|
31666
31954
|
try {
|
|
31667
31955
|
const cfg = getConfig();
|
|
@@ -31684,7 +31972,9 @@ async function pushPolicySnapshot(creds) {
|
|
|
31684
31972
|
readSyncHealth(),
|
|
31685
31973
|
// Machine identity — heals legacy machineId-less keys on the SaaS side
|
|
31686
31974
|
// (login-v2 duplicate-key fix). getMachineId mints on first need.
|
|
31687
|
-
{ machineId: getMachineId(), platform: process.platform }
|
|
31975
|
+
{ machineId: getMachineId(), platform: process.platform },
|
|
31976
|
+
// fw#318 2b: the developer's personal trusted recipients.
|
|
31977
|
+
personalTrustedRecipients()
|
|
31688
31978
|
);
|
|
31689
31979
|
await shipPolicySnapshot(body, creds);
|
|
31690
31980
|
} catch {
|
|
@@ -31715,7 +32005,9 @@ async function runPolicyPush() {
|
|
|
31715
32005
|
// (daemon here, user shell in runPolicyPush); see the P2 design's two-env note.
|
|
31716
32006
|
resolveMcpStatus(),
|
|
31717
32007
|
readSyncHealth(),
|
|
31718
|
-
{ machineId: getMachineId(), platform: process.platform }
|
|
32008
|
+
{ machineId: getMachineId(), platform: process.platform },
|
|
32009
|
+
// fw#318 2b: the developer's personal trusted recipients.
|
|
32010
|
+
personalTrustedRecipients()
|
|
31719
32011
|
);
|
|
31720
32012
|
const sent = await shipPolicySnapshot(body, creds);
|
|
31721
32013
|
return sent ? { ok: true } : { ok: false, reason: "Push failed (network or server error)" };
|
|
@@ -31897,6 +32189,7 @@ var init_sync = __esm({
|
|
|
31897
32189
|
init_ship();
|
|
31898
32190
|
init_build2();
|
|
31899
32191
|
init_mcp_tools();
|
|
32192
|
+
init_trusted_recipients();
|
|
31900
32193
|
init_state2();
|
|
31901
32194
|
init_mcp_status();
|
|
31902
32195
|
init_ship2();
|
|
@@ -31949,6 +32242,13 @@ __export(audit_shipper_exports, {
|
|
|
31949
32242
|
startAuditShipper: () => startAuditShipper,
|
|
31950
32243
|
writeWatermark: () => writeWatermark
|
|
31951
32244
|
});
|
|
32245
|
+
function exposureOf(v) {
|
|
32246
|
+
if (!v || typeof v !== "object") return null;
|
|
32247
|
+
const o = v;
|
|
32248
|
+
if (o.kind !== "source" && o.kind !== "logged") return null;
|
|
32249
|
+
const n = typeof o.destinations === "number" && Number.isFinite(o.destinations) ? o.destinations : 0;
|
|
32250
|
+
return { kind: o.kind, destinations: Math.max(0, Math.min(1e3, Math.floor(n))) };
|
|
32251
|
+
}
|
|
31952
32252
|
function fileSignature(filePath) {
|
|
31953
32253
|
const fd = import_fs53.default.openSync(filePath, "r");
|
|
31954
32254
|
try {
|
|
@@ -32024,7 +32324,9 @@ function buildWireRows(chunk2) {
|
|
|
32024
32324
|
...typeof parsed.loopCount === "number" ? { loopCount: parsed.loopCount } : {},
|
|
32025
32325
|
...typeof parsed.transcriptPath === "string" ? { transcriptPath: parsed.transcriptPath } : {},
|
|
32026
32326
|
...typeof parsed.taintFromEid === "string" ? { taintFromEid: parsed.taintFromEid } : {},
|
|
32027
|
-
...typeof parsed.taintSource === "string" ? { taintSource: parsed.taintSource } : {}
|
|
32327
|
+
...typeof parsed.taintSource === "string" ? { taintSource: parsed.taintSource } : {},
|
|
32328
|
+
...exposureOf(parsed.exposure) ? { exposure: exposureOf(parsed.exposure) } : {},
|
|
32329
|
+
...typeof parsed.exposureFromEid === "string" ? { exposureFromEid: parsed.exposureFromEid } : {}
|
|
32028
32330
|
});
|
|
32029
32331
|
}
|
|
32030
32332
|
return { rows, consumed: lastNl + 1 };
|
|
@@ -34481,6 +34783,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34481
34783
|
tool: body.source.tool.slice(0, 200),
|
|
34482
34784
|
server: typeof body.source.server === "string" ? body.source.server.slice(0, 80) : void 0,
|
|
34483
34785
|
word: typeof body.source.word === "string" ? body.source.word.slice(0, 40) : void 0,
|
|
34786
|
+
eid: typeof body.source.eid === "string" ? body.source.eid.slice(0, 64) : void 0,
|
|
34484
34787
|
at: Date.now()
|
|
34485
34788
|
},
|
|
34486
34789
|
strings(body.destinations)
|
|
@@ -64321,7 +64624,8 @@ RAW: ${raw}
|
|
|
64321
64624
|
sessionId: sid,
|
|
64322
64625
|
ts: Date.now(),
|
|
64323
64626
|
label: result2.blockedByLabel,
|
|
64324
|
-
...result2.exposureKeys?.length ? { exposureKeys: result2.exposureKeys } : {}
|
|
64627
|
+
...result2.exposureKeys?.length ? { exposureKeys: result2.exposureKeys } : {},
|
|
64628
|
+
...result2.exposureFromEid ? { exposureFromEid: result2.exposureFromEid } : {}
|
|
64325
64629
|
});
|
|
64326
64630
|
}
|
|
64327
64631
|
} catch {
|
|
@@ -64784,9 +65088,14 @@ function buildArgsField(rawInput) {
|
|
|
64784
65088
|
const hit = scanArgs(rawInput);
|
|
64785
65089
|
if (hit) return { ...hashed, dlpPattern: hit.patternName, dlpSample: hit.redactedSample };
|
|
64786
65090
|
if (!scanCoveredEverything(rawInput)) return { ...hashed, argsUnscanned: true };
|
|
64787
|
-
const
|
|
64788
|
-
if (
|
|
64789
|
-
return {
|
|
65091
|
+
const inspected = inspectArgumentSecrets(rawInput);
|
|
65092
|
+
if (inspected.match)
|
|
65093
|
+
return {
|
|
65094
|
+
...hashed,
|
|
65095
|
+
dlpPattern: inspected.match.patternName,
|
|
65096
|
+
dlpSample: inspected.match.redactedSample
|
|
65097
|
+
};
|
|
65098
|
+
if (inspected.coverage === "incomplete") return { ...hashed, argsUnscanned: true };
|
|
64790
65099
|
return { args: JSON.parse(redactSecrets(JSON.stringify(rawInput))) };
|
|
64791
65100
|
} catch {
|
|
64792
65101
|
return hashed;
|
|
@@ -64897,6 +65206,7 @@ function registerLogCommand(program2) {
|
|
|
64897
65206
|
{
|
|
64898
65207
|
agent,
|
|
64899
65208
|
ruleName: reviewLabel,
|
|
65209
|
+
...resolvedReview.exposureFromEid ? { exposureFromEid: resolvedReview.exposureFromEid } : {},
|
|
64900
65210
|
...rawToolName !== tool ? { agentToolName: rawToolName } : {},
|
|
64901
65211
|
...typeof payloadSessionId === "string" ? { sessionId: payloadSessionId } : {},
|
|
64902
65212
|
...safeCwd ? { workingDir: safeCwd } : {}
|
|
@@ -64934,17 +65244,21 @@ function registerLogCommand(program2) {
|
|
|
64934
65244
|
if (redactOutputMode) {
|
|
64935
65245
|
const { result, found } = redactText(toolOutput);
|
|
64936
65246
|
let out = result;
|
|
65247
|
+
const ucA = config.policy.untrustedContent;
|
|
65248
|
+
const shellA = ucA?.enabled ? shellFetch(tool, rawInput) : null;
|
|
65249
|
+
const sourceA = !!ucA?.enabled && (toolRole(tool, ucA).source || !!shellA?.fetches);
|
|
64937
65250
|
let injection = null;
|
|
64938
65251
|
if (injectionOn) {
|
|
64939
|
-
const m = scanInjection(result, {
|
|
65252
|
+
const m = scanInjection(result, {
|
|
65253
|
+
tool: rawToolName,
|
|
65254
|
+
...sourceA ? { untrusted: true } : {}
|
|
65255
|
+
});
|
|
64940
65256
|
if (m && atLeastConfidence(m.confidence, inj.minConfidence)) {
|
|
64941
65257
|
injection = m;
|
|
64942
65258
|
out = frameUntrusted(result);
|
|
64943
65259
|
}
|
|
64944
65260
|
}
|
|
64945
|
-
|
|
64946
|
-
const shellA = ucA?.enabled ? shellFetch(tool, rawInput) : null;
|
|
64947
|
-
if (ucA?.enabled && (toolRole(tool, ucA).source || shellA?.fetches)) {
|
|
65261
|
+
if (sourceA) {
|
|
64948
65262
|
const wordA = shellA?.fetches ? shellSourceWord(shellA.fetchKinds) : sourceWord(tool);
|
|
64949
65263
|
markSessionExposed(payloadSessionId ?? "");
|
|
64950
65264
|
await notifySessionExposure(
|
|
@@ -64986,15 +65300,16 @@ function registerLogCommand(program2) {
|
|
|
64986
65300
|
);
|
|
64987
65301
|
}
|
|
64988
65302
|
if (injectionOn) {
|
|
64989
|
-
const
|
|
65303
|
+
const injCtx = { tool: rawToolName, ...sourceRead ? { untrusted: true } : {} };
|
|
65304
|
+
const m = scanInjection(toolOutput, injCtx);
|
|
64990
65305
|
if (m && atLeastConfidence(m.confidence, inj.minConfidence)) {
|
|
64991
65306
|
if (spotlightApplies) {
|
|
64992
|
-
injectedLeaf = (leaf) => scanInjection(leaf,
|
|
65307
|
+
injectedLeaf = (leaf) => scanInjection(leaf, injCtx) !== null;
|
|
64993
65308
|
}
|
|
64994
65309
|
if (agent === "Claude Code" && !spotlightApplies) {
|
|
64995
65310
|
updatedToolOutput = frameToolOutputLeaves(
|
|
64996
65311
|
updatedToolOutput ?? payload.tool_response,
|
|
64997
|
-
(leaf) => scanInjection(leaf,
|
|
65312
|
+
(leaf) => scanInjection(leaf, injCtx) !== null,
|
|
64998
65313
|
newUntrustedFrame,
|
|
64999
65314
|
neutralizeMarkers
|
|
65000
65315
|
);
|
|
@@ -65011,22 +65326,29 @@ function registerLogCommand(program2) {
|
|
|
65011
65326
|
if (sourceRead) {
|
|
65012
65327
|
const destinations = extractContentDestinations(toolOutput);
|
|
65013
65328
|
markSessionExposed(payloadSessionId ?? "");
|
|
65329
|
+
const sourceEid = appendLocalAudit(
|
|
65330
|
+
tool,
|
|
65331
|
+
rawInput,
|
|
65332
|
+
"allow",
|
|
65333
|
+
"exposure-source",
|
|
65334
|
+
{
|
|
65335
|
+
agent,
|
|
65336
|
+
...payloadSessionId ? { sessionId: payloadSessionId } : {},
|
|
65337
|
+
...rawToolName !== tool ? { agentToolName: rawToolName } : {},
|
|
65338
|
+
exposure: { kind: "source", destinations: destinations.length }
|
|
65339
|
+
},
|
|
65340
|
+
config.settings.auditHashArgs === true
|
|
65341
|
+
);
|
|
65014
65342
|
await notifySessionExposure(
|
|
65015
65343
|
payloadSessionId ?? "",
|
|
65016
65344
|
{
|
|
65017
65345
|
tool,
|
|
65018
65346
|
server: splitMcpToolName(tool)?.server,
|
|
65019
|
-
...shellExposure?.fetches ? { word: shellSourceWord(shellExposure.fetchKinds) } : {}
|
|
65347
|
+
...shellExposure?.fetches ? { word: shellSourceWord(shellExposure.fetchKinds) } : {},
|
|
65348
|
+
eid: sourceEid
|
|
65020
65349
|
},
|
|
65021
65350
|
destinations
|
|
65022
65351
|
);
|
|
65023
|
-
appendToLog(LOCAL_AUDIT_LOG, {
|
|
65024
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
65025
|
-
tool,
|
|
65026
|
-
source: "exposure-source",
|
|
65027
|
-
destinations: destinations.length,
|
|
65028
|
-
...payloadSessionId ? { sessionId: payloadSessionId } : {}
|
|
65029
|
-
});
|
|
65030
65352
|
if (spotlightApplies) {
|
|
65031
65353
|
const word = shellExposure?.fetches ? shellSourceWord(shellExposure.fetchKinds) : sourceWord(tool);
|
|
65032
65354
|
if (agent === "Claude Code") {
|
|
@@ -66556,7 +66878,9 @@ function dimensionOfBlock(checkedBy, ruleName) {
|
|
|
66556
66878
|
if (checkedBy.includes("egress")) return "network";
|
|
66557
66879
|
if (ruleName.startsWith("ssrf:")) return "network";
|
|
66558
66880
|
if (checkedBy.includes("pii") || checkedBy.includes("dlp")) return "data";
|
|
66559
|
-
if (checkedBy === "loop-detected" || checkedBy === "mcp-pin-mismatch" || checkedBy.startsWith("injection")
|
|
66881
|
+
if (checkedBy === "loop-detected" || checkedBy === "mcp-pin-mismatch" || checkedBy.startsWith("injection") || // fw#318: a send held after the session read outside content, and the
|
|
66882
|
+
// read rows themselves. Mirrors node9Firewall canon-taxonomy.ts.
|
|
66883
|
+
ruleName.startsWith("exposure") || checkedBy.startsWith("exposure"))
|
|
66560
66884
|
return "detection";
|
|
66561
66885
|
if (checkedBy === "app-permission-block" || ruleName.startsWith("app-permission:")) return "apps";
|
|
66562
66886
|
if (ruleName.startsWith("shield:project-jail")) return "files";
|
|
@@ -67880,7 +68204,10 @@ function scanToolResult(line, parsed, tool, cfg) {
|
|
|
67880
68204
|
let injection = null;
|
|
67881
68205
|
const inj = cfg.injection;
|
|
67882
68206
|
if (inj.enabled && !inj.allow.includes(tool) && texts.length > 0) {
|
|
67883
|
-
const m = scanInjection(texts.join("\n"), {
|
|
68207
|
+
const m = scanInjection(texts.join("\n"), {
|
|
68208
|
+
tool,
|
|
68209
|
+
...cfg.untrusted ? { untrusted: true } : {}
|
|
68210
|
+
});
|
|
67884
68211
|
if (m && CONFIDENCE_RANK2[m.confidence] >= CONFIDENCE_RANK2[inj.minConfidence]) {
|
|
67885
68212
|
injection = m;
|
|
67886
68213
|
mutated = true;
|
|
@@ -68396,7 +68723,8 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
68396
68723
|
const scan = scanToolResult(line, parsed, exec.toolName, {
|
|
68397
68724
|
dlpEnabled: cfg.policy.dlp.enabled,
|
|
68398
68725
|
injection: cfg.policy.injectionScan,
|
|
68399
|
-
spotlight: isSource && uc.spotlight ? sourceWord(nsName) : void 0
|
|
68726
|
+
spotlight: isSource && uc.spotlight ? sourceWord(nsName) : void 0,
|
|
68727
|
+
untrusted: isSource
|
|
68400
68728
|
});
|
|
68401
68729
|
if (scan.changed) outLine = scan.line;
|
|
68402
68730
|
if (isSource) {
|