@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/index.mjs
CHANGED
|
@@ -2147,32 +2147,109 @@ function evaluateSmartConditions(args, rule) {
|
|
|
2147
2147
|
function isBashTool(tool) {
|
|
2148
2148
|
return BASH_TOOL_NAMES.has(tool.toLowerCase());
|
|
2149
2149
|
}
|
|
2150
|
-
function
|
|
2150
|
+
function inspectionLayers(policy) {
|
|
2151
|
+
return { trusted: policy.toolInspection, project: policy.projectToolInspection };
|
|
2152
|
+
}
|
|
2153
|
+
function isShellEntry(value) {
|
|
2154
|
+
return typeof value === "object" && value !== null && value.kind === "shell" && typeof value.field === "string" && Object.keys(value).length === 2;
|
|
2155
|
+
}
|
|
2156
|
+
function isInspectionLayers(input) {
|
|
2157
|
+
const trusted = input.trusted;
|
|
2158
|
+
return typeof trusted === "object" && trusted !== null && !isShellEntry(trusted);
|
|
2159
|
+
}
|
|
2160
|
+
function layersOf(input) {
|
|
2161
|
+
return isInspectionLayers(input) ? { trusted: input.trusted, project: input.project ?? {} } : { trusted: input, project: {} };
|
|
2162
|
+
}
|
|
2163
|
+
function entryOf(tool, inspection) {
|
|
2151
2164
|
const pattern = Object.keys(inspection).find((p) => matchesPattern(tool, p));
|
|
2152
2165
|
return pattern === void 0 ? void 0 : inspection[pattern];
|
|
2153
2166
|
}
|
|
2167
|
+
function inspectionEntry(tool, inspection = {}) {
|
|
2168
|
+
const { trusted, project } = layersOf(inspection);
|
|
2169
|
+
return entryOf(tool, trusted) ?? entryOf(tool, project);
|
|
2170
|
+
}
|
|
2171
|
+
function executorName(tool) {
|
|
2172
|
+
const name = tool.toLowerCase();
|
|
2173
|
+
if (!name.startsWith("mcp__")) return name;
|
|
2174
|
+
const at = name.lastIndexOf("__");
|
|
2175
|
+
return at >= "mcp__".length ? name.slice(at + 2) : name;
|
|
2176
|
+
}
|
|
2177
|
+
function entryFields(entry) {
|
|
2178
|
+
if (typeof entry === "object") return [entry.field];
|
|
2179
|
+
return entry === "command" ? ["command"] : null;
|
|
2180
|
+
}
|
|
2154
2181
|
function shellFields(tool, inspection) {
|
|
2155
2182
|
if (isBashTool(tool)) return ["command"];
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
if (
|
|
2159
|
-
const
|
|
2160
|
-
|
|
2183
|
+
const { trusted, project } = layersOf(inspection);
|
|
2184
|
+
const trustedEntry = entryOf(tool, trusted);
|
|
2185
|
+
if (trustedEntry !== void 0) return entryFields(trustedEntry);
|
|
2186
|
+
const builtin = MCP_EXECUTORS.has(executorName(tool)) ? BUILTIN_FIELDS : null;
|
|
2187
|
+
const projectEntry = entryOf(tool, project);
|
|
2188
|
+
if (projectEntry === void 0) return builtin;
|
|
2189
|
+
const added = entryFields(projectEntry);
|
|
2190
|
+
if (!builtin) return added;
|
|
2191
|
+
if (!added) return builtin;
|
|
2192
|
+
return [.../* @__PURE__ */ new Set([...builtin, ...added])];
|
|
2161
2193
|
}
|
|
2162
2194
|
function isShellShapedTool(tool, inspection = {}) {
|
|
2163
2195
|
return shellFields(tool, inspection) !== null;
|
|
2164
2196
|
}
|
|
2197
|
+
function shellWord(word) {
|
|
2198
|
+
if (BARE_WORD.test(word)) return word;
|
|
2199
|
+
return word === "" ? "''" : `'${word.replace(/'/g, `'\\''`)}'`;
|
|
2200
|
+
}
|
|
2201
|
+
function isStringArray(value) {
|
|
2202
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
2203
|
+
}
|
|
2204
|
+
function commandLine(args, fieldPath, value, native) {
|
|
2205
|
+
const missing = { reason: "Shell command field is missing or is not a string" };
|
|
2206
|
+
const unreadable = { reason: "Shell command arguments could not be read" };
|
|
2207
|
+
if (typeof value !== "string" && (native || !Array.isArray(value))) return missing;
|
|
2208
|
+
const dot = fieldPath.lastIndexOf(".");
|
|
2209
|
+
const parent = dot === -1 ? args : getNestedValue(args, fieldPath.slice(0, dot));
|
|
2210
|
+
const siblings = ARGV_FIELDS.map((key) => ({
|
|
2211
|
+
key,
|
|
2212
|
+
value: parent && typeof parent === "object" ? parent[key] : void 0
|
|
2213
|
+
})).filter(({ value: value2 }) => value2 !== void 0 && value2 !== null);
|
|
2214
|
+
let argv = siblings[0];
|
|
2215
|
+
if (native) argv = siblings.find(({ value: v }) => isStringArray(v));
|
|
2216
|
+
else if (siblings.length === 2 && JSON.stringify(siblings[0].value) !== JSON.stringify(siblings[1].value))
|
|
2217
|
+
return { reason: "Shell command arguments disagree" };
|
|
2218
|
+
let command;
|
|
2219
|
+
let source = fieldPath;
|
|
2220
|
+
if (Array.isArray(value)) {
|
|
2221
|
+
if (argv || !value.length || !isStringArray(value)) return unreadable;
|
|
2222
|
+
command = value.map(shellWord).join(" ");
|
|
2223
|
+
} else if (!argv) {
|
|
2224
|
+
return { command: value, fieldPath };
|
|
2225
|
+
} else {
|
|
2226
|
+
if (!isStringArray(argv.value)) return unreadable;
|
|
2227
|
+
if (!argv.value.length) return { command: value, fieldPath };
|
|
2228
|
+
command = `${value} ${argv.value.map(shellWord).join(" ")}`;
|
|
2229
|
+
source = `${fieldPath}+${argv.key}`;
|
|
2230
|
+
}
|
|
2231
|
+
if (command.length > MAX_ARGV_CHARACTERS)
|
|
2232
|
+
return { reason: "Shell command arguments exceed the inspectable size" };
|
|
2233
|
+
return { command, fieldPath: source };
|
|
2234
|
+
}
|
|
2165
2235
|
function resolveShellInput(tool, args, inspection = {}) {
|
|
2166
2236
|
const fields = shellFields(tool, inspection);
|
|
2167
2237
|
if (!fields) return { kind: "not-shell" };
|
|
2168
2238
|
const values = fields.map((fieldPath) => ({ fieldPath, value: getNestedValue(args, fieldPath) })).filter(({ value }) => value !== void 0 && value !== null && value !== "");
|
|
2169
|
-
if (!values.length
|
|
2239
|
+
if (!values.length) {
|
|
2170
2240
|
return { kind: "uninspectable", reason: "Shell command field is missing or is not a string" };
|
|
2171
2241
|
}
|
|
2172
|
-
|
|
2242
|
+
const native = isBashTool(tool);
|
|
2243
|
+
const lines = values.map(({ fieldPath, value }) => commandLine(args, fieldPath, value, native));
|
|
2244
|
+
const failed = lines.find((line) => "reason" in line);
|
|
2245
|
+
if (failed) return { kind: "uninspectable", reason: failed.reason };
|
|
2246
|
+
const commands = lines.filter(
|
|
2247
|
+
(line) => "command" in line
|
|
2248
|
+
);
|
|
2249
|
+
if (new Set(commands.map(({ command }) => command)).size !== 1) {
|
|
2173
2250
|
return { kind: "uninspectable", reason: "Multiple shell command fields disagree" };
|
|
2174
2251
|
}
|
|
2175
|
-
return { kind: "shell",
|
|
2252
|
+
return { kind: "shell", ...commands[0] };
|
|
2176
2253
|
}
|
|
2177
2254
|
function shellRuleArgs(tool, args, ruleTool, inspection = {}) {
|
|
2178
2255
|
if (!ruleTool || !matchesPattern("bash", ruleTool) || matchesPattern(tool, ruleTool)) return args;
|
|
@@ -2913,20 +2990,25 @@ function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
|
2913
2990
|
}
|
|
2914
2991
|
return best;
|
|
2915
2992
|
}
|
|
2916
|
-
function findMnemonic(text, budget = {
|
|
2917
|
-
const words = [...text.matchAll(/[a-zA-Z]+/g)];
|
|
2993
|
+
function findMnemonic(text, budget = { hashes: 0, reasons: /* @__PURE__ */ new Set() }) {
|
|
2918
2994
|
let run = [];
|
|
2995
|
+
let runIndex = [];
|
|
2919
2996
|
let previousEnd = 0;
|
|
2920
2997
|
const rejected = /* @__PURE__ */ new Set();
|
|
2921
2998
|
const inspect = () => {
|
|
2999
|
+
if (run.length < 12) return false;
|
|
3000
|
+
const ascending = new Array(run.length).fill(0);
|
|
3001
|
+
for (let i = 1; i < run.length; i++)
|
|
3002
|
+
ascending[i] = ascending[i - 1] + (runIndex[i] > runIndex[i - 1] ? 1 : 0);
|
|
2922
3003
|
for (let start = 0; start + 12 <= run.length; start++) {
|
|
2923
3004
|
for (const count of lengths) {
|
|
2924
3005
|
if (start + count > run.length) continue;
|
|
3006
|
+
if (ascending[start + count - 1] - ascending[start] === count - 1) continue;
|
|
2925
3007
|
const candidate = run.slice(start, start + count);
|
|
2926
3008
|
const sentence = candidate.join(" ");
|
|
2927
3009
|
if (rejected.has(sentence)) continue;
|
|
2928
|
-
if (++budget.
|
|
2929
|
-
budget.reasons.add("mnemonic-
|
|
3010
|
+
if (++budget.hashes > MAX_MNEMONIC_HASHES) {
|
|
3011
|
+
budget.reasons.add("mnemonic-budget");
|
|
2930
3012
|
return false;
|
|
2931
3013
|
}
|
|
2932
3014
|
if (BIP39_PUBLIC_VECTORS.has(hash(sentence).toString("hex"))) {
|
|
@@ -2944,13 +3026,18 @@ function findMnemonic(text, budget = { candidates: 0, reasons: /* @__PURE__ */ n
|
|
|
2944
3026
|
}
|
|
2945
3027
|
return false;
|
|
2946
3028
|
};
|
|
2947
|
-
for (const word of
|
|
2948
|
-
|
|
3029
|
+
for (const word of text.matchAll(/[a-zA-Z]+/g)) {
|
|
3030
|
+
const index = indices.get(word[0]);
|
|
3031
|
+
if (index === void 0 || run.length && !/^\s+$/.test(text.slice(previousEnd, word.index))) {
|
|
2949
3032
|
if (inspect()) return true;
|
|
2950
|
-
if (budget.reasons.has("mnemonic-
|
|
3033
|
+
if (budget.reasons.has("mnemonic-budget")) return false;
|
|
2951
3034
|
run = [];
|
|
3035
|
+
runIndex = [];
|
|
3036
|
+
}
|
|
3037
|
+
if (index !== void 0) {
|
|
3038
|
+
run.push(word[0]);
|
|
3039
|
+
runIndex.push(index);
|
|
2952
3040
|
}
|
|
2953
|
-
if (indices.has(word[0])) run.push(word[0]);
|
|
2954
3041
|
previousEnd = word.index + word[0].length;
|
|
2955
3042
|
}
|
|
2956
3043
|
return inspect();
|
|
@@ -3081,6 +3168,9 @@ function validateCask(raw) {
|
|
|
3081
3168
|
const second = b64urlIndex(s[tsAt + 5]);
|
|
3082
3169
|
return month <= 11 && day <= 30 && hour <= 23 && minute <= 59 && second <= 59;
|
|
3083
3170
|
}
|
|
3171
|
+
function isInvisibleChar(char) {
|
|
3172
|
+
return INVISIBLE_ONE.test(char);
|
|
3173
|
+
}
|
|
3084
3174
|
function stripInvisible(text) {
|
|
3085
3175
|
return text.replace(INVISIBLE_RE, "");
|
|
3086
3176
|
}
|
|
@@ -3267,6 +3357,45 @@ function matchArgumentText(text, fieldPath, budget) {
|
|
|
3267
3357
|
}
|
|
3268
3358
|
return best;
|
|
3269
3359
|
}
|
|
3360
|
+
function matchArgumentBatch(fragments, fieldPath, budget) {
|
|
3361
|
+
if (!fragments.length) return null;
|
|
3362
|
+
const text = fragments.join(BATCH_SEPARATOR);
|
|
3363
|
+
const lower2 = text.toLowerCase();
|
|
3364
|
+
let best = null;
|
|
3365
|
+
for (const pattern of DLP_PATTERNS) {
|
|
3366
|
+
if (pattern.keywords && !pattern.keywords.some((kw) => lower2.includes(kw.toLowerCase())))
|
|
3367
|
+
continue;
|
|
3368
|
+
const flags = pattern.regex.flags.replace(/[gmy]/g, "");
|
|
3369
|
+
if (!new RegExp(pattern.regex.source, flags + "m").test(text)) continue;
|
|
3370
|
+
for (const fragment of fragments) {
|
|
3371
|
+
if (firstAcceptedMatch(pattern, fragment) === null) continue;
|
|
3372
|
+
const severity = pattern.contextBoost && isAssignmentContext(fragment) ? "block" : pattern.severity;
|
|
3373
|
+
if (severity === "block")
|
|
3374
|
+
return {
|
|
3375
|
+
patternName: pattern.name,
|
|
3376
|
+
fieldPath,
|
|
3377
|
+
redactedSample: "[encoded secret]",
|
|
3378
|
+
severity
|
|
3379
|
+
};
|
|
3380
|
+
best ??= {
|
|
3381
|
+
patternName: pattern.name,
|
|
3382
|
+
fieldPath,
|
|
3383
|
+
redactedSample: "[encoded secret]",
|
|
3384
|
+
severity
|
|
3385
|
+
};
|
|
3386
|
+
if (!pattern.contextBoost) break;
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
if (findMnemonic(text, budget)) {
|
|
3390
|
+
return {
|
|
3391
|
+
patternName: "BIP-39 Recovery Phrase",
|
|
3392
|
+
fieldPath,
|
|
3393
|
+
redactedSample: "[recovery phrase]",
|
|
3394
|
+
severity: "block"
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
return best;
|
|
3398
|
+
}
|
|
3270
3399
|
function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
3271
3400
|
if (depth > MAX_DEPTH2 || args === null || args === void 0) return null;
|
|
3272
3401
|
if (Array.isArray(args)) {
|
|
@@ -4138,6 +4267,9 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
4138
4267
|
return null;
|
|
4139
4268
|
}
|
|
4140
4269
|
}
|
|
4270
|
+
function newSecretViewBudget() {
|
|
4271
|
+
return { hashes: 0, nestedBytes: 0, joinedLabelBytes: 0, reasons: /* @__PURE__ */ new Set() };
|
|
4272
|
+
}
|
|
4141
4273
|
function textBytes(buf) {
|
|
4142
4274
|
if (buf.length < 16) return null;
|
|
4143
4275
|
const text = buf.toString("utf8");
|
|
@@ -4156,13 +4288,41 @@ function percentOnce(text) {
|
|
|
4156
4288
|
}
|
|
4157
4289
|
});
|
|
4158
4290
|
}
|
|
4291
|
+
function* wrappedBase64(text) {
|
|
4292
|
+
if (!text.includes("\n")) return;
|
|
4293
|
+
const word = /^[A-Za-z0-9+/_-]+={0,2}$/;
|
|
4294
|
+
let run = [];
|
|
4295
|
+
let width = 0;
|
|
4296
|
+
const flush = function* () {
|
|
4297
|
+
if (run.length >= 2) yield run.join("");
|
|
4298
|
+
run = [];
|
|
4299
|
+
width = 0;
|
|
4300
|
+
};
|
|
4301
|
+
for (const raw of text.split("\n")) {
|
|
4302
|
+
const line = raw.trim();
|
|
4303
|
+
const full = line.length >= 16 && word.test(line) && !line.includes("=");
|
|
4304
|
+
if (full && (!run.length || line.length === width)) {
|
|
4305
|
+
run.push(line);
|
|
4306
|
+
width = line.length;
|
|
4307
|
+
continue;
|
|
4308
|
+
}
|
|
4309
|
+
if (run.length && word.test(line) && line.length >= 2 && line.length <= width) {
|
|
4310
|
+
run.push(line);
|
|
4311
|
+
yield* flush();
|
|
4312
|
+
continue;
|
|
4313
|
+
}
|
|
4314
|
+
yield* flush();
|
|
4315
|
+
if (full) {
|
|
4316
|
+
run.push(line);
|
|
4317
|
+
width = line.length;
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
yield* flush();
|
|
4321
|
+
}
|
|
4159
4322
|
function* encodedRegions(text, budget) {
|
|
4160
4323
|
yield [text, false];
|
|
4324
|
+
for (const block of wrappedBase64(text)) yield [block, false];
|
|
4161
4325
|
for (const match of text.matchAll(/(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,30}:\/\/[^\s<>"']+/gi)) {
|
|
4162
|
-
if (++budget.candidates > SECRET_VIEW_LIMITS.maxCandidates) {
|
|
4163
|
-
budget.reasons.add("decode-candidates");
|
|
4164
|
-
return;
|
|
4165
|
-
}
|
|
4166
4326
|
try {
|
|
4167
4327
|
new URL(match[0]);
|
|
4168
4328
|
} catch {
|
|
@@ -4182,6 +4342,8 @@ function* encodedRegions(text, budget) {
|
|
|
4182
4342
|
];
|
|
4183
4343
|
for (const region of regions) if (region) yield [region, false];
|
|
4184
4344
|
const labels = host2.split(".");
|
|
4345
|
+
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))
|
|
4346
|
+
continue;
|
|
4185
4347
|
for (let start = 0; start < labels.length; start++) {
|
|
4186
4348
|
let joined = labels[start];
|
|
4187
4349
|
for (let end = start + 1; end < Math.min(labels.length, start + 8); end++) {
|
|
@@ -4189,11 +4351,13 @@ function* encodedRegions(text, budget) {
|
|
|
4189
4351
|
if (!run.every((s) => /^[a-f0-9]{2,}$/i.test(s)) && !run.every((s) => /^[A-Z2-7]{4,}$/i.test(s)))
|
|
4190
4352
|
break;
|
|
4191
4353
|
joined += labels[end];
|
|
4192
|
-
if (
|
|
4193
|
-
|
|
4354
|
+
if (joined.length < 16) continue;
|
|
4355
|
+
budget.joinedLabelBytes += joined.length;
|
|
4356
|
+
if (budget.joinedLabelBytes > SECRET_VIEW_LIMITS.maxJoinedLabelBytes) {
|
|
4357
|
+
budget.reasons.add("decode-labels");
|
|
4194
4358
|
return;
|
|
4195
4359
|
}
|
|
4196
|
-
|
|
4360
|
+
yield [joined, true];
|
|
4197
4361
|
}
|
|
4198
4362
|
}
|
|
4199
4363
|
}
|
|
@@ -4230,10 +4394,6 @@ function* decodedCandidates(text, budget) {
|
|
|
4230
4394
|
if (joined) yield ["dns-labels", region];
|
|
4231
4395
|
for (const [kind, re] of families) {
|
|
4232
4396
|
for (const match of region.matchAll(re)) {
|
|
4233
|
-
if (++budget.candidates > SECRET_VIEW_LIMITS.maxCandidates) {
|
|
4234
|
-
budget.reasons.add("decode-candidates");
|
|
4235
|
-
return;
|
|
4236
|
-
}
|
|
4237
4397
|
const raw = match[0];
|
|
4238
4398
|
let buf;
|
|
4239
4399
|
if (kind === "base64") {
|
|
@@ -4256,42 +4416,76 @@ function* decodedCandidates(text, budget) {
|
|
|
4256
4416
|
}
|
|
4257
4417
|
}
|
|
4258
4418
|
}
|
|
4259
|
-
function
|
|
4419
|
+
function secretViews(text, budget) {
|
|
4260
4420
|
const seen = /* @__PURE__ */ new Set([text]);
|
|
4261
|
-
const
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
for (const
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4421
|
+
const out = [];
|
|
4422
|
+
let frontier = [{ text, transforms: [] }];
|
|
4423
|
+
for (let depth = 1; depth <= SECRET_VIEW_LIMITS.maxDepth; depth++) {
|
|
4424
|
+
const next = [];
|
|
4425
|
+
for (const parent of frontier) {
|
|
4426
|
+
let perLeaf = 0;
|
|
4427
|
+
for (const [kind, decoded] of decodedCandidates(parent.text, budget)) {
|
|
4428
|
+
if (seen.has(decoded)) continue;
|
|
4429
|
+
seen.add(decoded);
|
|
4430
|
+
const normalizing = kind === "invisibles" || kind === "percent";
|
|
4431
|
+
if (depth >= 2 && !normalizing) {
|
|
4432
|
+
budget.nestedBytes += Buffer.byteLength(decoded);
|
|
4433
|
+
if (++perLeaf > SECRET_VIEW_LIMITS.maxNestedViewsPerLeaf || budget.nestedBytes > SECRET_VIEW_LIMITS.maxNestedBytes) {
|
|
4434
|
+
budget.reasons.add("decode-nested");
|
|
4435
|
+
return out;
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
const view = { text: decoded, transforms: [...parent.transforms, kind] };
|
|
4439
|
+
out.push(view);
|
|
4440
|
+
next.push(view);
|
|
4275
4441
|
}
|
|
4276
|
-
seen.add(decoded);
|
|
4277
|
-
budget.views++;
|
|
4278
|
-
budget.bytes += bytes;
|
|
4279
|
-
queue.push({ text: decoded, transforms: [...view.transforms, kind] });
|
|
4280
4442
|
}
|
|
4281
|
-
|
|
4443
|
+
frontier = next;
|
|
4444
|
+
if (!frontier.length) return out;
|
|
4445
|
+
}
|
|
4446
|
+
for (const view of frontier) {
|
|
4447
|
+
if (!decodedCandidates(view.text, budget).next().done) {
|
|
4448
|
+
budget.reasons.add("decode-nested");
|
|
4449
|
+
break;
|
|
4450
|
+
}
|
|
4282
4451
|
}
|
|
4452
|
+
return out;
|
|
4453
|
+
}
|
|
4454
|
+
function batches(views) {
|
|
4455
|
+
const out = [];
|
|
4456
|
+
let current = [];
|
|
4457
|
+
let size = 0;
|
|
4458
|
+
for (const { text } of views) {
|
|
4459
|
+
if (current.length && size + text.length > SECRET_VIEW_LIMITS.batchCharacters) {
|
|
4460
|
+
out.push(current);
|
|
4461
|
+
current = [];
|
|
4462
|
+
size = 0;
|
|
4463
|
+
}
|
|
4464
|
+
current.push(text);
|
|
4465
|
+
size += text.length;
|
|
4466
|
+
}
|
|
4467
|
+
if (current.length) out.push(current);
|
|
4468
|
+
return out;
|
|
4283
4469
|
}
|
|
4284
4470
|
function inspectArgumentSecrets(args) {
|
|
4285
|
-
const budget =
|
|
4471
|
+
const budget = newSecretViewBudget();
|
|
4286
4472
|
const ancestors = /* @__PURE__ */ new Set();
|
|
4287
4473
|
let nodes = 0;
|
|
4288
4474
|
let sourceBytes = 0;
|
|
4289
4475
|
let tailBytes = 0;
|
|
4290
4476
|
let best = null;
|
|
4477
|
+
const blocked = () => best?.severity === "block";
|
|
4291
4478
|
const accept = (match) => {
|
|
4292
4479
|
if (match && (!best || best.severity !== "block" && match.severity === "block")) best = match;
|
|
4293
4480
|
};
|
|
4481
|
+
const decodeSegment = (segment, fieldPath) => {
|
|
4482
|
+
for (const batch of batches(secretViews(segment, budget))) {
|
|
4483
|
+
accept(matchArgumentBatch(batch, fieldPath, budget));
|
|
4484
|
+
if (blocked()) return;
|
|
4485
|
+
}
|
|
4486
|
+
};
|
|
4294
4487
|
const visit = (value, depth, fieldPath) => {
|
|
4488
|
+
if (blocked()) return;
|
|
4295
4489
|
if (++nodes > ARGUMENT_SCAN_LIMITS.maxNodes) {
|
|
4296
4490
|
budget.reasons.add("argument-nodes");
|
|
4297
4491
|
return;
|
|
@@ -4333,14 +4527,11 @@ function inspectArgumentSecrets(args) {
|
|
|
4333
4527
|
}
|
|
4334
4528
|
sourceBytes += Math.min(bytes, Math.max(0, remaining));
|
|
4335
4529
|
if (!text) return;
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
if (hit)
|
|
4339
|
-
accept(view.transforms.length ? { ...hit, redactedSample: "[encoded secret]" } : hit);
|
|
4340
|
-
}
|
|
4530
|
+
accept(matchArgumentText(text, fieldPath, budget));
|
|
4531
|
+
let scannedEnd = text.length;
|
|
4341
4532
|
if (!truncated && value.length > window) {
|
|
4342
|
-
budget.reasons.add("decode-window");
|
|
4343
4533
|
for (let start = window - overlap; start < value.length; start += window - overlap) {
|
|
4534
|
+
if (blocked()) return;
|
|
4344
4535
|
const chunk = value.slice(start, start + window);
|
|
4345
4536
|
const chunkBytes = Buffer.byteLength(chunk);
|
|
4346
4537
|
if (tailBytes + chunkBytes > ARGUMENT_SCAN_LIMITS.maxTailBytes) {
|
|
@@ -4349,9 +4540,16 @@ function inspectArgumentSecrets(args) {
|
|
|
4349
4540
|
}
|
|
4350
4541
|
tailBytes += chunkBytes;
|
|
4351
4542
|
accept(matchArgumentText(chunk, fieldPath, budget));
|
|
4543
|
+
scannedEnd = start + chunk.length;
|
|
4352
4544
|
if (start + window >= value.length) break;
|
|
4353
4545
|
}
|
|
4354
4546
|
}
|
|
4547
|
+
for (let start = 0; start < scannedEnd && !blocked(); ) {
|
|
4548
|
+
let end = Math.min(start + window, scannedEnd);
|
|
4549
|
+
while (end < scannedEnd && runChar(value, end - 1) && runChar(value, end)) end++;
|
|
4550
|
+
decodeSegment(value.slice(start, end), fieldPath);
|
|
4551
|
+
start = end;
|
|
4552
|
+
}
|
|
4355
4553
|
const trimmed = text.trim();
|
|
4356
4554
|
if (value.length < ARGUMENT_SCAN_LIMITS.maxJsonCharacters && (trimmed.startsWith("{") || trimmed.startsWith("["))) {
|
|
4357
4555
|
try {
|
|
@@ -4370,7 +4568,7 @@ function inspectArgumentSecrets(args) {
|
|
|
4370
4568
|
function scanArgumentSecrets(args) {
|
|
4371
4569
|
const result = inspectArgumentSecrets(args);
|
|
4372
4570
|
if (result.match) return result.match;
|
|
4373
|
-
return result.
|
|
4571
|
+
return result.coverage === "incomplete" ? {
|
|
4374
4572
|
patternName: "Argument inspection incomplete",
|
|
4375
4573
|
fieldPath: "args",
|
|
4376
4574
|
redactedSample: "[uninspected content]",
|
|
@@ -4398,18 +4596,13 @@ function tokenize2(toolName) {
|
|
|
4398
4596
|
return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
|
|
4399
4597
|
}
|
|
4400
4598
|
function extractShellCommand(toolName, args, toolInspection) {
|
|
4401
|
-
const
|
|
4402
|
-
|
|
4403
|
-
if (!matchingPattern) return null;
|
|
4404
|
-
const fieldPath = toolInspection[matchingPattern];
|
|
4599
|
+
const fieldPath = inspectionEntry(toolName, toolInspection);
|
|
4600
|
+
if (fieldPath === void 0) return null;
|
|
4405
4601
|
const value = getNestedValue(args, typeof fieldPath === "string" ? fieldPath : fieldPath.field);
|
|
4406
4602
|
return typeof value === "string" ? value : null;
|
|
4407
4603
|
}
|
|
4408
4604
|
function isSqlTool(toolName, toolInspection) {
|
|
4409
|
-
const
|
|
4410
|
-
const matchingPattern = patterns.find((p) => matchesPattern(toolName, p));
|
|
4411
|
-
if (!matchingPattern) return false;
|
|
4412
|
-
const fieldName = toolInspection[matchingPattern];
|
|
4605
|
+
const fieldName = inspectionEntry(toolName, toolInspection);
|
|
4413
4606
|
return fieldName === "sql" || fieldName === "query";
|
|
4414
4607
|
}
|
|
4415
4608
|
function pipeChainVerdict(command, isTrustedHost2, highAction = "review") {
|
|
@@ -4476,9 +4669,10 @@ function egressPolicyVerdict(eg) {
|
|
|
4476
4669
|
}
|
|
4477
4670
|
async function evaluatePolicy(config, toolName, args, context = {}, hooks = {}) {
|
|
4478
4671
|
let protectedVerdict = null;
|
|
4672
|
+
const layers = inspectionLayers(config.policy);
|
|
4479
4673
|
if (context.agent !== "Terminal") {
|
|
4480
4674
|
try {
|
|
4481
|
-
const accesses = context.accesses ?? collectFileAccesses(toolName, args, context,
|
|
4675
|
+
const accesses = context.accesses ?? collectFileAccesses(toolName, args, context, layers);
|
|
4482
4676
|
protectedVerdict = evaluateProtectedFileAccess(accesses, context, catalogSettings(config));
|
|
4483
4677
|
} catch {
|
|
4484
4678
|
protectedVerdict = incompleteFileVerdict();
|
|
@@ -4513,7 +4707,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4513
4707
|
hooks
|
|
4514
4708
|
);
|
|
4515
4709
|
if (result.decision === "block") return result;
|
|
4516
|
-
if (context.agent !== "Terminal" && resolveShellInput(toolName, args,
|
|
4710
|
+
if (context.agent !== "Terminal" && resolveShellInput(toolName, args, layers).kind === "uninspectable") {
|
|
4517
4711
|
return {
|
|
4518
4712
|
decision: "review",
|
|
4519
4713
|
ruleName: "shell-inspection-incomplete",
|
|
@@ -4529,13 +4723,14 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4529
4723
|
const { agent, cwd, activeEnvironment } = context;
|
|
4530
4724
|
const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2 } = hooks;
|
|
4531
4725
|
const settingsView = catalogSettings(config);
|
|
4726
|
+
const layers = inspectionLayers(config.policy);
|
|
4532
4727
|
const cv = (id) => checkValue(settingsView, id);
|
|
4533
4728
|
let loggedFinding;
|
|
4534
4729
|
const noteLogged = (v) => {
|
|
4535
4730
|
if (v?.logged && !loggedFinding) loggedFinding = v;
|
|
4536
4731
|
};
|
|
4537
4732
|
const allowed = () => loggedFinding ?? { decision: "allow" };
|
|
4538
|
-
const shellInput = resolveShellInput(toolName, args,
|
|
4733
|
+
const shellInput = resolveShellInput(toolName, args, layers);
|
|
4539
4734
|
const wouldBeIgnored = matchesPattern(toolName, config.policy.ignoredTools);
|
|
4540
4735
|
{
|
|
4541
4736
|
const dest = ssrfDestinationFloor(toolName, args, {
|
|
@@ -4640,13 +4835,10 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4640
4835
|
return { id, value: cv(id) };
|
|
4641
4836
|
};
|
|
4642
4837
|
const matches = config.policy.smartRules.filter((rule) => {
|
|
4643
|
-
if (!toolMatchesRule(toolName, rule.tool,
|
|
4838
|
+
if (!toolMatchesRule(toolName, rule.tool, layers)) return false;
|
|
4644
4839
|
if (astSuppressed(rule)) return false;
|
|
4645
4840
|
if (rmCleanupWaiver && rule.name === "review-rm" && rule.verdict === "review") return false;
|
|
4646
|
-
if (!evaluateSmartConditions(
|
|
4647
|
-
shellRuleArgs(toolName, args, rule.tool, config.policy.toolInspection),
|
|
4648
|
-
rule
|
|
4649
|
-
))
|
|
4841
|
+
if (!evaluateSmartConditions(shellRuleArgs(toolName, args, rule.tool, layers), rule))
|
|
4650
4842
|
return false;
|
|
4651
4843
|
const check = ruleCheck(rule);
|
|
4652
4844
|
if (check?.value === "off") return false;
|
|
@@ -4693,7 +4885,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4693
4885
|
let allTokens = [];
|
|
4694
4886
|
let pathTokens = [];
|
|
4695
4887
|
if (pendingToolEgress) return pendingToolEgress;
|
|
4696
|
-
const shellCommand = shellInput.kind === "shell" ? shellInput.command : shellInput.kind === "not-shell" ? extractShellCommand(toolName, args,
|
|
4888
|
+
const shellCommand = shellInput.kind === "shell" ? shellInput.command : shellInput.kind === "not-shell" ? extractShellCommand(toolName, args, layers) : null;
|
|
4697
4889
|
if (shellCommand) {
|
|
4698
4890
|
const analyzed = analyzeShellCommand(shellCommand);
|
|
4699
4891
|
allTokens = analyzed.allTokens;
|
|
@@ -4770,7 +4962,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4770
4962
|
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
4771
4963
|
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
4772
4964
|
const sshHosts = extractAllSshHosts(rawTokens.slice(1));
|
|
4773
|
-
allTokens.
|
|
4965
|
+
allTokens = allTokens.concat(sshHosts);
|
|
4774
4966
|
}
|
|
4775
4967
|
if (firstToken && firstToken.startsWith("/") && checkProvenance2) {
|
|
4776
4968
|
const prov = checkProvenance2(firstToken, cwd);
|
|
@@ -4799,7 +4991,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4799
4991
|
};
|
|
4800
4992
|
}
|
|
4801
4993
|
}
|
|
4802
|
-
if (isSqlTool(toolName,
|
|
4994
|
+
if (isSqlTool(toolName, layers)) {
|
|
4803
4995
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
|
|
4804
4996
|
}
|
|
4805
4997
|
} else {
|
|
@@ -4807,7 +4999,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
4807
4999
|
if (args && typeof args === "object") {
|
|
4808
5000
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
4809
5001
|
const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
|
|
4810
|
-
allTokens.
|
|
5002
|
+
allTokens = allTokens.concat(extraTokens);
|
|
4811
5003
|
}
|
|
4812
5004
|
}
|
|
4813
5005
|
const isManual = agent === "Terminal";
|
|
@@ -5576,7 +5768,7 @@ function matchCanaryArgs(args, values) {
|
|
|
5576
5768
|
return null;
|
|
5577
5769
|
}
|
|
5578
5770
|
}
|
|
5579
|
-
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, 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, 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, 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, LOOP_MAX_RECORDS, 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;
|
|
5771
|
+
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, 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, 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, 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, LOOP_MAX_RECORDS, 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;
|
|
5580
5772
|
var init_dist = __esm({
|
|
5581
5773
|
"packages/policy-engine/dist/index.mjs"() {
|
|
5582
5774
|
"use strict";
|
|
@@ -7941,7 +8133,23 @@ var init_dist = __esm({
|
|
|
7941
8133
|
"shell",
|
|
7942
8134
|
"exec_command"
|
|
7943
8135
|
]);
|
|
7944
|
-
MCP_EXECUTORS = /* @__PURE__ */ new Set([
|
|
8136
|
+
MCP_EXECUTORS = /* @__PURE__ */ new Set([
|
|
8137
|
+
...BASH_TOOL_NAMES,
|
|
8138
|
+
"exec",
|
|
8139
|
+
"run_command",
|
|
8140
|
+
"terminal",
|
|
8141
|
+
"execute_command",
|
|
8142
|
+
"execute_shell",
|
|
8143
|
+
"execute_shell_command",
|
|
8144
|
+
"shell_execute",
|
|
8145
|
+
"run_shell",
|
|
8146
|
+
"run_terminal_command",
|
|
8147
|
+
"start_process"
|
|
8148
|
+
]);
|
|
8149
|
+
BUILTIN_FIELDS = ["command", "cmd", "script"];
|
|
8150
|
+
ARGV_FIELDS = ["args", "argv"];
|
|
8151
|
+
BARE_WORD = /^[A-Za-z0-9_./+:@%,-]+$/;
|
|
8152
|
+
MAX_ARGV_CHARACTERS = 262144;
|
|
7945
8153
|
({ syntax: syntax2 } = mvdanSh2);
|
|
7946
8154
|
MAX_ALTERNATIVES = 64;
|
|
7947
8155
|
MAX_DEPTH = 64;
|
|
@@ -10067,6 +10275,7 @@ zoo`.split("\n");
|
|
|
10067
10275
|
indices = new Map(BIP39_ENGLISH.map((word, i) => [word, i]));
|
|
10068
10276
|
lengths = [24, 21, 18, 15, 12];
|
|
10069
10277
|
hash = (value) => createHash2("sha256").update(value).digest();
|
|
10278
|
+
MAX_MNEMONIC_HASHES = 25e3;
|
|
10070
10279
|
IBAN_LENGTH = {
|
|
10071
10280
|
AD: 24,
|
|
10072
10281
|
AE: 23,
|
|
@@ -10174,6 +10383,7 @@ zoo`.split("\n");
|
|
|
10174
10383
|
CASK_TWO_ZERO_SUFFIX = /* @__PURE__ */ new Set([..."AEIMQUYcgkosw048"]);
|
|
10175
10384
|
CASK_FOUR_ZERO_SUFFIX = /* @__PURE__ */ new Set([..."AQgw"]);
|
|
10176
10385
|
INVISIBLE_RE = /[͏----︀-️]|[\u{E0000}-\u{E007F}]/gu;
|
|
10386
|
+
INVISIBLE_ONE = new RegExp(`^(?:${INVISIBLE_RE.source.split("|")[0]})$`, "u");
|
|
10177
10387
|
HOMOGLYPHS = {
|
|
10178
10388
|
// Cyrillic lowercase
|
|
10179
10389
|
\u0430: "a",
|
|
@@ -10832,6 +11042,7 @@ zoo`.split("\n");
|
|
|
10832
11042
|
MAX_DEPTH2 = 5;
|
|
10833
11043
|
MAX_STRING_BYTES = 1e5;
|
|
10834
11044
|
MAX_JSON_PARSE_BYTES = 1e4;
|
|
11045
|
+
BATCH_SEPARATOR = "\n";
|
|
10835
11046
|
({ syntax: syntax3 } = mvdanSh3);
|
|
10836
11047
|
NPM_INSTALL_VERBS = /* @__PURE__ */ new Set([
|
|
10837
11048
|
"install",
|
|
@@ -11301,16 +11512,24 @@ zoo`.split("\n");
|
|
|
11301
11512
|
["browser_batch", ["actions[].input.url"]]
|
|
11302
11513
|
]);
|
|
11303
11514
|
SECRET_VIEW_LIMITS = {
|
|
11515
|
+
/** A decoded text that itself decodes is followed this many layers; a deeper layer is reviewed. */
|
|
11304
11516
|
maxDepth: 4,
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11517
|
+
/** Nested views per parent view, and nested text per call; past either the call is reviewed. */
|
|
11518
|
+
maxNestedViewsPerLeaf: 64,
|
|
11519
|
+
maxNestedBytes: 1048576,
|
|
11520
|
+
/** Joined hostname-label text per call; past it the call is reviewed. */
|
|
11521
|
+
maxJoinedLabelBytes: 1048576,
|
|
11522
|
+
/** Decoded fragments are matched in batches of about this many characters. */
|
|
11523
|
+
batchCharacters: 65536,
|
|
11524
|
+
/** Only a valid DNS name has its labels joined. */
|
|
11525
|
+
maxHostLength: 253,
|
|
11526
|
+
maxLabelLength: 63,
|
|
11527
|
+
maxLabels: 127
|
|
11309
11528
|
};
|
|
11310
11529
|
ARGUMENT_SCAN_LIMITS = {
|
|
11311
11530
|
maxDepth: 12,
|
|
11312
11531
|
maxNodes: 16384,
|
|
11313
|
-
/**
|
|
11532
|
+
/** Text is matched and decoded in windows of this size. */
|
|
11314
11533
|
maxStringCharacters: 1e5,
|
|
11315
11534
|
/** Windows overlap by this much so a token on a window edge is still seen whole. */
|
|
11316
11535
|
windowOverlap: 4096,
|
|
@@ -11320,6 +11539,11 @@ zoo`.split("\n");
|
|
|
11320
11539
|
maxTailBytes: 4194304,
|
|
11321
11540
|
maxJsonCharacters: 1e4
|
|
11322
11541
|
};
|
|
11542
|
+
RUN_CHAR = /[A-Za-z0-9+/=_%\\.-]/;
|
|
11543
|
+
runChar = (value, index) => {
|
|
11544
|
+
const char = value[index] ?? "";
|
|
11545
|
+
return RUN_CHAR.test(char) || char !== "" && isInvisibleChar(char);
|
|
11546
|
+
};
|
|
11323
11547
|
VERDICT_RANK = {
|
|
11324
11548
|
allow: 0,
|
|
11325
11549
|
review: 1,
|
|
@@ -11664,6 +11888,10 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
11664
11888
|
taintFromEid: meta.taintFromEid,
|
|
11665
11889
|
...meta.taintSource && { taintSource: meta.taintSource }
|
|
11666
11890
|
} : {};
|
|
11891
|
+
const exposureFields = {
|
|
11892
|
+
...meta?.exposure ? { exposure: meta.exposure } : {},
|
|
11893
|
+
...meta?.exposureFromEid ? { exposureFromEid: meta.exposureFromEid } : {}
|
|
11894
|
+
};
|
|
11667
11895
|
const eid = generateEventId();
|
|
11668
11896
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
11669
11897
|
// eid first: the outbox shipper dedups on it, and a fixed leading field
|
|
@@ -11686,6 +11914,7 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
11686
11914
|
...loopCountField,
|
|
11687
11915
|
...transcriptPathField,
|
|
11688
11916
|
...taintFields,
|
|
11917
|
+
...exposureFields,
|
|
11689
11918
|
...testRun,
|
|
11690
11919
|
agent: meta?.agent,
|
|
11691
11920
|
mcpServer: meta?.mcpServer,
|
|
@@ -11717,10 +11946,72 @@ var init_audit = __esm({
|
|
|
11717
11946
|
// src/core.ts
|
|
11718
11947
|
init_audit();
|
|
11719
11948
|
|
|
11949
|
+
// src/auth/trusted-recipients.ts
|
|
11950
|
+
init_dist();
|
|
11951
|
+
import fs2 from "fs";
|
|
11952
|
+
import path2 from "path";
|
|
11953
|
+
import os2 from "os";
|
|
11954
|
+
function getTrustedRecipientsPath() {
|
|
11955
|
+
return path2.join(os2.homedir(), ".node9", "trusted-recipients.json");
|
|
11956
|
+
}
|
|
11957
|
+
function readTrustedRecipients() {
|
|
11958
|
+
try {
|
|
11959
|
+
const raw = fs2.readFileSync(getTrustedRecipientsPath(), "utf8");
|
|
11960
|
+
const parsed = JSON.parse(raw);
|
|
11961
|
+
return Array.isArray(parsed.entries) ? parsed.entries.filter((e) => e && typeof e.value === "string") : [];
|
|
11962
|
+
} catch {
|
|
11963
|
+
return [];
|
|
11964
|
+
}
|
|
11965
|
+
}
|
|
11966
|
+
function writeTrustedRecipients(entries) {
|
|
11967
|
+
const filePath = getTrustedRecipientsPath();
|
|
11968
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
11969
|
+
const tmp = filePath + ".node9-tmp";
|
|
11970
|
+
fs2.writeFileSync(tmp, JSON.stringify({ entries }, null, 2), { mode: 384 });
|
|
11971
|
+
fs2.renameSync(tmp, filePath);
|
|
11972
|
+
}
|
|
11973
|
+
function normalizeRecipient(raw) {
|
|
11974
|
+
const v = raw.trim();
|
|
11975
|
+
if (!v) return null;
|
|
11976
|
+
if (/^(email|host|iban):/i.test(v)) {
|
|
11977
|
+
const i = v.indexOf(":");
|
|
11978
|
+
const kind = v.slice(0, i).toLowerCase();
|
|
11979
|
+
const inner = normalizeRecipient(v.slice(i + 1));
|
|
11980
|
+
return inner && inner.startsWith(`${kind}:`) ? inner : null;
|
|
11981
|
+
}
|
|
11982
|
+
const found = extractArgDestinations({ to: v });
|
|
11983
|
+
return found.length === 1 ? found[0] : null;
|
|
11984
|
+
}
|
|
11985
|
+
function addTrustedRecipient(raw, addedBy = "user") {
|
|
11986
|
+
const value = normalizeRecipient(raw);
|
|
11987
|
+
if (!value) return null;
|
|
11988
|
+
const entries = readTrustedRecipients();
|
|
11989
|
+
if (!entries.some((e) => e.value === value)) {
|
|
11990
|
+
entries.push({ value, addedAt: Date.now(), addedBy });
|
|
11991
|
+
writeTrustedRecipients(entries);
|
|
11992
|
+
}
|
|
11993
|
+
return value;
|
|
11994
|
+
}
|
|
11995
|
+
var cache = null;
|
|
11996
|
+
function mtimeOf() {
|
|
11997
|
+
try {
|
|
11998
|
+
return fs2.statSync(getTrustedRecipientsPath()).mtimeMs;
|
|
11999
|
+
} catch {
|
|
12000
|
+
return 0;
|
|
12001
|
+
}
|
|
12002
|
+
}
|
|
12003
|
+
function isTrustedRecipient(value) {
|
|
12004
|
+
const mtime = mtimeOf();
|
|
12005
|
+
if (!cache || cache.mtime !== mtime) {
|
|
12006
|
+
cache = { mtime, values: new Set(readTrustedRecipients().map((e) => e.value)) };
|
|
12007
|
+
}
|
|
12008
|
+
return cache.values.has(value);
|
|
12009
|
+
}
|
|
12010
|
+
|
|
11720
12011
|
// src/config/index.ts
|
|
11721
|
-
import
|
|
11722
|
-
import
|
|
11723
|
-
import
|
|
12012
|
+
import fs5 from "fs";
|
|
12013
|
+
import path5 from "path";
|
|
12014
|
+
import os5 from "os";
|
|
11724
12015
|
|
|
11725
12016
|
// src/config-schema.ts
|
|
11726
12017
|
import { z } from "zod";
|
|
@@ -11989,17 +12280,17 @@ function sanitizeConfig(raw) {
|
|
|
11989
12280
|
}
|
|
11990
12281
|
|
|
11991
12282
|
// src/shields.ts
|
|
11992
|
-
import
|
|
11993
|
-
import
|
|
11994
|
-
import
|
|
12283
|
+
import fs3 from "fs";
|
|
12284
|
+
import path3 from "path";
|
|
12285
|
+
import os3 from "os";
|
|
11995
12286
|
init_dist();
|
|
11996
12287
|
init_dist();
|
|
11997
|
-
var USER_SHIELDS_DIR =
|
|
12288
|
+
var USER_SHIELDS_DIR = path3.join(os3.homedir(), ".node9", "shields");
|
|
11998
12289
|
function loadUserShields() {
|
|
11999
12290
|
const result = {};
|
|
12000
12291
|
let entries;
|
|
12001
12292
|
try {
|
|
12002
|
-
entries =
|
|
12293
|
+
entries = fs3.readdirSync(USER_SHIELDS_DIR).filter((f) => f.endsWith(".json"));
|
|
12003
12294
|
} catch (err) {
|
|
12004
12295
|
if (err.code !== "ENOENT") {
|
|
12005
12296
|
process.stderr.write(
|
|
@@ -12010,9 +12301,9 @@ function loadUserShields() {
|
|
|
12010
12301
|
return result;
|
|
12011
12302
|
}
|
|
12012
12303
|
for (const file2 of entries) {
|
|
12013
|
-
const filePath =
|
|
12304
|
+
const filePath = path3.join(USER_SHIELDS_DIR, file2);
|
|
12014
12305
|
try {
|
|
12015
|
-
const raw = JSON.parse(
|
|
12306
|
+
const raw = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
12016
12307
|
const v = validateShieldDefinition(raw);
|
|
12017
12308
|
if ("error" in v) {
|
|
12018
12309
|
process.stderr.write(`[node9] ${v.error}: ${filePath}
|
|
@@ -12043,10 +12334,10 @@ function getShield(name) {
|
|
|
12043
12334
|
const resolved = resolveShieldName(name);
|
|
12044
12335
|
return resolved ? SHIELDS[resolved] : null;
|
|
12045
12336
|
}
|
|
12046
|
-
var SHIELDS_STATE_FILE =
|
|
12337
|
+
var SHIELDS_STATE_FILE = path3.join(os3.homedir(), ".node9", "shields.json");
|
|
12047
12338
|
function readShieldsFile() {
|
|
12048
12339
|
try {
|
|
12049
|
-
const raw =
|
|
12340
|
+
const raw = fs3.readFileSync(SHIELDS_STATE_FILE, "utf-8");
|
|
12050
12341
|
if (!raw.trim()) return { active: [] };
|
|
12051
12342
|
const parsed = JSON.parse(raw);
|
|
12052
12343
|
const active = Array.isArray(parsed.active) ? parsed.active.filter(
|
|
@@ -12258,15 +12549,15 @@ function pathRules(rawPath, verdict, reason) {
|
|
|
12258
12549
|
}
|
|
12259
12550
|
|
|
12260
12551
|
// src/auth/trusted-hosts.ts
|
|
12261
|
-
import
|
|
12262
|
-
import
|
|
12263
|
-
import
|
|
12552
|
+
import fs4 from "fs";
|
|
12553
|
+
import path4 from "path";
|
|
12554
|
+
import os4 from "os";
|
|
12264
12555
|
function getTrustedHostsPath() {
|
|
12265
|
-
return
|
|
12556
|
+
return path4.join(os4.homedir(), ".node9", "trusted-hosts.json");
|
|
12266
12557
|
}
|
|
12267
12558
|
function readTrustedHosts() {
|
|
12268
12559
|
try {
|
|
12269
|
-
const raw =
|
|
12560
|
+
const raw = fs4.readFileSync(getTrustedHostsPath(), "utf8");
|
|
12270
12561
|
const parsed = JSON.parse(raw);
|
|
12271
12562
|
return Array.isArray(parsed.hosts) ? parsed.hosts : [];
|
|
12272
12563
|
} catch {
|
|
@@ -12277,7 +12568,7 @@ var _cache = null;
|
|
|
12277
12568
|
var CACHE_TTL_MS = 5e3;
|
|
12278
12569
|
function getFileMtime() {
|
|
12279
12570
|
try {
|
|
12280
|
-
return
|
|
12571
|
+
return fs4.statSync(getTrustedHostsPath()).mtimeMs;
|
|
12281
12572
|
} catch {
|
|
12282
12573
|
return 0;
|
|
12283
12574
|
}
|
|
@@ -12742,7 +13033,8 @@ var DEFAULT_CONFIG = {
|
|
|
12742
13033
|
spotlight: true,
|
|
12743
13034
|
afterExposure: "ask-once",
|
|
12744
13035
|
orgDomains: [],
|
|
12745
|
-
roles: {}
|
|
13036
|
+
roles: {},
|
|
13037
|
+
trustedRecipients: []
|
|
12746
13038
|
},
|
|
12747
13039
|
trustedHosts: [],
|
|
12748
13040
|
trustedHostsManaged: false,
|
|
@@ -12816,8 +13108,8 @@ function noteRejectedApiUrl(raw) {
|
|
|
12816
13108
|
if (rejectedApiUrlsSeen.has(key)) return;
|
|
12817
13109
|
rejectedApiUrlsSeen.add(key);
|
|
12818
13110
|
try {
|
|
12819
|
-
|
|
12820
|
-
|
|
13111
|
+
fs5.appendFileSync(
|
|
13112
|
+
path5.join(os5.homedir(), ".node9", "hook-debug.log"),
|
|
12821
13113
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] REFUSED apiUrl, using the default instead: ${String(raw).slice(0, 200).replace(/[\r\n]+/g, " ")}
|
|
12822
13114
|
`
|
|
12823
13115
|
);
|
|
@@ -12834,9 +13126,9 @@ function getCredentials() {
|
|
|
12834
13126
|
};
|
|
12835
13127
|
}
|
|
12836
13128
|
try {
|
|
12837
|
-
const credPath =
|
|
12838
|
-
if (
|
|
12839
|
-
const creds = JSON.parse(
|
|
13129
|
+
const credPath = path5.join(os5.homedir(), ".node9", "credentials.json");
|
|
13130
|
+
if (fs5.existsSync(credPath)) {
|
|
13131
|
+
const creds = JSON.parse(fs5.readFileSync(credPath, "utf-8"));
|
|
12840
13132
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
12841
13133
|
const profile = creds[profileName];
|
|
12842
13134
|
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
@@ -12868,7 +13160,7 @@ function readRulesCacheResilient(cacheFile) {
|
|
|
12868
13160
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
12869
13161
|
let content;
|
|
12870
13162
|
try {
|
|
12871
|
-
content =
|
|
13163
|
+
content = fs5.readFileSync(cacheFile, "utf-8");
|
|
12872
13164
|
existed = true;
|
|
12873
13165
|
} catch (err) {
|
|
12874
13166
|
if (err.code === "ENOENT") return {};
|
|
@@ -12883,10 +13175,10 @@ function readRulesCacheResilient(cacheFile) {
|
|
|
12883
13175
|
}
|
|
12884
13176
|
}
|
|
12885
13177
|
if (existed || sawReadError) {
|
|
12886
|
-
const backup =
|
|
13178
|
+
const backup = path5.join(path5.dirname(cacheFile), "rules-cache.last-good.json");
|
|
12887
13179
|
if (backup !== cacheFile) {
|
|
12888
13180
|
try {
|
|
12889
|
-
const raw = JSON.parse(
|
|
13181
|
+
const raw = JSON.parse(fs5.readFileSync(backup, "utf-8"));
|
|
12890
13182
|
logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
|
|
12891
13183
|
lastParsedRulesCache = raw;
|
|
12892
13184
|
return raw;
|
|
@@ -12909,8 +13201,8 @@ function logCacheReadIssue(cacheFile, kind) {
|
|
|
12909
13201
|
if (now - cacheReadLastLoggedAt < CACHE_LOG_REARM_MS) return;
|
|
12910
13202
|
cacheReadLastLoggedAt = now;
|
|
12911
13203
|
try {
|
|
12912
|
-
|
|
12913
|
-
|
|
13204
|
+
fs5.appendFileSync(
|
|
13205
|
+
path5.join(os5.homedir(), ".node9", "hook-debug.log"),
|
|
12914
13206
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
|
|
12915
13207
|
`
|
|
12916
13208
|
);
|
|
@@ -12931,8 +13223,8 @@ function catalogSettingsFrom(settings, policy) {
|
|
|
12931
13223
|
}
|
|
12932
13224
|
function getConfig(cwd) {
|
|
12933
13225
|
if (!cwd && cachedConfig) return cachedConfig;
|
|
12934
|
-
const globalPath =
|
|
12935
|
-
const projectPath =
|
|
13226
|
+
const globalPath = path5.join(os5.homedir(), ".node9", "config.json");
|
|
13227
|
+
const projectPath = path5.join(cwd ?? process.cwd(), "node9.config.json");
|
|
12936
13228
|
const globalConfig = tryLoadConfig(globalPath);
|
|
12937
13229
|
const projectConfig = tryLoadConfig(projectPath, { project: true });
|
|
12938
13230
|
const mergedSettings = {
|
|
@@ -12945,6 +13237,7 @@ function getConfig(cwd) {
|
|
|
12945
13237
|
dangerousWords: [...DEFAULT_CONFIG.policy.dangerousWords],
|
|
12946
13238
|
ignoredTools: [...DEFAULT_CONFIG.policy.ignoredTools],
|
|
12947
13239
|
toolInspection: { ...DEFAULT_CONFIG.policy.toolInspection },
|
|
13240
|
+
projectToolInspection: {},
|
|
12948
13241
|
// Shipped rules carry `builtin`: the catalog's per-check value governs
|
|
12949
13242
|
// them and only them (a user rule that reuses a name keeps its verdict).
|
|
12950
13243
|
smartRules: DEFAULT_CONFIG.policy.smartRules.map((r) => ({ ...r, builtin: true })),
|
|
@@ -12971,7 +13264,8 @@ function getConfig(cwd) {
|
|
|
12971
13264
|
untrustedContent: {
|
|
12972
13265
|
...DEFAULT_CONFIG.policy.untrustedContent,
|
|
12973
13266
|
orgDomains: [...DEFAULT_CONFIG.policy.untrustedContent.orgDomains],
|
|
12974
|
-
roles: {}
|
|
13267
|
+
roles: {},
|
|
13268
|
+
trustedRecipients: []
|
|
12975
13269
|
},
|
|
12976
13270
|
// Left empty on purpose: the local file is read fresh at policy-eval time
|
|
12977
13271
|
// (getCachedHosts via isTrustedHost), NOT snapshotted into the frozen config
|
|
@@ -13017,8 +13311,14 @@ function getConfig(cwd) {
|
|
|
13017
13311
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
13018
13312
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
13019
13313
|
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
13020
|
-
if (p.toolInspection)
|
|
13021
|
-
|
|
13314
|
+
if (p.toolInspection) {
|
|
13315
|
+
if (isProject)
|
|
13316
|
+
mergedPolicy.projectToolInspection = {
|
|
13317
|
+
...mergedPolicy.projectToolInspection,
|
|
13318
|
+
...p.toolInspection
|
|
13319
|
+
};
|
|
13320
|
+
else mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
|
|
13321
|
+
}
|
|
13022
13322
|
if (p.smartRules) {
|
|
13023
13323
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
13024
13324
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
@@ -13166,7 +13466,7 @@ function getConfig(cwd) {
|
|
|
13166
13466
|
let cloudMandatesEnforcement = false;
|
|
13167
13467
|
let cloudMandatesAppPerm = false;
|
|
13168
13468
|
{
|
|
13169
|
-
const cacheFile =
|
|
13469
|
+
const cacheFile = path5.join(os5.homedir(), ".node9", "rules-cache.json");
|
|
13170
13470
|
try {
|
|
13171
13471
|
const raw = readRulesCacheResilient(cacheFile);
|
|
13172
13472
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
@@ -13559,10 +13859,10 @@ function getConfig(cwd) {
|
|
|
13559
13859
|
}
|
|
13560
13860
|
function tryLoadConfig(filePath, options = {}) {
|
|
13561
13861
|
v2ChecksByPath.delete(filePath);
|
|
13562
|
-
if (!
|
|
13862
|
+
if (!fs5.existsSync(filePath)) return null;
|
|
13563
13863
|
let raw;
|
|
13564
13864
|
try {
|
|
13565
|
-
raw = JSON.parse(
|
|
13865
|
+
raw = JSON.parse(fs5.readFileSync(filePath, "utf-8"));
|
|
13566
13866
|
} catch (err) {
|
|
13567
13867
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13568
13868
|
process.stderr.write(
|
|
@@ -13637,6 +13937,13 @@ function applyUntrustedContent(cur, raw, replaceLists = false, isProject = false
|
|
|
13637
13937
|
if (replaceLists) cur.orgDomains = domains;
|
|
13638
13938
|
else for (const d of domains) if (!cur.orgDomains.includes(d)) cur.orgDomains.push(d);
|
|
13639
13939
|
}
|
|
13940
|
+
if (replaceLists && Array.isArray(uc.trustedRecipients)) {
|
|
13941
|
+
cur.trustedRecipients = [
|
|
13942
|
+
...new Set(
|
|
13943
|
+
uc.trustedRecipients.filter((v) => typeof v === "string").map((v) => normalizeRecipient(v)).filter((v) => v !== null)
|
|
13944
|
+
)
|
|
13945
|
+
].slice(0, 200);
|
|
13946
|
+
}
|
|
13640
13947
|
if (uc.roles && typeof uc.roles === "object" && !Array.isArray(uc.roles)) {
|
|
13641
13948
|
const next = replaceLists ? {} : cur.roles;
|
|
13642
13949
|
for (const [server, tools] of Object.entries(uc.roles)) {
|
|
@@ -13660,18 +13967,18 @@ init_dist();
|
|
|
13660
13967
|
// src/dlp.ts
|
|
13661
13968
|
init_dist();
|
|
13662
13969
|
init_dist();
|
|
13663
|
-
import
|
|
13664
|
-
import
|
|
13970
|
+
import fs6 from "fs";
|
|
13971
|
+
import path6 from "path";
|
|
13665
13972
|
function scanFilePath(filePath, cwd = process.cwd(), options = {}) {
|
|
13666
13973
|
if (!filePath) return null;
|
|
13667
13974
|
let resolved;
|
|
13668
13975
|
try {
|
|
13669
|
-
const absolute =
|
|
13670
|
-
resolved =
|
|
13976
|
+
const absolute = path6.resolve(cwd, filePath);
|
|
13977
|
+
resolved = fs6.realpathSync.native(absolute);
|
|
13671
13978
|
} catch (err) {
|
|
13672
13979
|
const code = err.code;
|
|
13673
13980
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
13674
|
-
resolved =
|
|
13981
|
+
resolved = path6.resolve(cwd, filePath);
|
|
13675
13982
|
} else {
|
|
13676
13983
|
return sensitivePathMatch(filePath);
|
|
13677
13984
|
}
|
|
@@ -13681,29 +13988,29 @@ function scanFilePath(filePath, cwd = process.cwd(), options = {}) {
|
|
|
13681
13988
|
|
|
13682
13989
|
// src/policy/file-access.ts
|
|
13683
13990
|
init_dist();
|
|
13684
|
-
import
|
|
13685
|
-
import
|
|
13686
|
-
import
|
|
13991
|
+
import fs7 from "fs";
|
|
13992
|
+
import os6 from "os";
|
|
13993
|
+
import path7 from "path";
|
|
13687
13994
|
function absoluteSpelling(p, cwd, home) {
|
|
13688
13995
|
if (p === "~" || p.startsWith("~/")) p = home + p.slice(1);
|
|
13689
|
-
return
|
|
13996
|
+
return path7.isAbsolute(p) ? p : `${cwd}/${p}`;
|
|
13690
13997
|
}
|
|
13691
13998
|
function physical(p, followFinal, links = 0) {
|
|
13692
13999
|
if (links > 40) throw new Error("Too many symbolic links");
|
|
13693
14000
|
if (!followFinal) {
|
|
13694
|
-
const parent = physical(
|
|
13695
|
-
return { path: `${parent.path}/${
|
|
14001
|
+
const parent = physical(path7.dirname(p), true, links);
|
|
14002
|
+
return { path: `${parent.path}/${path7.basename(p)}`, missing: parent.missing };
|
|
13696
14003
|
}
|
|
13697
14004
|
try {
|
|
13698
|
-
return { path:
|
|
14005
|
+
return { path: fs7.realpathSync.native(p), missing: false };
|
|
13699
14006
|
} catch (error) {
|
|
13700
14007
|
const code = error.code;
|
|
13701
14008
|
if (!["ENOENT", "ENOTDIR"].includes(code ?? "")) throw error;
|
|
13702
14009
|
try {
|
|
13703
|
-
if (
|
|
13704
|
-
const target =
|
|
14010
|
+
if (fs7.lstatSync(p).isSymbolicLink()) {
|
|
14011
|
+
const target = fs7.readlinkSync(p);
|
|
13705
14012
|
return physical(
|
|
13706
|
-
|
|
14013
|
+
path7.isAbsolute(target) ? target : `${path7.dirname(p)}/${target}`,
|
|
13707
14014
|
true,
|
|
13708
14015
|
links + 1
|
|
13709
14016
|
);
|
|
@@ -13712,13 +14019,13 @@ function physical(p, followFinal, links = 0) {
|
|
|
13712
14019
|
if (!["ENOENT", "ENOTDIR"].includes(linkError.code ?? ""))
|
|
13713
14020
|
throw linkError;
|
|
13714
14021
|
}
|
|
13715
|
-
const parent =
|
|
14022
|
+
const parent = path7.dirname(p);
|
|
13716
14023
|
if (parent === p) return { path: p, missing: true };
|
|
13717
14024
|
const resolved = physical(parent, true, links);
|
|
13718
|
-
return { path: `${resolved.path}/${
|
|
14025
|
+
return { path: `${resolved.path}/${path7.basename(p)}`, missing: true };
|
|
13719
14026
|
}
|
|
13720
14027
|
}
|
|
13721
|
-
function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, home =
|
|
14028
|
+
function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, home = os6.homedir()) {
|
|
13722
14029
|
const context = { cwd, homeDirs: [home, "/root"] };
|
|
13723
14030
|
const accesses = collectFileAccesses(tool, args, context, inspection);
|
|
13724
14031
|
const handledPaths = /* @__PURE__ */ new Set();
|
|
@@ -13741,14 +14048,14 @@ function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, h
|
|
|
13741
14048
|
const raw = absoluteSpelling(access.path, access.cwd ?? cwd, home);
|
|
13742
14049
|
if (access.directoryTarget) {
|
|
13743
14050
|
try {
|
|
13744
|
-
if (!
|
|
14051
|
+
if (!fs7.statSync(absoluteSpelling(access.directoryTarget, access.cwd ?? cwd, home)).isDirectory())
|
|
13745
14052
|
continue;
|
|
13746
14053
|
} catch {
|
|
13747
14054
|
}
|
|
13748
14055
|
}
|
|
13749
14056
|
if (access.operation === "create") {
|
|
13750
14057
|
try {
|
|
13751
|
-
|
|
14058
|
+
fs7.statSync(raw);
|
|
13752
14059
|
continue;
|
|
13753
14060
|
} catch {
|
|
13754
14061
|
}
|
|
@@ -13797,27 +14104,27 @@ function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, h
|
|
|
13797
14104
|
}
|
|
13798
14105
|
|
|
13799
14106
|
// src/utils/provenance.ts
|
|
13800
|
-
import
|
|
13801
|
-
import
|
|
13802
|
-
import
|
|
14107
|
+
import fs8 from "fs";
|
|
14108
|
+
import path8 from "path";
|
|
14109
|
+
import os7 from "os";
|
|
13803
14110
|
var SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
|
|
13804
14111
|
var MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
|
|
13805
14112
|
var USER_PREFIXES = [
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
14113
|
+
path8.join(os7.homedir(), "bin"),
|
|
14114
|
+
path8.join(os7.homedir(), ".local", "bin"),
|
|
14115
|
+
path8.join(os7.homedir(), ".cargo", "bin"),
|
|
14116
|
+
path8.join(os7.homedir(), ".npm-global", "bin"),
|
|
14117
|
+
path8.join(os7.homedir(), ".volta", "bin")
|
|
13811
14118
|
];
|
|
13812
14119
|
var SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
|
|
13813
14120
|
function findInPath(cmd) {
|
|
13814
|
-
if (
|
|
14121
|
+
if (path8.posix.isAbsolute(cmd)) return cmd;
|
|
13815
14122
|
const pathEnv = process.env.PATH ?? "";
|
|
13816
|
-
for (const dir2 of pathEnv.split(
|
|
14123
|
+
for (const dir2 of pathEnv.split(path8.delimiter)) {
|
|
13817
14124
|
if (!dir2) continue;
|
|
13818
|
-
const full =
|
|
14125
|
+
const full = path8.join(dir2, cmd);
|
|
13819
14126
|
try {
|
|
13820
|
-
|
|
14127
|
+
fs8.accessSync(full, fs8.constants.X_OK);
|
|
13821
14128
|
return full;
|
|
13822
14129
|
} catch {
|
|
13823
14130
|
}
|
|
@@ -13828,7 +14135,7 @@ function _classifyPath(resolved, cwd) {
|
|
|
13828
14135
|
if (cwd && resolved.startsWith(cwd + "/")) {
|
|
13829
14136
|
return { trustLevel: "user", reason: "binary in project directory" };
|
|
13830
14137
|
}
|
|
13831
|
-
const osTmp =
|
|
14138
|
+
const osTmp = os7.tmpdir();
|
|
13832
14139
|
const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
|
|
13833
14140
|
if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
|
|
13834
14141
|
return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
|
|
@@ -13846,7 +14153,7 @@ function _classifyPath(resolved, cwd) {
|
|
|
13846
14153
|
}
|
|
13847
14154
|
function checkProvenance(cmd, cwd) {
|
|
13848
14155
|
const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
|
|
13849
|
-
if (
|
|
14156
|
+
if (path8.posix.isAbsolute(bare)) {
|
|
13850
14157
|
const early = _classifyPath(bare, cwd);
|
|
13851
14158
|
if (early.trustLevel === "suspect") {
|
|
13852
14159
|
return { resolvedPath: bare, ...early };
|
|
@@ -13862,7 +14169,7 @@ function checkProvenance(cmd, cwd) {
|
|
|
13862
14169
|
reason: "binary not found in PATH"
|
|
13863
14170
|
};
|
|
13864
14171
|
}
|
|
13865
|
-
resolved =
|
|
14172
|
+
resolved = fs8.realpathSync(found);
|
|
13866
14173
|
} catch {
|
|
13867
14174
|
return {
|
|
13868
14175
|
resolvedPath: cmd,
|
|
@@ -13871,7 +14178,7 @@ function checkProvenance(cmd, cwd) {
|
|
|
13871
14178
|
};
|
|
13872
14179
|
}
|
|
13873
14180
|
try {
|
|
13874
|
-
const stat =
|
|
14181
|
+
const stat = fs8.statSync(resolved);
|
|
13875
14182
|
if (stat.mode & 2) {
|
|
13876
14183
|
return {
|
|
13877
14184
|
resolvedPath: resolved,
|
|
@@ -13896,7 +14203,7 @@ init_dist();
|
|
|
13896
14203
|
init_dist();
|
|
13897
14204
|
async function evaluatePolicy2(toolName, args, agent, cwd, opts) {
|
|
13898
14205
|
const config = opts?.config ?? getConfig(cwd);
|
|
13899
|
-
const prepared = opts?.fileAccess ?? prepareFileAccesses(toolName, args, cwd, config.policy
|
|
14206
|
+
const prepared = opts?.fileAccess ?? prepareFileAccesses(toolName, args, cwd, inspectionLayers(config.policy));
|
|
13900
14207
|
const activeEnvironment = getActiveEnvironment(config) ?? void 0;
|
|
13901
14208
|
return evaluatePolicy(
|
|
13902
14209
|
config,
|
|
@@ -13925,32 +14232,32 @@ function isIgnoredTool2(toolName) {
|
|
|
13925
14232
|
}
|
|
13926
14233
|
|
|
13927
14234
|
// src/auth/state.ts
|
|
13928
|
-
import
|
|
13929
|
-
import
|
|
13930
|
-
import
|
|
14235
|
+
import fs10 from "fs";
|
|
14236
|
+
import path10 from "path";
|
|
14237
|
+
import os8 from "os";
|
|
13931
14238
|
|
|
13932
14239
|
// src/utils/atomic-write.ts
|
|
13933
|
-
import
|
|
13934
|
-
import
|
|
14240
|
+
import fs9 from "fs";
|
|
14241
|
+
import path9 from "path";
|
|
13935
14242
|
import { randomUUID } from "crypto";
|
|
13936
14243
|
function atomicWriteSync(filePath, data, options) {
|
|
13937
|
-
const dir2 =
|
|
13938
|
-
if (!
|
|
14244
|
+
const dir2 = path9.dirname(filePath);
|
|
14245
|
+
if (!fs9.existsSync(dir2)) fs9.mkdirSync(dir2, { recursive: true });
|
|
13939
14246
|
const tmpPath = `${filePath}.${randomUUID()}.tmp`;
|
|
13940
14247
|
try {
|
|
13941
|
-
|
|
14248
|
+
fs9.writeFileSync(tmpPath, data, options);
|
|
13942
14249
|
} catch (err) {
|
|
13943
14250
|
try {
|
|
13944
|
-
|
|
14251
|
+
fs9.unlinkSync(tmpPath);
|
|
13945
14252
|
} catch {
|
|
13946
14253
|
}
|
|
13947
14254
|
throw err;
|
|
13948
14255
|
}
|
|
13949
14256
|
try {
|
|
13950
|
-
|
|
14257
|
+
fs9.renameSync(tmpPath, filePath);
|
|
13951
14258
|
} catch (err) {
|
|
13952
14259
|
try {
|
|
13953
|
-
|
|
14260
|
+
fs9.unlinkSync(tmpPath);
|
|
13954
14261
|
} catch {
|
|
13955
14262
|
}
|
|
13956
14263
|
throw err;
|
|
@@ -13958,8 +14265,8 @@ function atomicWriteSync(filePath, data, options) {
|
|
|
13958
14265
|
}
|
|
13959
14266
|
|
|
13960
14267
|
// src/auth/state.ts
|
|
13961
|
-
var PAUSED_FILE =
|
|
13962
|
-
var TRUST_FILE =
|
|
14268
|
+
var PAUSED_FILE = path10.join(os8.homedir(), ".node9", "PAUSED");
|
|
14269
|
+
var TRUST_FILE = path10.join(os8.homedir(), ".node9", "trust.json");
|
|
13963
14270
|
function extractCommandPattern(toolName, args) {
|
|
13964
14271
|
const lower2 = toolName.toLowerCase();
|
|
13965
14272
|
if (lower2 !== "bash" && lower2 !== "execute_bash" && lower2 !== "shell") return void 0;
|
|
@@ -13971,11 +14278,11 @@ function extractCommandPattern(toolName, args) {
|
|
|
13971
14278
|
}
|
|
13972
14279
|
function checkPause() {
|
|
13973
14280
|
try {
|
|
13974
|
-
if (!
|
|
13975
|
-
const state = JSON.parse(
|
|
14281
|
+
if (!fs10.existsSync(PAUSED_FILE)) return { paused: false };
|
|
14282
|
+
const state = JSON.parse(fs10.readFileSync(PAUSED_FILE, "utf-8"));
|
|
13976
14283
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
13977
14284
|
try {
|
|
13978
|
-
|
|
14285
|
+
fs10.unlinkSync(PAUSED_FILE);
|
|
13979
14286
|
} catch {
|
|
13980
14287
|
}
|
|
13981
14288
|
return { paused: false };
|
|
@@ -13987,8 +14294,8 @@ function checkPause() {
|
|
|
13987
14294
|
}
|
|
13988
14295
|
function getActiveTrustSession(toolName, args) {
|
|
13989
14296
|
try {
|
|
13990
|
-
if (!
|
|
13991
|
-
const trust = JSON.parse(
|
|
14297
|
+
if (!fs10.existsSync(TRUST_FILE)) return false;
|
|
14298
|
+
const trust = JSON.parse(fs10.readFileSync(TRUST_FILE, "utf-8"));
|
|
13992
14299
|
const now = Date.now();
|
|
13993
14300
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
13994
14301
|
if (active.length !== trust.entries.length) {
|
|
@@ -14011,8 +14318,8 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
14011
14318
|
try {
|
|
14012
14319
|
let trust = { entries: [] };
|
|
14013
14320
|
try {
|
|
14014
|
-
if (
|
|
14015
|
-
trust = JSON.parse(
|
|
14321
|
+
if (fs10.existsSync(TRUST_FILE)) {
|
|
14322
|
+
trust = JSON.parse(fs10.readFileSync(TRUST_FILE, "utf-8"));
|
|
14016
14323
|
}
|
|
14017
14324
|
} catch {
|
|
14018
14325
|
}
|
|
@@ -14034,9 +14341,9 @@ function writeTrustSession(toolName, durationMs, args) {
|
|
|
14034
14341
|
}
|
|
14035
14342
|
function getPersistentDecision(toolName) {
|
|
14036
14343
|
try {
|
|
14037
|
-
const file2 =
|
|
14038
|
-
if (!
|
|
14039
|
-
const decisions = JSON.parse(
|
|
14344
|
+
const file2 = path10.join(os8.homedir(), ".node9", "decisions.json");
|
|
14345
|
+
if (!fs10.existsSync(file2)) return null;
|
|
14346
|
+
const decisions = JSON.parse(fs10.readFileSync(file2, "utf-8"));
|
|
14040
14347
|
const d = decisions[toolName];
|
|
14041
14348
|
if (d === "allow" || d === "deny") return d;
|
|
14042
14349
|
} catch {
|
|
@@ -14045,11 +14352,11 @@ function getPersistentDecision(toolName) {
|
|
|
14045
14352
|
}
|
|
14046
14353
|
|
|
14047
14354
|
// src/auth/daemon.ts
|
|
14048
|
-
import
|
|
14355
|
+
import fs11 from "fs";
|
|
14049
14356
|
import net from "net";
|
|
14050
|
-
import
|
|
14051
|
-
import
|
|
14052
|
-
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
14357
|
+
import path11 from "path";
|
|
14358
|
+
import os9 from "os";
|
|
14359
|
+
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path11.join(os9.tmpdir(), "node9-activity.sock");
|
|
14053
14360
|
function notifyActivitySocket(data) {
|
|
14054
14361
|
return new Promise((resolve) => {
|
|
14055
14362
|
try {
|
|
@@ -14091,9 +14398,9 @@ var DAEMON_PORT = resolveDaemonPort();
|
|
|
14091
14398
|
var DAEMON_HOST = "127.0.0.1";
|
|
14092
14399
|
function getInternalToken() {
|
|
14093
14400
|
try {
|
|
14094
|
-
const pidFile =
|
|
14095
|
-
if (!
|
|
14096
|
-
const data = JSON.parse(
|
|
14401
|
+
const pidFile = path11.join(os9.homedir(), ".node9", "daemon.pid");
|
|
14402
|
+
if (!fs11.existsSync(pidFile)) return null;
|
|
14403
|
+
const data = JSON.parse(fs11.readFileSync(pidFile, "utf-8"));
|
|
14097
14404
|
process.kill(data.pid, 0);
|
|
14098
14405
|
return data.internalToken ?? null;
|
|
14099
14406
|
} catch {
|
|
@@ -14101,12 +14408,12 @@ function getInternalToken() {
|
|
|
14101
14408
|
}
|
|
14102
14409
|
}
|
|
14103
14410
|
function isDaemonRunning() {
|
|
14104
|
-
const pidFile =
|
|
14105
|
-
if (
|
|
14411
|
+
const pidFile = path11.join(os9.homedir(), ".node9", "daemon.pid");
|
|
14412
|
+
if (fs11.existsSync(pidFile)) {
|
|
14106
14413
|
let pid;
|
|
14107
14414
|
let port;
|
|
14108
14415
|
try {
|
|
14109
|
-
const data = JSON.parse(
|
|
14416
|
+
const data = JSON.parse(fs11.readFileSync(pidFile, "utf-8"));
|
|
14110
14417
|
pid = data.pid;
|
|
14111
14418
|
port = data.port;
|
|
14112
14419
|
} catch {
|
|
@@ -14124,7 +14431,7 @@ function isDaemonRunning() {
|
|
|
14124
14431
|
} catch (err) {
|
|
14125
14432
|
if (err instanceof Error && "code" in err && err.code === "ESRCH") {
|
|
14126
14433
|
try {
|
|
14127
|
-
|
|
14434
|
+
fs11.unlinkSync(pidFile);
|
|
14128
14435
|
} catch {
|
|
14129
14436
|
}
|
|
14130
14437
|
}
|
|
@@ -14332,10 +14639,10 @@ import os18 from "os";
|
|
|
14332
14639
|
|
|
14333
14640
|
// src/ui/native.ts
|
|
14334
14641
|
import { spawn } from "child_process";
|
|
14335
|
-
import
|
|
14642
|
+
import path13 from "path";
|
|
14336
14643
|
|
|
14337
14644
|
// src/context-sniper.ts
|
|
14338
|
-
import
|
|
14645
|
+
import path12 from "path";
|
|
14339
14646
|
function smartTruncate(str, maxLen = 500) {
|
|
14340
14647
|
if (str.length <= maxLen) return str;
|
|
14341
14648
|
const edge = Math.floor(maxLen / 2) - 3;
|
|
@@ -14403,7 +14710,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
14403
14710
|
intent = "EDIT";
|
|
14404
14711
|
if (obj.file_path) {
|
|
14405
14712
|
editFilePath = String(obj.file_path);
|
|
14406
|
-
editFileName =
|
|
14713
|
+
editFileName = path12.basename(editFilePath);
|
|
14407
14714
|
}
|
|
14408
14715
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
14409
14716
|
contextSnippet = result.snippet;
|
|
@@ -14471,7 +14778,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
14471
14778
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
14472
14779
|
const obj = parsed;
|
|
14473
14780
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
14474
|
-
const file2 = obj.file_path ?
|
|
14781
|
+
const file2 = obj.file_path ? path13.basename(String(obj.file_path)) : "file";
|
|
14475
14782
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
14476
14783
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
14477
14784
|
return {
|
|
@@ -14674,24 +14981,24 @@ end run`;
|
|
|
14674
14981
|
}
|
|
14675
14982
|
|
|
14676
14983
|
// src/canary/registry.ts
|
|
14677
|
-
import
|
|
14678
|
-
import
|
|
14679
|
-
import
|
|
14984
|
+
import fs13 from "fs";
|
|
14985
|
+
import os11 from "os";
|
|
14986
|
+
import path15 from "path";
|
|
14680
14987
|
init_dist();
|
|
14681
14988
|
|
|
14682
14989
|
// src/shields/jail.ts
|
|
14683
|
-
import
|
|
14684
|
-
import
|
|
14685
|
-
import
|
|
14990
|
+
import fs12 from "fs";
|
|
14991
|
+
import os10 from "os";
|
|
14992
|
+
import path14 from "path";
|
|
14686
14993
|
init_dist();
|
|
14687
14994
|
var USER_JAIL_SHIELD = "user-jail";
|
|
14688
14995
|
function jailStorePath() {
|
|
14689
|
-
return
|
|
14996
|
+
return path14.join(os10.homedir(), ".node9", "jail-paths.json");
|
|
14690
14997
|
}
|
|
14691
14998
|
function readJailPaths() {
|
|
14692
14999
|
let text;
|
|
14693
15000
|
try {
|
|
14694
|
-
text =
|
|
15001
|
+
text = fs12.readFileSync(jailStorePath(), "utf8");
|
|
14695
15002
|
} catch (err) {
|
|
14696
15003
|
if (err.code === "ENOENT") return [];
|
|
14697
15004
|
throw err;
|
|
@@ -14720,7 +15027,7 @@ function findJailedPathIn(candidate, paths) {
|
|
|
14720
15027
|
|
|
14721
15028
|
// src/canary/registry.ts
|
|
14722
15029
|
function canaryStorePath() {
|
|
14723
|
-
return
|
|
15030
|
+
return path15.join(os11.homedir(), ".node9", "canaries.json");
|
|
14724
15031
|
}
|
|
14725
15032
|
function isRecord(x) {
|
|
14726
15033
|
if (!x || typeof x !== "object") return false;
|
|
@@ -14731,7 +15038,7 @@ function loadCanaries(opts) {
|
|
|
14731
15038
|
const p = canaryStorePath();
|
|
14732
15039
|
let raw;
|
|
14733
15040
|
try {
|
|
14734
|
-
raw =
|
|
15041
|
+
raw = fs13.readFileSync(p, "utf-8");
|
|
14735
15042
|
} catch (e) {
|
|
14736
15043
|
if (e.code === "ENOENT") return [];
|
|
14737
15044
|
throw e;
|
|
@@ -14760,26 +15067,26 @@ init_audit();
|
|
|
14760
15067
|
init_dist();
|
|
14761
15068
|
|
|
14762
15069
|
// src/exposure/known-hosts.ts
|
|
14763
|
-
import
|
|
14764
|
-
import
|
|
15070
|
+
import fs15 from "fs";
|
|
15071
|
+
import path17 from "path";
|
|
14765
15072
|
|
|
14766
15073
|
// src/utils/git-remotes.ts
|
|
14767
15074
|
init_dist();
|
|
14768
|
-
import
|
|
14769
|
-
import
|
|
15075
|
+
import fs14 from "fs";
|
|
15076
|
+
import path16 from "path";
|
|
14770
15077
|
function readSmallFile(p, max) {
|
|
14771
15078
|
let fd;
|
|
14772
15079
|
try {
|
|
14773
|
-
fd =
|
|
14774
|
-
const st =
|
|
15080
|
+
fd = fs14.openSync(p, fs14.constants.O_RDONLY | (fs14.constants.O_NOFOLLOW ?? 0));
|
|
15081
|
+
const st = fs14.fstatSync(fd);
|
|
14775
15082
|
if (!st.isFile() || st.size > max) return null;
|
|
14776
15083
|
const buf = Buffer.alloc(st.size);
|
|
14777
|
-
const n =
|
|
15084
|
+
const n = fs14.readSync(fd, buf, 0, st.size, 0);
|
|
14778
15085
|
return buf.subarray(0, n).toString("utf8");
|
|
14779
15086
|
} catch {
|
|
14780
15087
|
return null;
|
|
14781
15088
|
} finally {
|
|
14782
|
-
if (fd !== void 0)
|
|
15089
|
+
if (fd !== void 0) fs14.closeSync(fd);
|
|
14783
15090
|
}
|
|
14784
15091
|
}
|
|
14785
15092
|
function gitRemoteHost(url) {
|
|
@@ -14798,35 +15105,35 @@ function gitRemoteHost(url) {
|
|
|
14798
15105
|
return null;
|
|
14799
15106
|
}
|
|
14800
15107
|
function findGitConfig(dir2, walkUp = true) {
|
|
14801
|
-
let cur =
|
|
15108
|
+
let cur = path16.resolve(dir2);
|
|
14802
15109
|
for (let i = 0; i < (walkUp ? 64 : 1); i++) {
|
|
14803
|
-
const dotGit =
|
|
14804
|
-
const direct = readSmallFile(
|
|
15110
|
+
const dotGit = path16.join(cur, ".git");
|
|
15111
|
+
const direct = readSmallFile(path16.join(dotGit, "config"), 1e6);
|
|
14805
15112
|
if (direct !== null) return direct;
|
|
14806
15113
|
const pointer = readSmallFile(dotGit, 4096);
|
|
14807
15114
|
const m = pointer && /^gitdir:\s*(.+?)\s*$/m.exec(pointer);
|
|
14808
15115
|
if (m) {
|
|
14809
|
-
let gitDir =
|
|
14810
|
-
const common = readSmallFile(
|
|
14811
|
-
if (common !== null) gitDir =
|
|
14812
|
-
return readSmallFile(
|
|
15116
|
+
let gitDir = path16.resolve(cur, m[1]);
|
|
15117
|
+
const common = readSmallFile(path16.join(gitDir, "commondir"), 4096);
|
|
15118
|
+
if (common !== null) gitDir = path16.resolve(gitDir, common.trim());
|
|
15119
|
+
return readSmallFile(path16.join(gitDir, "config"), 1e6);
|
|
14813
15120
|
}
|
|
14814
|
-
const parent =
|
|
15121
|
+
const parent = path16.dirname(cur);
|
|
14815
15122
|
if (parent === cur) return null;
|
|
14816
15123
|
cur = parent;
|
|
14817
15124
|
}
|
|
14818
15125
|
return null;
|
|
14819
15126
|
}
|
|
14820
|
-
var
|
|
15127
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
14821
15128
|
function readGitRemotes(dir2, opts = {}) {
|
|
14822
15129
|
if (!dir2) return [];
|
|
14823
15130
|
const walkUp = opts.walkUp !== false;
|
|
14824
|
-
const key = `${walkUp ? "up" : "at"}:${
|
|
14825
|
-
const hit =
|
|
15131
|
+
const key = `${walkUp ? "up" : "at"}:${path16.resolve(dir2)}`;
|
|
15132
|
+
const hit = cache2.get(key);
|
|
14826
15133
|
if (hit) return hit;
|
|
14827
15134
|
const out = [];
|
|
14828
15135
|
try {
|
|
14829
|
-
const cfg = findGitConfig(
|
|
15136
|
+
const cfg = findGitConfig(path16.resolve(dir2), walkUp);
|
|
14830
15137
|
if (cfg !== null) {
|
|
14831
15138
|
let current = null;
|
|
14832
15139
|
for (const line of cfg.split(/\r?\n/)) {
|
|
@@ -14848,7 +15155,7 @@ function readGitRemotes(dir2, opts = {}) {
|
|
|
14848
15155
|
}
|
|
14849
15156
|
} catch {
|
|
14850
15157
|
}
|
|
14851
|
-
|
|
15158
|
+
cache2.set(key, out);
|
|
14852
15159
|
return out;
|
|
14853
15160
|
}
|
|
14854
15161
|
|
|
@@ -14860,17 +15167,17 @@ var ECOSYSTEMS = [
|
|
|
14860
15167
|
["Cargo.toml", "host:crates.io"],
|
|
14861
15168
|
["Gemfile", "host:rubygems.org"]
|
|
14862
15169
|
];
|
|
14863
|
-
var
|
|
15170
|
+
var cache3 = /* @__PURE__ */ new Map();
|
|
14864
15171
|
function manifestRoot(dir2) {
|
|
14865
|
-
let cur =
|
|
15172
|
+
let cur = path17.resolve(dir2);
|
|
14866
15173
|
for (let i = 0; i < 64; i++) {
|
|
14867
15174
|
for (const [m] of ECOSYSTEMS) {
|
|
14868
15175
|
try {
|
|
14869
|
-
if (
|
|
15176
|
+
if (fs15.existsSync(path17.join(cur, m))) return cur;
|
|
14870
15177
|
} catch {
|
|
14871
15178
|
}
|
|
14872
15179
|
}
|
|
14873
|
-
const parent =
|
|
15180
|
+
const parent = path17.dirname(cur);
|
|
14874
15181
|
if (parent === cur) return null;
|
|
14875
15182
|
cur = parent;
|
|
14876
15183
|
}
|
|
@@ -14879,8 +15186,8 @@ function manifestRoot(dir2) {
|
|
|
14879
15186
|
function knownProjectHosts(dir2) {
|
|
14880
15187
|
const empty = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
|
|
14881
15188
|
if (!dir2) return empty;
|
|
14882
|
-
const key =
|
|
14883
|
-
const hit =
|
|
15189
|
+
const key = path17.resolve(dir2);
|
|
15190
|
+
const hit = cache3.get(key);
|
|
14884
15191
|
if (hit) return hit;
|
|
14885
15192
|
const out = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
|
|
14886
15193
|
try {
|
|
@@ -14892,25 +15199,25 @@ function knownProjectHosts(dir2) {
|
|
|
14892
15199
|
const root = manifestRoot(key);
|
|
14893
15200
|
if (root !== null) {
|
|
14894
15201
|
for (const [m, registry] of ECOSYSTEMS) {
|
|
14895
|
-
if (
|
|
15202
|
+
if (fs15.existsSync(path17.join(root, m))) out.hosts.add(registry);
|
|
14896
15203
|
}
|
|
14897
15204
|
try {
|
|
14898
|
-
if (
|
|
15205
|
+
if (fs15.readdirSync(root).some((f) => f.endsWith(".gemspec")))
|
|
14899
15206
|
out.hosts.add("host:rubygems.org");
|
|
14900
15207
|
} catch {
|
|
14901
15208
|
}
|
|
14902
15209
|
}
|
|
14903
15210
|
} catch {
|
|
14904
15211
|
}
|
|
14905
|
-
|
|
15212
|
+
cache3.set(key, out);
|
|
14906
15213
|
return out;
|
|
14907
15214
|
}
|
|
14908
15215
|
|
|
14909
15216
|
// src/exposure/writes.ts
|
|
14910
15217
|
init_dist();
|
|
14911
|
-
import
|
|
14912
|
-
import
|
|
14913
|
-
import
|
|
15218
|
+
import fs16 from "fs";
|
|
15219
|
+
import os12 from "os";
|
|
15220
|
+
import path18 from "path";
|
|
14914
15221
|
var WRITE_OPS = /* @__PURE__ */ new Set(["write", "create", "remove"]);
|
|
14915
15222
|
var LIFECYCLE_RE = /"(scripts|preinstall|install|postinstall|prepare|prepublish|prepublishOnly|prepack|postpack|publish|postpublish)"\s*:/;
|
|
14916
15223
|
function writtenContent(args) {
|
|
@@ -14939,7 +15246,7 @@ function packageScriptsTouched(args, absPath, shellCommand) {
|
|
|
14939
15246
|
if (typeof a.content === "string") {
|
|
14940
15247
|
try {
|
|
14941
15248
|
const next = JSON.parse(a.content);
|
|
14942
|
-
const prev = JSON.parse(
|
|
15249
|
+
const prev = JSON.parse(fs16.readFileSync(absPath, "utf8"));
|
|
14943
15250
|
return JSON.stringify(next.scripts ?? null) !== JSON.stringify(prev.scripts ?? null);
|
|
14944
15251
|
} catch {
|
|
14945
15252
|
return LIFECYCLE_RE.test(a.content);
|
|
@@ -14949,8 +15256,8 @@ function packageScriptsTouched(args, absPath, shellCommand) {
|
|
|
14949
15256
|
}
|
|
14950
15257
|
function judgeWritesAfterExposure(opts) {
|
|
14951
15258
|
const holds = [];
|
|
14952
|
-
const home = opts.home ??
|
|
14953
|
-
const tmp = normalizeFilePath(
|
|
15259
|
+
const home = opts.home ?? os12.homedir();
|
|
15260
|
+
const tmp = normalizeFilePath(os12.tmpdir());
|
|
14954
15261
|
const root = opts.cwd ? normalizeFilePath(opts.cwd, "", home) : null;
|
|
14955
15262
|
const seen = /* @__PURE__ */ new Set();
|
|
14956
15263
|
for (const access of opts.accesses) {
|
|
@@ -14960,7 +15267,7 @@ function judgeWritesAfterExposure(opts) {
|
|
|
14960
15267
|
seen.add(p);
|
|
14961
15268
|
if (p === "/dev" || p.startsWith("/dev/")) continue;
|
|
14962
15269
|
if (root !== null && isInside(root, p)) {
|
|
14963
|
-
if (isAgentInstructionPath(p, root) && (
|
|
15270
|
+
if (isAgentInstructionPath(p, root) && (path18.basename(p) !== "package.json" || packageScriptsTouched(opts.args, p, opts.shellCommand)))
|
|
14964
15271
|
holds.push({ path: p, why: "agent-instruction" });
|
|
14965
15272
|
continue;
|
|
14966
15273
|
}
|
|
@@ -14982,25 +15289,25 @@ function judgeWritesAfterExposure(opts) {
|
|
|
14982
15289
|
|
|
14983
15290
|
// src/exposure/marker.ts
|
|
14984
15291
|
import crypto3 from "crypto";
|
|
14985
|
-
import
|
|
14986
|
-
import
|
|
14987
|
-
import
|
|
15292
|
+
import fs17 from "fs";
|
|
15293
|
+
import os13 from "os";
|
|
15294
|
+
import path19 from "path";
|
|
14988
15295
|
|
|
14989
15296
|
// src/daemon/exposure-store.ts
|
|
14990
15297
|
var SESSION_EXPOSURE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
14991
15298
|
|
|
14992
15299
|
// src/exposure/marker.ts
|
|
14993
15300
|
function dir() {
|
|
14994
|
-
return
|
|
15301
|
+
return path19.join(os13.homedir(), ".node9", "exposure-sessions");
|
|
14995
15302
|
}
|
|
14996
15303
|
function file(sessionId) {
|
|
14997
15304
|
const id = crypto3.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
14998
|
-
return
|
|
15305
|
+
return path19.join(dir(), id);
|
|
14999
15306
|
}
|
|
15000
15307
|
function maybeSessionExposed(sessionId) {
|
|
15001
15308
|
if (!sessionId) return false;
|
|
15002
15309
|
try {
|
|
15003
|
-
const st =
|
|
15310
|
+
const st = fs17.statSync(file(sessionId));
|
|
15004
15311
|
return Date.now() - st.mtimeMs < SESSION_EXPOSURE_TTL_MS;
|
|
15005
15312
|
} catch (e) {
|
|
15006
15313
|
return e.code !== "ENOENT";
|
|
@@ -15008,20 +15315,20 @@ function maybeSessionExposed(sessionId) {
|
|
|
15008
15315
|
}
|
|
15009
15316
|
|
|
15010
15317
|
// src/exposure/roles.ts
|
|
15011
|
-
import
|
|
15318
|
+
import fs19 from "fs";
|
|
15012
15319
|
|
|
15013
15320
|
// src/daemon/mcp-tools.ts
|
|
15014
|
-
import
|
|
15015
|
-
import
|
|
15016
|
-
import
|
|
15321
|
+
import fs18 from "fs";
|
|
15322
|
+
import path20 from "path";
|
|
15323
|
+
import os14 from "os";
|
|
15017
15324
|
function getMcpToolsFile() {
|
|
15018
|
-
return
|
|
15325
|
+
return path20.join(os14.homedir(), ".node9", "mcp-tools.json");
|
|
15019
15326
|
}
|
|
15020
15327
|
function readMcpToolsConfig() {
|
|
15021
15328
|
try {
|
|
15022
15329
|
const file2 = getMcpToolsFile();
|
|
15023
|
-
if (!
|
|
15024
|
-
const raw =
|
|
15330
|
+
if (!fs18.existsSync(file2)) return {};
|
|
15331
|
+
const raw = fs18.readFileSync(file2, "utf-8");
|
|
15025
15332
|
return JSON.parse(raw);
|
|
15026
15333
|
} catch {
|
|
15027
15334
|
return {};
|
|
@@ -15038,7 +15345,7 @@ var inventoryCache = null;
|
|
|
15038
15345
|
var inventoryStamp = null;
|
|
15039
15346
|
function inventoryFileStamp() {
|
|
15040
15347
|
try {
|
|
15041
|
-
const st =
|
|
15348
|
+
const st = fs19.statSync(getMcpToolsFile());
|
|
15042
15349
|
return `${st.mtimeMs}:${st.size}`;
|
|
15043
15350
|
} catch {
|
|
15044
15351
|
return "";
|
|
@@ -15117,68 +15424,6 @@ function sourceWord(toolName) {
|
|
|
15117
15424
|
return "tool result";
|
|
15118
15425
|
}
|
|
15119
15426
|
|
|
15120
|
-
// src/auth/trusted-recipients.ts
|
|
15121
|
-
init_dist();
|
|
15122
|
-
import fs19 from "fs";
|
|
15123
|
-
import path20 from "path";
|
|
15124
|
-
import os14 from "os";
|
|
15125
|
-
function getTrustedRecipientsPath() {
|
|
15126
|
-
return path20.join(os14.homedir(), ".node9", "trusted-recipients.json");
|
|
15127
|
-
}
|
|
15128
|
-
function readTrustedRecipients() {
|
|
15129
|
-
try {
|
|
15130
|
-
const raw = fs19.readFileSync(getTrustedRecipientsPath(), "utf8");
|
|
15131
|
-
const parsed = JSON.parse(raw);
|
|
15132
|
-
return Array.isArray(parsed.entries) ? parsed.entries.filter((e) => e && typeof e.value === "string") : [];
|
|
15133
|
-
} catch {
|
|
15134
|
-
return [];
|
|
15135
|
-
}
|
|
15136
|
-
}
|
|
15137
|
-
function writeTrustedRecipients(entries) {
|
|
15138
|
-
const filePath = getTrustedRecipientsPath();
|
|
15139
|
-
fs19.mkdirSync(path20.dirname(filePath), { recursive: true });
|
|
15140
|
-
const tmp = filePath + ".node9-tmp";
|
|
15141
|
-
fs19.writeFileSync(tmp, JSON.stringify({ entries }, null, 2), { mode: 384 });
|
|
15142
|
-
fs19.renameSync(tmp, filePath);
|
|
15143
|
-
}
|
|
15144
|
-
function normalizeRecipient(raw) {
|
|
15145
|
-
const v = raw.trim();
|
|
15146
|
-
if (!v) return null;
|
|
15147
|
-
if (/^(email|host|iban):/i.test(v)) {
|
|
15148
|
-
const i = v.indexOf(":");
|
|
15149
|
-
const kind = v.slice(0, i).toLowerCase();
|
|
15150
|
-
const inner = normalizeRecipient(v.slice(i + 1));
|
|
15151
|
-
return inner && inner.startsWith(`${kind}:`) ? inner : null;
|
|
15152
|
-
}
|
|
15153
|
-
const found = extractArgDestinations({ to: v });
|
|
15154
|
-
return found.length === 1 ? found[0] : null;
|
|
15155
|
-
}
|
|
15156
|
-
function addTrustedRecipient(raw, addedBy = "user") {
|
|
15157
|
-
const value = normalizeRecipient(raw);
|
|
15158
|
-
if (!value) return null;
|
|
15159
|
-
const entries = readTrustedRecipients();
|
|
15160
|
-
if (!entries.some((e) => e.value === value)) {
|
|
15161
|
-
entries.push({ value, addedAt: Date.now(), addedBy });
|
|
15162
|
-
writeTrustedRecipients(entries);
|
|
15163
|
-
}
|
|
15164
|
-
return value;
|
|
15165
|
-
}
|
|
15166
|
-
var cache3 = null;
|
|
15167
|
-
function mtimeOf() {
|
|
15168
|
-
try {
|
|
15169
|
-
return fs19.statSync(getTrustedRecipientsPath()).mtimeMs;
|
|
15170
|
-
} catch {
|
|
15171
|
-
return 0;
|
|
15172
|
-
}
|
|
15173
|
-
}
|
|
15174
|
-
function isTrustedRecipient(value) {
|
|
15175
|
-
const mtime = mtimeOf();
|
|
15176
|
-
if (!cache3 || cache3.mtime !== mtime) {
|
|
15177
|
-
cache3 = { mtime, values: new Set(readTrustedRecipients().map((e) => e.value)) };
|
|
15178
|
-
}
|
|
15179
|
-
return cache3.values.has(value);
|
|
15180
|
-
}
|
|
15181
|
-
|
|
15182
15427
|
// src/auth/cloud.ts
|
|
15183
15428
|
init_audit();
|
|
15184
15429
|
import fs21 from "fs";
|
|
@@ -15273,11 +15518,15 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
|
|
|
15273
15518
|
cwd: process.cwd(),
|
|
15274
15519
|
platform: os15.platform()
|
|
15275
15520
|
},
|
|
15276
|
-
//
|
|
15277
|
-
//
|
|
15521
|
+
// The SaaS validates riskMetadata strictly and would drop it whole on
|
|
15522
|
+
// an unknown key, so only fields its schema declares go on the wire.
|
|
15523
|
+
// exposureDestinations ships since the dashboard's 2b contract
|
|
15524
|
+
// (fw#318): "Approve and trust" needs them.
|
|
15278
15525
|
...riskMetadata && {
|
|
15279
15526
|
riskMetadata: Object.fromEntries(
|
|
15280
|
-
Object.entries(riskMetadata).filter(
|
|
15527
|
+
Object.entries(riskMetadata).filter(
|
|
15528
|
+
([k, v]) => !Array.isArray(v) || k === "exposureDestinations"
|
|
15529
|
+
)
|
|
15281
15530
|
)
|
|
15282
15531
|
},
|
|
15283
15532
|
...ciContext && { ciContext },
|
|
@@ -16000,7 +16249,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
16000
16249
|
let fileAccess;
|
|
16001
16250
|
let fileFinding;
|
|
16002
16251
|
try {
|
|
16003
|
-
fileAccess = prepareFileAccesses(toolName, args, options?.cwd, config.policy
|
|
16252
|
+
fileAccess = prepareFileAccesses(toolName, args, options?.cwd, inspectionLayers(config.policy));
|
|
16004
16253
|
fileFinding = isManual ? null : evaluateProtectedFileAccess(
|
|
16005
16254
|
fileAccess.accesses,
|
|
16006
16255
|
fileAccess,
|
|
@@ -16019,7 +16268,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
16019
16268
|
fileAccess = { cwd: options?.cwd, homeDirs: [], accesses: [], handledPaths: /* @__PURE__ */ new Set() };
|
|
16020
16269
|
fileFinding = isManual ? null : incompleteFileVerdict();
|
|
16021
16270
|
}
|
|
16022
|
-
const shellInput = resolveShellInput(toolName, args, config.policy
|
|
16271
|
+
const shellInput = resolveShellInput(toolName, args, inspectionLayers(config.policy));
|
|
16023
16272
|
const networkShell = shellInput.kind === "shell" && NETWORK_COMMAND_RE.test(shellInput.command);
|
|
16024
16273
|
const shellReview = !isManual && shellInput.kind === "uninspectable";
|
|
16025
16274
|
if (shellReview) {
|
|
@@ -16129,6 +16378,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
16129
16378
|
let exposureReview = null;
|
|
16130
16379
|
let exposureKeys = [];
|
|
16131
16380
|
let exposureDestinations = [];
|
|
16381
|
+
let exposureFromEid;
|
|
16132
16382
|
const ucPolicy = config.policy.untrustedContent;
|
|
16133
16383
|
if (ucPolicy?.enabled && !taintWarning && meta?.sessionId && !isManual) {
|
|
16134
16384
|
const role = toolRole(toolName, ucPolicy);
|
|
@@ -16136,7 +16386,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
16136
16386
|
const shellExp = shellInput.kind === "shell" ? classifyShellExposure(shellInput.command) : null;
|
|
16137
16387
|
const bareName = splitMcpToolName(toolName)?.bare;
|
|
16138
16388
|
const mcpFileWrite = bareName !== void 0 && fileToolOperation(bareName, args) === "write";
|
|
16139
|
-
const writes = (mcpFileWrite ? prepareFileAccesses(bareName, args, options?.cwd, config.policy
|
|
16389
|
+
const writes = (mcpFileWrite ? prepareFileAccesses(bareName, args, options?.cwd, inspectionLayers(config.policy)).accesses : fileAccess.accesses).filter((a) => ["write", "create", "remove"].includes(a.operation));
|
|
16140
16390
|
const sendsSomething = acts && !mcpFileWrite || !!shellExp?.sends;
|
|
16141
16391
|
const mayBeExposed = daemonSaysExposed ?? (maybeSessionExposed(meta.sessionId) || writes.length > 0 && judgeWritesAfterExposure({
|
|
16142
16392
|
accesses: writes,
|
|
@@ -16180,7 +16430,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
16180
16430
|
const isKnown = (d) => {
|
|
16181
16431
|
const domain = destinationDomain(d);
|
|
16182
16432
|
const value = destinationValue(d);
|
|
16183
|
-
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));
|
|
16433
|
+
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));
|
|
16184
16434
|
};
|
|
16185
16435
|
const approvedOnce = (key) => ucPolicy.afterExposure === "ask-once" && approved.has(key);
|
|
16186
16436
|
const repos = (shellExp?.sends ? shellExp.repos : []).filter(
|
|
@@ -16244,6 +16494,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
16244
16494
|
);
|
|
16245
16495
|
}
|
|
16246
16496
|
exposureReview = `\u{1F6E1}\uFE0F Node9: this session read ${read}. This call ${parts.join("; and ")}.`;
|
|
16497
|
+
exposureFromEid = last?.eid;
|
|
16498
|
+
if (exposureFromEid) meta = { ...meta, exposureFromEid };
|
|
16247
16499
|
exposureKeys = [
|
|
16248
16500
|
...held.filter((h) => h.dest || h.why === "unresolved" || h.why === "repository").map(
|
|
16249
16501
|
(h) => h.why === "repository" ? `${toolName}|repo:${h.repo}` : `${toolName}|${h.dest || "unresolved"}`
|
|
@@ -16257,6 +16509,11 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
16257
16509
|
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("; ") + ".";
|
|
16258
16510
|
}
|
|
16259
16511
|
} else if (held.length > 0 || writeHolds.length > 0 || logged.length > 0) {
|
|
16512
|
+
meta = {
|
|
16513
|
+
...meta,
|
|
16514
|
+
exposure: { kind: "logged", destinations: logged.length },
|
|
16515
|
+
...last?.eid ? { exposureFromEid: last.eid } : {}
|
|
16516
|
+
};
|
|
16260
16517
|
appendToLog(HOOK_DEBUG_LOG, {
|
|
16261
16518
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16262
16519
|
event: "exposure-logged",
|
|
@@ -16810,6 +17067,7 @@ ${packageReview}`;
|
|
|
16810
17067
|
// deferReview and serverKey never co-occur in production — see above.)
|
|
16811
17068
|
reason: taintWarning || packageReview || exposureReview || explainableLabel || "Node9 flagged this action for review.",
|
|
16812
17069
|
...exposureReview && exposureKeys.length > 0 ? { exposureKeys } : {},
|
|
17070
|
+
...exposureReview && exposureFromEid ? { exposureFromEid } : {},
|
|
16813
17071
|
ruleDescription: policyRuleDescription,
|
|
16814
17072
|
blockedByLabel: explainableLabel
|
|
16815
17073
|
};
|