@mrciphersmith/keryx 0.3.18 → 0.3.20
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 +701 -305
- package/dist/core.js +547 -90
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3913,6 +3913,19 @@ var init_provider_catalog_cache = __esm(() => {
|
|
|
3913
3913
|
|
|
3914
3914
|
// src/lib/oauth/open-url.ts
|
|
3915
3915
|
import { spawn } from "child_process";
|
|
3916
|
+
function isLoopbackHost(hostname) {
|
|
3917
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
3918
|
+
}
|
|
3919
|
+
function isSafeVerificationUrl(url) {
|
|
3920
|
+
let parsed;
|
|
3921
|
+
try {
|
|
3922
|
+
parsed = new URL(url);
|
|
3923
|
+
} catch {
|
|
3924
|
+
return false;
|
|
3925
|
+
}
|
|
3926
|
+
const schemeOk = parsed.protocol === "https:" || parsed.protocol === "http:" && isLoopbackHost(parsed.hostname);
|
|
3927
|
+
return schemeOk && ALLOWED_URL_CHARS.test(url);
|
|
3928
|
+
}
|
|
3916
3929
|
function browserOpenPlan(url, platform = process.platform, env = process.env) {
|
|
3917
3930
|
if (url.length === 0) {
|
|
3918
3931
|
return;
|
|
@@ -3921,7 +3934,10 @@ function browserOpenPlan(url, platform = process.platform, env = process.env) {
|
|
|
3921
3934
|
return { cmd: "open", args: [url] };
|
|
3922
3935
|
}
|
|
3923
3936
|
if (platform === "win32") {
|
|
3924
|
-
|
|
3937
|
+
if (!isSafeVerificationUrl(url)) {
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
|
|
3925
3941
|
}
|
|
3926
3942
|
if (typeof env.DISPLAY === "string" && env.DISPLAY.length > 0) {
|
|
3927
3943
|
return { cmd: "xdg-open", args: [url] };
|
|
@@ -3943,7 +3959,10 @@ function openVerificationUrl(url, deps = {}) {
|
|
|
3943
3959
|
child.unref();
|
|
3944
3960
|
} catch {}
|
|
3945
3961
|
}
|
|
3946
|
-
var
|
|
3962
|
+
var ALLOWED_URL_CHARS;
|
|
3963
|
+
var init_open_url = __esm(() => {
|
|
3964
|
+
ALLOWED_URL_CHARS = /^[A-Za-z0-9\-._~:/?#[\]@!$'()*+,;=%]+$/;
|
|
3965
|
+
});
|
|
3947
3966
|
|
|
3948
3967
|
// src/harness/policy/engine.ts
|
|
3949
3968
|
import path8 from "path";
|
|
@@ -4282,7 +4301,7 @@ function isPrivateEgressToken(token) {
|
|
|
4282
4301
|
function isPrivateEgressHost(host) {
|
|
4283
4302
|
return isPrivateEgressToken(host);
|
|
4284
4303
|
}
|
|
4285
|
-
function
|
|
4304
|
+
function isLoopbackHost2(host) {
|
|
4286
4305
|
if (typeof host !== "string" || host.length === 0)
|
|
4287
4306
|
return false;
|
|
4288
4307
|
const normalized = host.trim();
|
|
@@ -4415,7 +4434,7 @@ async function probeOllamaModels(deps, baseUrl) {
|
|
|
4415
4434
|
} catch {
|
|
4416
4435
|
host = baseUrl;
|
|
4417
4436
|
}
|
|
4418
|
-
if (isPrivateEgressHost(host) && !
|
|
4437
|
+
if (isPrivateEgressHost(host) && !isLoopbackHost2(host)) {
|
|
4419
4438
|
return;
|
|
4420
4439
|
}
|
|
4421
4440
|
let response;
|
|
@@ -9060,6 +9079,325 @@ var init_config = __esm(() => {
|
|
|
9060
9079
|
]);
|
|
9061
9080
|
});
|
|
9062
9081
|
|
|
9082
|
+
// src/security/detect/entropy.ts
|
|
9083
|
+
function isHexBlob(value) {
|
|
9084
|
+
return HEX_BLOB.test(value) && /[a-f]/i.test(value);
|
|
9085
|
+
}
|
|
9086
|
+
function isAllowShapedValue(value) {
|
|
9087
|
+
return FULL_GIT_SHA_RE.test(value) || UUID_RE.test(value) || INTEGRITY_RE.test(value);
|
|
9088
|
+
}
|
|
9089
|
+
function lastLineOf(before) {
|
|
9090
|
+
const newline = before.lastIndexOf(`
|
|
9091
|
+
`);
|
|
9092
|
+
return newline < 0 ? before : before.slice(newline + 1);
|
|
9093
|
+
}
|
|
9094
|
+
function isPlainCasedWord(letters) {
|
|
9095
|
+
return /^[a-z]+$/.test(letters) || /^[A-Z]+$/.test(letters) || /^[A-Z][a-z]*$/.test(letters);
|
|
9096
|
+
}
|
|
9097
|
+
function isSingleTransitionTag(segment) {
|
|
9098
|
+
const letterThenDigit = /^([A-Za-z]+)([0-9]+)$/.exec(segment);
|
|
9099
|
+
if (letterThenDigit !== null) {
|
|
9100
|
+
const letters = letterThenDigit[1];
|
|
9101
|
+
return letters.length <= TAG_MAX_LETTER_RUN && isPlainCasedWord(letters);
|
|
9102
|
+
}
|
|
9103
|
+
const digitThenLetter = /^([0-9]+)([A-Za-z]+)$/.exec(segment);
|
|
9104
|
+
if (digitThenLetter !== null) {
|
|
9105
|
+
const letters = digitThenLetter[2];
|
|
9106
|
+
return letters.length <= TAG_MAX_LETTER_RUN && isPlainCasedWord(letters);
|
|
9107
|
+
}
|
|
9108
|
+
return false;
|
|
9109
|
+
}
|
|
9110
|
+
function isSlugHexTail(segment) {
|
|
9111
|
+
return SLUG_HEX_TAIL_RE.test(segment);
|
|
9112
|
+
}
|
|
9113
|
+
function isWordSlug(value) {
|
|
9114
|
+
const segments = value.split(/[-_]/);
|
|
9115
|
+
if (segments.length < 3) {
|
|
9116
|
+
return false;
|
|
9117
|
+
}
|
|
9118
|
+
let words = 0;
|
|
9119
|
+
for (let i = 0;i < segments.length; i += 1) {
|
|
9120
|
+
const segment = segments[i];
|
|
9121
|
+
const isDigits = /^[0-9]+$/.test(segment);
|
|
9122
|
+
if (isDigits) {
|
|
9123
|
+
continue;
|
|
9124
|
+
}
|
|
9125
|
+
if (isPlainCasedWord(segment)) {
|
|
9126
|
+
if (segment.length >= 3) {
|
|
9127
|
+
words += 1;
|
|
9128
|
+
}
|
|
9129
|
+
continue;
|
|
9130
|
+
}
|
|
9131
|
+
if (isSingleTransitionTag(segment)) {
|
|
9132
|
+
continue;
|
|
9133
|
+
}
|
|
9134
|
+
if (i === segments.length - 1 && isSlugHexTail(segment)) {
|
|
9135
|
+
continue;
|
|
9136
|
+
}
|
|
9137
|
+
return false;
|
|
9138
|
+
}
|
|
9139
|
+
return words >= 1;
|
|
9140
|
+
}
|
|
9141
|
+
function shannonEntropy(value) {
|
|
9142
|
+
const counts = new Map;
|
|
9143
|
+
for (const ch of value) {
|
|
9144
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
9145
|
+
}
|
|
9146
|
+
let entropy = 0;
|
|
9147
|
+
for (const count of counts.values()) {
|
|
9148
|
+
const p = count / value.length;
|
|
9149
|
+
entropy -= p * Math.log2(p);
|
|
9150
|
+
}
|
|
9151
|
+
return entropy;
|
|
9152
|
+
}
|
|
9153
|
+
function bareShapeQualifies(head) {
|
|
9154
|
+
if (!/[0-9]/.test(head) && !/[+=]/.test(head)) {
|
|
9155
|
+
return { qualifies: false, entropy: 0 };
|
|
9156
|
+
}
|
|
9157
|
+
if (isWordSlug(head)) {
|
|
9158
|
+
return { qualifies: false, entropy: 0 };
|
|
9159
|
+
}
|
|
9160
|
+
const entropy = shannonEntropy(head);
|
|
9161
|
+
const hexBlob = isHexBlob(head);
|
|
9162
|
+
if (entropy < 3.6 && !hexBlob) {
|
|
9163
|
+
return { qualifies: false, entropy };
|
|
9164
|
+
}
|
|
9165
|
+
return { qualifies: true, entropy };
|
|
9166
|
+
}
|
|
9167
|
+
function shapeQualifiesAsSecret(value) {
|
|
9168
|
+
if (isAllowShapedValue(value)) {
|
|
9169
|
+
return { qualifies: false, entropy: 0 };
|
|
9170
|
+
}
|
|
9171
|
+
return bareShapeQualifies(value);
|
|
9172
|
+
}
|
|
9173
|
+
function looksSecretShaped(value) {
|
|
9174
|
+
return shapeQualifiesAsSecret(value).qualifies;
|
|
9175
|
+
}
|
|
9176
|
+
function buildMatch(start, end, value, entropy) {
|
|
9177
|
+
const confidence = Math.max(0.4, Math.min(0.7, 0.4 + (entropy - 3.6) * 0.21));
|
|
9178
|
+
return {
|
|
9179
|
+
category: "secret",
|
|
9180
|
+
policyId: "secrets.high-entropy",
|
|
9181
|
+
severity: "medium",
|
|
9182
|
+
confidence: Number(confidence.toFixed(2)),
|
|
9183
|
+
start,
|
|
9184
|
+
end,
|
|
9185
|
+
value,
|
|
9186
|
+
mask: "entropy",
|
|
9187
|
+
remediation: "Verify this high-entropy value is not a live credential."
|
|
9188
|
+
};
|
|
9189
|
+
}
|
|
9190
|
+
function findUrlSpans(content) {
|
|
9191
|
+
const spans = [];
|
|
9192
|
+
URL_SPAN_RE.lastIndex = 0;
|
|
9193
|
+
let m;
|
|
9194
|
+
while ((m = URL_SPAN_RE.exec(content)) !== null) {
|
|
9195
|
+
let raw = m[0];
|
|
9196
|
+
while (raw.length > 0 && URL_TRAILING_PUNCTUATION_RE.test(raw)) {
|
|
9197
|
+
raw = raw.slice(0, -1);
|
|
9198
|
+
}
|
|
9199
|
+
if (raw.length > 0) {
|
|
9200
|
+
spans.push({ start: m.index, end: m.index + raw.length, raw });
|
|
9201
|
+
}
|
|
9202
|
+
if (m.index === URL_SPAN_RE.lastIndex) {
|
|
9203
|
+
URL_SPAN_RE.lastIndex += 1;
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
return spans;
|
|
9207
|
+
}
|
|
9208
|
+
function urlComponentSpans(raw) {
|
|
9209
|
+
const spans = [];
|
|
9210
|
+
const schemeMatch = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.exec(raw);
|
|
9211
|
+
if (schemeMatch === null) {
|
|
9212
|
+
return spans;
|
|
9213
|
+
}
|
|
9214
|
+
let i = schemeMatch[0].length;
|
|
9215
|
+
while (i < raw.length && raw[i] !== "/" && raw[i] !== "?" && raw[i] !== "#") {
|
|
9216
|
+
i += 1;
|
|
9217
|
+
}
|
|
9218
|
+
let pathEnd = i;
|
|
9219
|
+
while (pathEnd < raw.length && raw[pathEnd] !== "?" && raw[pathEnd] !== "#") {
|
|
9220
|
+
pathEnd += 1;
|
|
9221
|
+
}
|
|
9222
|
+
let segStart = i;
|
|
9223
|
+
for (let j = i;j <= pathEnd; j += 1) {
|
|
9224
|
+
if (j === pathEnd || raw[j] === "/") {
|
|
9225
|
+
if (j > segStart) {
|
|
9226
|
+
spans.push({ start: segStart, end: j });
|
|
9227
|
+
}
|
|
9228
|
+
segStart = j + 1;
|
|
9229
|
+
}
|
|
9230
|
+
}
|
|
9231
|
+
let cursor = pathEnd;
|
|
9232
|
+
if (cursor < raw.length && raw[cursor] === "?") {
|
|
9233
|
+
let queryEnd = cursor + 1;
|
|
9234
|
+
while (queryEnd < raw.length && raw[queryEnd] !== "#") {
|
|
9235
|
+
queryEnd += 1;
|
|
9236
|
+
}
|
|
9237
|
+
let pairStart = cursor + 1;
|
|
9238
|
+
for (let j = cursor + 1;j <= queryEnd; j += 1) {
|
|
9239
|
+
if (j === queryEnd || raw[j] === "&") {
|
|
9240
|
+
const pair = raw.slice(pairStart, j);
|
|
9241
|
+
const eq = pair.indexOf("=");
|
|
9242
|
+
if (eq !== -1 && eq + 1 < pair.length) {
|
|
9243
|
+
spans.push({ start: pairStart + eq + 1, end: j });
|
|
9244
|
+
} else if (eq === -1 && pair.length > 0) {
|
|
9245
|
+
spans.push({ start: pairStart, end: j });
|
|
9246
|
+
}
|
|
9247
|
+
pairStart = j + 1;
|
|
9248
|
+
}
|
|
9249
|
+
}
|
|
9250
|
+
cursor = queryEnd;
|
|
9251
|
+
}
|
|
9252
|
+
if (cursor < raw.length && raw[cursor] === "#" && cursor + 1 < raw.length) {
|
|
9253
|
+
spans.push({ start: cursor + 1, end: raw.length });
|
|
9254
|
+
}
|
|
9255
|
+
return spans;
|
|
9256
|
+
}
|
|
9257
|
+
function secretShapedCandidatesIn(text) {
|
|
9258
|
+
const normalized = text.replace(/\+/g, " ");
|
|
9259
|
+
const out = [];
|
|
9260
|
+
TOKEN.lastIndex = 0;
|
|
9261
|
+
let m;
|
|
9262
|
+
while ((m = TOKEN.exec(normalized)) !== null) {
|
|
9263
|
+
const value = m[0];
|
|
9264
|
+
if (value.length < URL_COMPONENT_MIN_LENGTH) {
|
|
9265
|
+
continue;
|
|
9266
|
+
}
|
|
9267
|
+
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
9268
|
+
const { qualifies, entropy } = shapeQualifiesAsSecret(head);
|
|
9269
|
+
if (!qualifies) {
|
|
9270
|
+
continue;
|
|
9271
|
+
}
|
|
9272
|
+
const start = m.index;
|
|
9273
|
+
const end = start + value.length;
|
|
9274
|
+
out.push({ start, end, value: text.slice(start, end), entropy });
|
|
9275
|
+
}
|
|
9276
|
+
return out;
|
|
9277
|
+
}
|
|
9278
|
+
function looksSecretShapedIn(text) {
|
|
9279
|
+
return secretShapedCandidatesIn(text).length > 0;
|
|
9280
|
+
}
|
|
9281
|
+
function labelledPieceQualifies(piece) {
|
|
9282
|
+
const base = bareShapeQualifies(piece);
|
|
9283
|
+
if (base.qualifies) {
|
|
9284
|
+
return { entropy: base.entropy };
|
|
9285
|
+
}
|
|
9286
|
+
if (isAllowShapedValue(piece)) {
|
|
9287
|
+
return { entropy: base.entropy };
|
|
9288
|
+
}
|
|
9289
|
+
return null;
|
|
9290
|
+
}
|
|
9291
|
+
function firstQualifyingSlashPiece(head) {
|
|
9292
|
+
let offset = 0;
|
|
9293
|
+
for (const piece of head.split("/")) {
|
|
9294
|
+
if (piece.length > 0) {
|
|
9295
|
+
const found = labelledPieceQualifies(piece);
|
|
9296
|
+
if (found !== null) {
|
|
9297
|
+
return { offset, length: piece.length, entropy: found.entropy };
|
|
9298
|
+
}
|
|
9299
|
+
}
|
|
9300
|
+
offset += piece.length + 1;
|
|
9301
|
+
}
|
|
9302
|
+
return null;
|
|
9303
|
+
}
|
|
9304
|
+
function detectEntropy(content) {
|
|
9305
|
+
const matches = [];
|
|
9306
|
+
const urlSpans = findUrlSpans(content);
|
|
9307
|
+
for (const url of urlSpans) {
|
|
9308
|
+
for (const comp of urlComponentSpans(url.raw)) {
|
|
9309
|
+
const compText = url.raw.slice(comp.start, comp.end);
|
|
9310
|
+
for (const cand of secretShapedCandidatesIn(compText)) {
|
|
9311
|
+
const absStart = url.start + comp.start + cand.start;
|
|
9312
|
+
const absEnd = url.start + comp.start + cand.end;
|
|
9313
|
+
matches.push(buildMatch(absStart, absEnd, cand.value, cand.entropy));
|
|
9314
|
+
}
|
|
9315
|
+
}
|
|
9316
|
+
}
|
|
9317
|
+
LABEL_ASSIGNMENT_RE.lastIndex = 0;
|
|
9318
|
+
let am;
|
|
9319
|
+
while ((am = LABEL_ASSIGNMENT_RE.exec(content)) !== null) {
|
|
9320
|
+
const value = am[1];
|
|
9321
|
+
const end = am.index + am[0].length;
|
|
9322
|
+
const start = end - value.length;
|
|
9323
|
+
if (urlSpans.some((u) => start < u.end && end > u.start)) {
|
|
9324
|
+
continue;
|
|
9325
|
+
}
|
|
9326
|
+
const { qualifies, entropy } = bareShapeQualifies(value);
|
|
9327
|
+
if (qualifies) {
|
|
9328
|
+
matches.push(buildMatch(start, end, value, entropy));
|
|
9329
|
+
}
|
|
9330
|
+
}
|
|
9331
|
+
TOKEN.lastIndex = 0;
|
|
9332
|
+
let m;
|
|
9333
|
+
while ((m = TOKEN.exec(content)) !== null) {
|
|
9334
|
+
const value = m[0];
|
|
9335
|
+
const start = m.index;
|
|
9336
|
+
const end = start + value.length;
|
|
9337
|
+
if (urlSpans.some((u) => start < u.end && end > u.start)) {
|
|
9338
|
+
continue;
|
|
9339
|
+
}
|
|
9340
|
+
if (matches.some((existing) => start < existing.end && end > existing.start)) {
|
|
9341
|
+
continue;
|
|
9342
|
+
}
|
|
9343
|
+
const before = lastLineOf(content.slice(Math.max(0, start - LABEL_WINDOW), start));
|
|
9344
|
+
if (!ADJACENT_LABEL.test(before)) {
|
|
9345
|
+
continue;
|
|
9346
|
+
}
|
|
9347
|
+
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
9348
|
+
if (head.includes("/")) {
|
|
9349
|
+
const piece = firstQualifyingSlashPiece(head);
|
|
9350
|
+
if (piece === null) {
|
|
9351
|
+
continue;
|
|
9352
|
+
}
|
|
9353
|
+
const pieceStart = start + piece.offset;
|
|
9354
|
+
const pieceEnd = pieceStart + piece.length;
|
|
9355
|
+
matches.push(buildMatch(pieceStart, pieceEnd, content.slice(pieceStart, pieceEnd), piece.entropy));
|
|
9356
|
+
continue;
|
|
9357
|
+
}
|
|
9358
|
+
const found = labelledPieceQualifies(head);
|
|
9359
|
+
if (found === null) {
|
|
9360
|
+
continue;
|
|
9361
|
+
}
|
|
9362
|
+
matches.push(buildMatch(start, end, value, found.entropy));
|
|
9363
|
+
}
|
|
9364
|
+
return matches.sort((a, b) => a.start - b.start);
|
|
9365
|
+
}
|
|
9366
|
+
var SENSITIVE_LABEL_WORDS = "key|secret|token|password|passwd|api|credential|auth|bearer", caseless = (words) => words.replace(/[a-z]/g, (ch) => `[${ch}${ch.toUpperCase()}]`), ADJACENT_LABEL, LABEL_ASSIGNMENT_RE, TOKEN, TOKEN_HEAD, LABEL_WINDOW = 40, HEX_BLOB, FULL_GIT_SHA_RE, UUID_RE, INTEGRITY_RE, TAG_MAX_LETTER_RUN = 8, SLUG_HEX_TAIL_RE, URL_SPAN_RE, URL_TRAILING_PUNCTUATION_RE, URL_COMPONENT_MIN_LENGTH = 20;
|
|
9367
|
+
var init_entropy = __esm(() => {
|
|
9368
|
+
ADJACENT_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*["']?\\s*[:=]?\\s*["']?$`);
|
|
9369
|
+
LABEL_ASSIGNMENT_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${SENSITIVE_LABEL_WORDS})[A-Za-z0-9_-]*"?\\s*=\\s*"?([A-Za-z0-9+/_=-]{6,})`, "gi");
|
|
9370
|
+
TOKEN = /[A-Za-z0-9+/=_-]{20,}(?:\.[A-Za-z0-9+/=_-]{6,})*/g;
|
|
9371
|
+
TOKEN_HEAD = /^[A-Za-z0-9+/=_-]+/;
|
|
9372
|
+
HEX_BLOB = /^[0-9a-f]{24,}$/i;
|
|
9373
|
+
FULL_GIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
9374
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9375
|
+
INTEGRITY_RE = /^(?:sha256|sha512)-[A-Za-z0-9+/]+=*$/;
|
|
9376
|
+
SLUG_HEX_TAIL_RE = /^[0-9a-f]{10,16}$/i;
|
|
9377
|
+
URL_SPAN_RE = /\bhttps?:\/\/[^\s<>"'\\]+/g;
|
|
9378
|
+
URL_TRAILING_PUNCTUATION_RE = /[.,;:!?)\]}]$/;
|
|
9379
|
+
});
|
|
9380
|
+
|
|
9381
|
+
// src/security/entropy-gate.ts
|
|
9382
|
+
function ensureLoading() {
|
|
9383
|
+
if (loadStarted) {
|
|
9384
|
+
return;
|
|
9385
|
+
}
|
|
9386
|
+
loadStarted = true;
|
|
9387
|
+
loadSecurityConfig(process.cwd()).then((config) => {
|
|
9388
|
+
cachedEnabled = config.backends.entropy.enabled;
|
|
9389
|
+
}).catch(() => {});
|
|
9390
|
+
}
|
|
9391
|
+
function isEntropyBackendEnabled() {
|
|
9392
|
+
ensureLoading();
|
|
9393
|
+
return cachedEnabled;
|
|
9394
|
+
}
|
|
9395
|
+
var cachedEnabled, loadStarted = false;
|
|
9396
|
+
var init_entropy_gate = __esm(() => {
|
|
9397
|
+
init_config();
|
|
9398
|
+
cachedEnabled = DEFAULT_SECURITY_CONFIG.backends.entropy.enabled;
|
|
9399
|
+
});
|
|
9400
|
+
|
|
9063
9401
|
// src/security/redact.ts
|
|
9064
9402
|
import path14 from "path";
|
|
9065
9403
|
import { createHmac, randomBytes } from "crypto";
|
|
@@ -9129,7 +9467,12 @@ function redactSensitiveText(text) {
|
|
|
9129
9467
|
if (text.length === 0) {
|
|
9130
9468
|
return text;
|
|
9131
9469
|
}
|
|
9132
|
-
const matches = [
|
|
9470
|
+
const matches = [
|
|
9471
|
+
...detectSecrets(text),
|
|
9472
|
+
...detectPii(text),
|
|
9473
|
+
...detectExfil(text),
|
|
9474
|
+
...isEntropyBackendEnabled() ? detectEntropy(text) : []
|
|
9475
|
+
];
|
|
9133
9476
|
if (matches.length === 0) {
|
|
9134
9477
|
return text;
|
|
9135
9478
|
}
|
|
@@ -9166,6 +9509,8 @@ var init_redact = __esm(() => {
|
|
|
9166
9509
|
init_secrets();
|
|
9167
9510
|
init_pii();
|
|
9168
9511
|
init_exfil();
|
|
9512
|
+
init_entropy();
|
|
9513
|
+
init_entropy_gate();
|
|
9169
9514
|
});
|
|
9170
9515
|
|
|
9171
9516
|
// src/security/output-validation.ts
|
|
@@ -9909,102 +10254,33 @@ var init_seam = __esm(() => {
|
|
|
9909
10254
|
init_warn_once();
|
|
9910
10255
|
});
|
|
9911
10256
|
|
|
9912
|
-
// src/security/detect/
|
|
9913
|
-
function
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
function isWordSlug(value) {
|
|
9919
|
-
const segments = value.split(/[-_]/);
|
|
9920
|
-
if (segments.length < 3) {
|
|
9921
|
-
return false;
|
|
9922
|
-
}
|
|
9923
|
-
let words = 0;
|
|
9924
|
-
for (const segment of segments) {
|
|
9925
|
-
const isAlpha = /^[A-Za-z]+$/.test(segment);
|
|
9926
|
-
const isDigits = /^[0-9]+$/.test(segment);
|
|
9927
|
-
if (!isAlpha && !isDigits) {
|
|
9928
|
-
return false;
|
|
9929
|
-
}
|
|
9930
|
-
if (isAlpha && segment.length >= 3) {
|
|
9931
|
-
words += 1;
|
|
9932
|
-
}
|
|
9933
|
-
}
|
|
9934
|
-
return words >= 2;
|
|
9935
|
-
}
|
|
9936
|
-
function shannonEntropy(value) {
|
|
9937
|
-
const counts = new Map;
|
|
9938
|
-
for (const ch of value) {
|
|
9939
|
-
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
9940
|
-
}
|
|
9941
|
-
let entropy = 0;
|
|
9942
|
-
for (const count of counts.values()) {
|
|
9943
|
-
const p = count / value.length;
|
|
9944
|
-
entropy -= p * Math.log2(p);
|
|
9945
|
-
}
|
|
9946
|
-
return entropy;
|
|
9947
|
-
}
|
|
9948
|
-
function detectEntropy(content) {
|
|
9949
|
-
const matches = [];
|
|
9950
|
-
TOKEN.lastIndex = 0;
|
|
9951
|
-
let m;
|
|
9952
|
-
while ((m = TOKEN.exec(content)) !== null) {
|
|
9953
|
-
const value = m[0];
|
|
9954
|
-
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
9955
|
-
if (!/[0-9]/.test(head) && !/[+=]/.test(head)) {
|
|
9956
|
-
continue;
|
|
9957
|
-
}
|
|
9958
|
-
if (isWordSlug(head)) {
|
|
9959
|
-
continue;
|
|
9960
|
-
}
|
|
9961
|
-
const entropy = shannonEntropy(head);
|
|
9962
|
-
const hexBlob = HEX_BLOB.test(head);
|
|
9963
|
-
if (entropy < 3.6 && !hexBlob) {
|
|
9964
|
-
continue;
|
|
9965
|
-
}
|
|
9966
|
-
const before = lastLineOf(content.slice(Math.max(0, m.index - LABEL_WINDOW), m.index));
|
|
9967
|
-
if (!SENSITIVE_LABEL.test(before)) {
|
|
9968
|
-
continue;
|
|
9969
|
-
}
|
|
9970
|
-
const confidence = Math.max(0.4, Math.min(0.7, 0.4 + (entropy - 3.6) * 0.21));
|
|
9971
|
-
matches.push({
|
|
9972
|
-
category: "secret",
|
|
9973
|
-
policyId: "secrets.high-entropy",
|
|
9974
|
-
severity: "medium",
|
|
9975
|
-
confidence: Number(confidence.toFixed(2)),
|
|
9976
|
-
start: m.index,
|
|
9977
|
-
end: m.index + value.length,
|
|
9978
|
-
value,
|
|
9979
|
-
mask: "secret",
|
|
9980
|
-
remediation: "Verify this high-entropy value is not a live credential."
|
|
9981
|
-
});
|
|
10257
|
+
// src/security/detect/injection.ts
|
|
10258
|
+
function foldForMatching(content) {
|
|
10259
|
+
let folded = "";
|
|
10260
|
+
for (let i = 0;i < content.length; i += 1) {
|
|
10261
|
+
const ch = content[i];
|
|
10262
|
+
folded += CONFUSABLES[ch] ?? ch;
|
|
9982
10263
|
}
|
|
9983
|
-
|
|
10264
|
+
const nfkc = folded.normalize("NFKC");
|
|
10265
|
+
return nfkc.length === folded.length ? nfkc : folded;
|
|
9984
10266
|
}
|
|
9985
|
-
var SENSITIVE_LABEL, TOKEN, TOKEN_HEAD, LABEL_WINDOW = 40, HEX_BLOB;
|
|
9986
|
-
var init_entropy = __esm(() => {
|
|
9987
|
-
SENSITIVE_LABEL = /(key|secret|token|password|passwd|api|credential|auth)/i;
|
|
9988
|
-
TOKEN = /[A-Za-z0-9+/=_-]{20,}(?:\.[A-Za-z0-9+/=_-]{6,})*/g;
|
|
9989
|
-
TOKEN_HEAD = /^[A-Za-z0-9+/=_-]+/;
|
|
9990
|
-
HEX_BLOB = /^[0-9a-f]{24,}$/i;
|
|
9991
|
-
});
|
|
9992
|
-
|
|
9993
|
-
// src/security/detect/injection.ts
|
|
9994
10267
|
function detectInjection(content) {
|
|
9995
10268
|
const matches = [];
|
|
10269
|
+
const folded = foldForMatching(content);
|
|
9996
10270
|
for (const pattern of PATTERNS) {
|
|
9997
10271
|
pattern.regex.lastIndex = 0;
|
|
9998
10272
|
let m;
|
|
9999
|
-
while ((m = pattern.regex.exec(
|
|
10273
|
+
while ((m = pattern.regex.exec(folded)) !== null) {
|
|
10274
|
+
const start = Math.min(m.index, content.length);
|
|
10275
|
+
const end = Math.min(m.index + m[0].length, content.length);
|
|
10000
10276
|
matches.push({
|
|
10001
10277
|
category: "prompt-injection",
|
|
10002
10278
|
policyId: pattern.policyId,
|
|
10003
10279
|
severity: "low",
|
|
10004
10280
|
confidence: pattern.confidence,
|
|
10005
|
-
start
|
|
10006
|
-
end
|
|
10007
|
-
value:
|
|
10281
|
+
start,
|
|
10282
|
+
end,
|
|
10283
|
+
value: content.slice(start, end),
|
|
10008
10284
|
remediation: "Treat external content as data, not instruction; require human review."
|
|
10009
10285
|
});
|
|
10010
10286
|
if (m.index === pattern.regex.lastIndex) {
|
|
@@ -10014,27 +10290,69 @@ function detectInjection(content) {
|
|
|
10014
10290
|
}
|
|
10015
10291
|
return matches;
|
|
10016
10292
|
}
|
|
10017
|
-
var PATTERNS;
|
|
10293
|
+
var CONFUSABLES, PATTERNS;
|
|
10018
10294
|
var init_injection = __esm(() => {
|
|
10295
|
+
CONFUSABLES = {
|
|
10296
|
+
"\u0430": "a",
|
|
10297
|
+
"\u0410": "A",
|
|
10298
|
+
"\u0435": "e",
|
|
10299
|
+
"\u0415": "E",
|
|
10300
|
+
"\u043E": "o",
|
|
10301
|
+
"\u041E": "O",
|
|
10302
|
+
"\u0440": "p",
|
|
10303
|
+
"\u0420": "P",
|
|
10304
|
+
"\u0441": "c",
|
|
10305
|
+
"\u0421": "C",
|
|
10306
|
+
"\u0445": "x",
|
|
10307
|
+
"\u0425": "X",
|
|
10308
|
+
"\u0443": "y",
|
|
10309
|
+
"\u0423": "Y",
|
|
10310
|
+
"\u0455": "s",
|
|
10311
|
+
"\u0405": "S",
|
|
10312
|
+
"\u0456": "o",
|
|
10313
|
+
"\u0406": "O",
|
|
10314
|
+
"\u0458": "j",
|
|
10315
|
+
"\u0408": "J",
|
|
10316
|
+
"\u03B1": "a",
|
|
10317
|
+
"\u0391": "A",
|
|
10318
|
+
"\u03B2": "b",
|
|
10319
|
+
"\u0392": "B",
|
|
10320
|
+
"\u03B5": "e",
|
|
10321
|
+
"\u0395": "E",
|
|
10322
|
+
"\u03B9": "i",
|
|
10323
|
+
"\u0399": "I",
|
|
10324
|
+
"\u03BA": "k",
|
|
10325
|
+
"\u039A": "K",
|
|
10326
|
+
"\u03BF": "o",
|
|
10327
|
+
"\u039F": "O",
|
|
10328
|
+
"\u03C1": "p",
|
|
10329
|
+
"\u03A1": "P",
|
|
10330
|
+
"\u03C4": "t",
|
|
10331
|
+
"\u03A4": "T",
|
|
10332
|
+
"\u03C5": "u",
|
|
10333
|
+
"\u03A5": "Y",
|
|
10334
|
+
"\u03C7": "x",
|
|
10335
|
+
"\u03A7": "X"
|
|
10336
|
+
};
|
|
10019
10337
|
PATTERNS = [
|
|
10020
10338
|
{
|
|
10021
10339
|
policyId: "prompt-injection.ignore-instructions",
|
|
10022
|
-
regex: /\b(ignore|disregard|forget|override)\b[
|
|
10340
|
+
regex: /\b(ignore|disregard|forget|override)\b[^.]{0,40}\b(all\s+)?(previous|prior|above|earlier|system|developer)\b[^.]{0,20}\b(instructions?|prompts?|rules?|context)\b/gi,
|
|
10023
10341
|
confidence: 0.4
|
|
10024
10342
|
},
|
|
10025
10343
|
{
|
|
10026
10344
|
policyId: "prompt-injection.reveal-context",
|
|
10027
|
-
regex: /\b(reveal|show|print|expose|leak|dump|repeat)\b[
|
|
10345
|
+
regex: /\b(reveal|show|print|expose|leak|dump|repeat)\b[^.]{0,30}\b(your\s+)?(system\s+prompt|instructions?|memory|secrets?|hidden\s+context|api\s*keys?)\b/gi,
|
|
10028
10346
|
confidence: 0.45
|
|
10029
10347
|
},
|
|
10030
10348
|
{
|
|
10031
10349
|
policyId: "prompt-injection.role-override",
|
|
10032
|
-
regex: /\b(you\s+are\s+now|from\s+now\s+on|act\s+as|pretend\s+to\s+be)\b[
|
|
10350
|
+
regex: /\b(you\s+are\s+now|from\s+now\s+on|act\s+as|pretend\s+to\s+be)\b[^.]{0,40}\b(unrestricted|jailbroken|dan|admin|developer\s+mode|no\s+rules)\b/gi,
|
|
10033
10351
|
confidence: 0.4
|
|
10034
10352
|
},
|
|
10035
10353
|
{
|
|
10036
10354
|
policyId: "prompt-injection.priority-override",
|
|
10037
|
-
regex: /\b(treat|follow|obey)\b[
|
|
10355
|
+
regex: /\b(treat|follow|obey)\b[^.]{0,30}\b(the\s+)?(following|external|below)\b[^.]{0,20}\b(as\s+)?(instructions?|higher\s+priority|commands?)\b/gi,
|
|
10038
10356
|
confidence: 0.35
|
|
10039
10357
|
}
|
|
10040
10358
|
];
|
|
@@ -11204,6 +11522,140 @@ var init_read_source = __esm(() => {
|
|
|
11204
11522
|
init_contained_path();
|
|
11205
11523
|
});
|
|
11206
11524
|
|
|
11525
|
+
// src/security/credential-shape.ts
|
|
11526
|
+
function isDeniedForMcpChild(name, value) {
|
|
11527
|
+
if (value !== undefined && CREDENTIAL_IN_VALUE_RE.test(value))
|
|
11528
|
+
return true;
|
|
11529
|
+
const upper = name.toUpperCase();
|
|
11530
|
+
if (MCP_ENV_ALLOW_EXACT.includes(upper))
|
|
11531
|
+
return false;
|
|
11532
|
+
const conditional = VALUE_DEPENDENT[upper];
|
|
11533
|
+
if (conditional !== undefined) {
|
|
11534
|
+
return value === undefined ? true : conditional(value);
|
|
11535
|
+
}
|
|
11536
|
+
if (EXTERNAL_ENV_DENY.some((denied) => denied.toUpperCase() === upper))
|
|
11537
|
+
return true;
|
|
11538
|
+
if (MCP_ENV_DENY.includes(upper))
|
|
11539
|
+
return true;
|
|
11540
|
+
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix.toUpperCase())))
|
|
11541
|
+
return true;
|
|
11542
|
+
if (MCP_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix)))
|
|
11543
|
+
return true;
|
|
11544
|
+
return SECRET_SEGMENT_RE.test(upper) || SECRET_SUBSTRING_RE.test(upper) || GLUED_SECRET_RE.test(upper) || CONNECTION_STRING_RE.test(upper);
|
|
11545
|
+
}
|
|
11546
|
+
var EXTERNAL_ENV_DENY, EXTERNAL_ENV_PREFIX_SWEEPS, MCP_ENV_ALLOW_EXACT, MCP_ENV_DENY, MCP_ENV_PREFIX_SWEEPS, SECRET_SEGMENTS, SECRET_SEGMENT_RE, SECRET_SUBSTRING_RE, GLUED_SECRET_RE, CONNECTION_STRING_RE, CREDENTIAL_IN_VALUE_RE, VALUE_DEPENDENT;
|
|
11547
|
+
var init_credential_shape = __esm(() => {
|
|
11548
|
+
EXTERNAL_ENV_DENY = [
|
|
11549
|
+
"ANTHROPIC_API_KEY",
|
|
11550
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
11551
|
+
"ANTHROPIC_BASE_URL",
|
|
11552
|
+
"ANTHROPIC_MODEL",
|
|
11553
|
+
"CLAUDE_CONFIG_DIR",
|
|
11554
|
+
"CODEX_HOME",
|
|
11555
|
+
"CLAUDECODE"
|
|
11556
|
+
];
|
|
11557
|
+
EXTERNAL_ENV_PREFIX_SWEEPS = ["CLAUDE_CODE_", "KERYX_"];
|
|
11558
|
+
MCP_ENV_ALLOW_EXACT = ["PWD", "OLDPWD"];
|
|
11559
|
+
MCP_ENV_DENY = [
|
|
11560
|
+
"SSH_AUTH_SOCK",
|
|
11561
|
+
"SSH_AGENT_PID",
|
|
11562
|
+
"GPG_AGENT_INFO",
|
|
11563
|
+
"GNUPGHOME",
|
|
11564
|
+
"GNOME_KEYRING_CONTROL",
|
|
11565
|
+
"GNOME_KEYRING_PID",
|
|
11566
|
+
"KDE_FULL_SESSION",
|
|
11567
|
+
"GIT_ASKPASS",
|
|
11568
|
+
"SSH_ASKPASS",
|
|
11569
|
+
"SUDO_ASKPASS",
|
|
11570
|
+
"GIT_CREDENTIAL_HELPER",
|
|
11571
|
+
"GIT_SSH_COMMAND",
|
|
11572
|
+
"KUBECONFIG",
|
|
11573
|
+
"NETRC",
|
|
11574
|
+
"PGPASSFILE",
|
|
11575
|
+
"PGSERVICEFILE",
|
|
11576
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
11577
|
+
"CLOUDSDK_CONFIG",
|
|
11578
|
+
"DOCKER_CONFIG",
|
|
11579
|
+
"AWS_SHARED_CREDENTIALS_FILE",
|
|
11580
|
+
"AWS_CONFIG_FILE",
|
|
11581
|
+
"BOTO_CONFIG",
|
|
11582
|
+
"AZURE_CONFIG_DIR",
|
|
11583
|
+
"CURLRC",
|
|
11584
|
+
"WGETRC",
|
|
11585
|
+
"NPMRC",
|
|
11586
|
+
"NPM_CONFIG_USERCONFIG",
|
|
11587
|
+
"PIP_CONFIG_FILE",
|
|
11588
|
+
"RCLONE_CONFIG",
|
|
11589
|
+
"MAVEN_SETTINGS",
|
|
11590
|
+
"GIT_CONFIG",
|
|
11591
|
+
"GIT_CONFIG_GLOBAL",
|
|
11592
|
+
"HGRCPATH",
|
|
11593
|
+
"ANSIBLE_VAULT_PASSWORD_FILE",
|
|
11594
|
+
"AZURE_CLIENT_CERTIFICATE_PATH",
|
|
11595
|
+
"ARM_CLIENT_CERTIFICATE_PATH",
|
|
11596
|
+
"VAULT_CLIENT_CERT",
|
|
11597
|
+
"VAULT_CLIENT_KEY",
|
|
11598
|
+
"SSL_CLIENT_CERT_PATH",
|
|
11599
|
+
"SSL_CLIENT_KEY_FILE",
|
|
11600
|
+
"IDENTITY_FILE",
|
|
11601
|
+
"SSH_IDENTITY_FILE",
|
|
11602
|
+
"GCP_SERVICE_ACCOUNT",
|
|
11603
|
+
"XAUTHORITY",
|
|
11604
|
+
"OPENAI_API_KEY",
|
|
11605
|
+
"GEMINI_API_KEY",
|
|
11606
|
+
"GOOGLE_API_KEY",
|
|
11607
|
+
"GROQ_API_KEY",
|
|
11608
|
+
"MISTRAL_API_KEY",
|
|
11609
|
+
"XAI_API_KEY",
|
|
11610
|
+
"DEEPSEEK_API_KEY",
|
|
11611
|
+
"OPENROUTER_API_KEY",
|
|
11612
|
+
"TOGETHER_API_KEY",
|
|
11613
|
+
"PERPLEXITY_API_KEY"
|
|
11614
|
+
];
|
|
11615
|
+
MCP_ENV_PREFIX_SWEEPS = [
|
|
11616
|
+
"OP_SESSION",
|
|
11617
|
+
"BW_SESSION"
|
|
11618
|
+
];
|
|
11619
|
+
SECRET_SEGMENTS = [
|
|
11620
|
+
"KEY",
|
|
11621
|
+
"KEYS",
|
|
11622
|
+
"TOKEN",
|
|
11623
|
+
"TOKENS",
|
|
11624
|
+
"SECRET",
|
|
11625
|
+
"SECRETS",
|
|
11626
|
+
"PASSWORD",
|
|
11627
|
+
"PASSWD",
|
|
11628
|
+
"PWD",
|
|
11629
|
+
"PASS",
|
|
11630
|
+
"PASSPHRASE",
|
|
11631
|
+
"PASSCODE",
|
|
11632
|
+
"PASSKEY",
|
|
11633
|
+
"CREDENTIAL",
|
|
11634
|
+
"CREDENTIALS",
|
|
11635
|
+
"CREDS",
|
|
11636
|
+
"AUTH",
|
|
11637
|
+
"AUTHORIZATION",
|
|
11638
|
+
"BEARER",
|
|
11639
|
+
"JWT",
|
|
11640
|
+
"COOKIE",
|
|
11641
|
+
"SIGNATURE",
|
|
11642
|
+
"APIKEY",
|
|
11643
|
+
"SECRETKEY",
|
|
11644
|
+
"ACCESSKEY",
|
|
11645
|
+
"AUTHTOKEN",
|
|
11646
|
+
"PAT",
|
|
11647
|
+
"WEBHOOK"
|
|
11648
|
+
];
|
|
11649
|
+
SECRET_SEGMENT_RE = new RegExp(`(^|_)(${SECRET_SEGMENTS.join("|")})($|_)`);
|
|
11650
|
+
SECRET_SUBSTRING_RE = /(PASSWORD|PASSWD|PASSPHRASE|PASSCODE|SSHPASS|SECRET|CREDENTIAL|APIKEY|KEYFILE|PRIVKEY)/;
|
|
11651
|
+
GLUED_SECRET_RE = /(PRIVATE|SECRET|ACCESS|REFRESH|SESSION|CLIENT|API|APP)(KEY|TOKEN|SECRET)($|_)|DB(PASS|PWD)($|_)/;
|
|
11652
|
+
CONNECTION_STRING_RE = /(^|_)(DSN|CONNECTION_STRING|CONNECTIONSTRING)($|_)/;
|
|
11653
|
+
CREDENTIAL_IN_VALUE_RE = /:\/\/[^/\s@]*(:[^/\s@]*)?@/;
|
|
11654
|
+
VALUE_DEPENDENT = {
|
|
11655
|
+
DOCKER_HOST: (value) => !value.startsWith("unix://") && !value.startsWith("fd://")
|
|
11656
|
+
};
|
|
11657
|
+
});
|
|
11658
|
+
|
|
11207
11659
|
// src/security/audit-harness/codex-toml.ts
|
|
11208
11660
|
function stripComment(line) {
|
|
11209
11661
|
let quoted = false;
|
|
@@ -17641,7 +18093,7 @@ var package_default;
|
|
|
17641
18093
|
var init_package = __esm(() => {
|
|
17642
18094
|
package_default = {
|
|
17643
18095
|
name: "@mrciphersmith/keryx",
|
|
17644
|
-
version: "0.3.
|
|
18096
|
+
version: "0.3.20",
|
|
17645
18097
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
17646
18098
|
private: false,
|
|
17647
18099
|
publishConfig: {
|
|
@@ -28787,6 +29239,11 @@ var init_service4 = __esm(() => {
|
|
|
28787
29239
|
init_path_scan();
|
|
28788
29240
|
init_read_source();
|
|
28789
29241
|
init_redact();
|
|
29242
|
+
init_secrets();
|
|
29243
|
+
init_entropy();
|
|
29244
|
+
init_incidents();
|
|
29245
|
+
init_entropy_gate();
|
|
29246
|
+
init_credential_shape();
|
|
28790
29247
|
init_audit_harness();
|
|
28791
29248
|
init_config();
|
|
28792
29249
|
init_impact_evidence();
|
|
@@ -29144,7 +29601,7 @@ class OpenAiCompatEngine {
|
|
|
29144
29601
|
} catch {
|
|
29145
29602
|
host = baseUrl;
|
|
29146
29603
|
}
|
|
29147
|
-
const permitted = !isPrivateEgressHost(host) || grant.allowLoopback === true &&
|
|
29604
|
+
const permitted = !isPrivateEgressHost(host) || grant.allowLoopback === true && isLoopbackHost2(host) || grant.allowPrivateLan === true && isPrivateLanHost(host);
|
|
29148
29605
|
if (!permitted) {
|
|
29149
29606
|
yield errorEvent({
|
|
29150
29607
|
kind: "invalid_request",
|
|
@@ -58893,53 +59350,18 @@ ${shown.join(`
|
|
|
58893
59350
|
}
|
|
58894
59351
|
const risk = toolByName.get(call.name)?.definition.risk;
|
|
58895
59352
|
const isPureReadTool = risk === "read" && !DURABLE_READ_TOOL_NAMES.has(call.name);
|
|
58896
|
-
|
|
58897
|
-
|
|
58898
|
-
|
|
58899
|
-
|
|
58900
|
-
|
|
58901
|
-
|
|
58902
|
-
|
|
58903
|
-
|
|
58904
|
-
|
|
58905
|
-
|
|
58906
|
-
|
|
58907
|
-
|
|
58908
|
-
io.onHistoryChange?.("tool");
|
|
58909
|
-
gateBlockedAny = true;
|
|
58910
|
-
continue;
|
|
58911
|
-
}
|
|
58912
|
-
const taintFingerprint = toolCallHash(call.name, call.input);
|
|
58913
|
-
let taintApproved;
|
|
58914
|
-
try {
|
|
58915
|
-
const response = await approver(call.name, call.input, {
|
|
58916
|
-
fingerprint: taintFingerprint,
|
|
58917
|
-
destructive: risk === "destructive",
|
|
58918
|
-
untrustedOrigin: true
|
|
58919
|
-
});
|
|
58920
|
-
taintApproved = isApprovalFor(response, taintFingerprint);
|
|
58921
|
-
} catch (err) {
|
|
58922
|
-
const result = {
|
|
58923
|
-
output: `${call.name} not executed: the approval prompt failed (${err instanceof Error ? err.message : String(err)})`,
|
|
58924
|
-
isError: true
|
|
58925
|
-
};
|
|
58926
|
-
io.onToolResult?.(call.name, result);
|
|
58927
|
-
history.push({ role: "tool", content: withBudgetWarning(result.output), provenance: "tool", toolCallId: call.id, ts: now() });
|
|
58928
|
-
io.onHistoryChange?.("tool");
|
|
58929
|
-
gateBlockedAny = true;
|
|
58930
|
-
continue;
|
|
58931
|
-
}
|
|
58932
|
-
if (!taintApproved) {
|
|
58933
|
-
const result = {
|
|
58934
|
-
output: "tool blocked: external web content cannot authorize this call, and the user did not authorize it either",
|
|
58935
|
-
isError: true
|
|
58936
|
-
};
|
|
58937
|
-
io.onToolResult?.(call.name, result);
|
|
58938
|
-
history.push({ role: "tool", content: withBudgetWarning(result.output), provenance: "tool", toolCallId: call.id, ts: now() });
|
|
58939
|
-
io.onHistoryChange?.("tool");
|
|
58940
|
-
gateBlockedAny = true;
|
|
58941
|
-
continue;
|
|
58942
|
-
}
|
|
59353
|
+
const untrustedOrigin = !isPureReadTool && untrustedContentSeen;
|
|
59354
|
+
if (untrustedOrigin && deps.unattended === true) {
|
|
59355
|
+
const result = {
|
|
59356
|
+
output: "tool blocked: external web content cannot authorize further tool calls in this turn",
|
|
59357
|
+
isError: true
|
|
59358
|
+
};
|
|
59359
|
+
io.onUnattendedDenial?.(call.name, "untrusted external content in this turn cannot authorize the call");
|
|
59360
|
+
io.onToolResult?.(call.name, result);
|
|
59361
|
+
history.push({ role: "tool", content: withBudgetWarning(result.output), provenance: "tool", toolCallId: call.id, ts: now() });
|
|
59362
|
+
io.onHistoryChange?.("tool");
|
|
59363
|
+
gateBlockedAny = true;
|
|
59364
|
+
continue;
|
|
58943
59365
|
}
|
|
58944
59366
|
io.onToolCall?.(call.name, call.input);
|
|
58945
59367
|
const reservation = reservationByCallId.get(call.id) ?? reserveToolAttempt(budget, call.name, call.input);
|
|
@@ -58961,7 +59383,7 @@ ${shown.join(`
|
|
|
58961
59383
|
result = precomputedResult;
|
|
58962
59384
|
} else {
|
|
58963
59385
|
try {
|
|
58964
|
-
result = await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.readOnly, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal, deps.busLeases, deps.hardDeny === undefined ? undefined : { check: deps.hardDeny, onDenied: io.onUnattendedDenial }, deps.hooks);
|
|
59386
|
+
result = await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.readOnly, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal, deps.busLeases, deps.hardDeny === undefined ? undefined : { check: deps.hardDeny, onDenied: io.onUnattendedDenial }, deps.hooks, untrustedOrigin);
|
|
58965
59387
|
} catch (err) {
|
|
58966
59388
|
const message = err instanceof Error ? err.message : String(err);
|
|
58967
59389
|
result = { output: `${call.name} failed: ${message}`, isError: true };
|
|
@@ -59531,7 +59953,7 @@ function composeWithHook(toolName, rawDecision, hookResult, interactive) {
|
|
|
59531
59953
|
}
|
|
59532
59954
|
return { decision, hookTightened: true, hookAsked, ...denyMessage !== undefined ? { denyMessage } : {} };
|
|
59533
59955
|
}
|
|
59534
|
-
async function executeCall(call, toolByName, requestApproval, permissionMode, readOnly, onAutoApproved, hasInvocationCapacity, reserveInvocation, maxToolCalls, signal, busLeases, hardDeny, hooks) {
|
|
59956
|
+
async function executeCall(call, toolByName, requestApproval, permissionMode, readOnly, onAutoApproved, hasInvocationCapacity, reserveInvocation, maxToolCalls, signal, busLeases, hardDeny, hooks, untrustedOrigin = false) {
|
|
59535
59957
|
const tool = toolByName.get(call.name);
|
|
59536
59958
|
if (tool === undefined) {
|
|
59537
59959
|
return { output: `unknown tool: ${call.name}`, isError: true };
|
|
@@ -59557,6 +59979,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59557
59979
|
}
|
|
59558
59980
|
}
|
|
59559
59981
|
const mode = permissionMode?.() ?? DEFAULT_PERMISSION_MODE;
|
|
59982
|
+
const untrustedDenial = "tool blocked: external web content cannot authorize this call, and the user did not authorize it either";
|
|
59560
59983
|
const isReadOnly = readOnly?.() ?? false;
|
|
59561
59984
|
let hookResult;
|
|
59562
59985
|
if (hooks !== undefined) {
|
|
@@ -59597,7 +60020,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59597
60020
|
isError: true
|
|
59598
60021
|
};
|
|
59599
60022
|
}
|
|
59600
|
-
if (gated.decision === "auto") {
|
|
60023
|
+
if (gated.decision === "auto" && !untrustedOrigin) {
|
|
59601
60024
|
onAutoApproved?.(call.name, call.input, { destructive, credentials });
|
|
59602
60025
|
} else {
|
|
59603
60026
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
@@ -59607,10 +60030,11 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59607
60030
|
...credentials ? { credentials } : {},
|
|
59608
60031
|
...publishLease ? { publishLease } : {},
|
|
59609
60032
|
...publishLease && publishLeaseHolder !== undefined ? { publishLeaseDetail: `held by @${publishLeaseHolder.name} \u2014 "${publishLeaseHolder.reason}"` } : {},
|
|
59610
|
-
...gated.hookAsked ? { hookAsk: true } : {}
|
|
60033
|
+
...gated.hookAsked ? { hookAsk: true } : {},
|
|
60034
|
+
...untrustedOrigin ? { untrustedOrigin: true } : {}
|
|
59611
60035
|
});
|
|
59612
60036
|
if (!isApprovalFor(response, fingerprint)) {
|
|
59613
|
-
return { output:
|
|
60037
|
+
return { output: untrustedOrigin ? untrustedDenial : "command not approved by the user; not executed", isError: true };
|
|
59614
60038
|
}
|
|
59615
60039
|
}
|
|
59616
60040
|
} else if (risk === "delegate") {
|
|
@@ -59629,17 +60053,18 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59629
60053
|
isError: true
|
|
59630
60054
|
};
|
|
59631
60055
|
}
|
|
59632
|
-
if (gated.decision === "auto") {
|
|
60056
|
+
if (gated.decision === "auto" && !untrustedOrigin) {
|
|
59633
60057
|
onAutoApproved?.(call.name, call.input, { destructive: false, credentials: false });
|
|
59634
60058
|
} else {
|
|
59635
60059
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
59636
60060
|
const response = requestApproval === undefined ? false : await requestApproval(call.name, call.input, {
|
|
59637
60061
|
fingerprint,
|
|
59638
60062
|
destructive: false,
|
|
59639
|
-
...gated.hookAsked ? { hookAsk: true } : {}
|
|
60063
|
+
...gated.hookAsked ? { hookAsk: true } : {},
|
|
60064
|
+
...untrustedOrigin ? { untrustedOrigin: true } : {}
|
|
59640
60065
|
});
|
|
59641
60066
|
if (!isApprovalFor(response, fingerprint)) {
|
|
59642
|
-
return { output:
|
|
60067
|
+
return { output: untrustedOrigin ? untrustedDenial : "subagent spawn not approved by the user; not executed", isError: true };
|
|
59643
60068
|
}
|
|
59644
60069
|
}
|
|
59645
60070
|
} else if (risk === "write") {
|
|
@@ -59670,7 +60095,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59670
60095
|
isError: true
|
|
59671
60096
|
};
|
|
59672
60097
|
}
|
|
59673
|
-
if (gated.decision === "auto") {
|
|
60098
|
+
if (gated.decision === "auto" && !untrustedOrigin) {
|
|
59674
60099
|
onAutoApproved?.(call.name, call.input, { destructive, credentials });
|
|
59675
60100
|
} else {
|
|
59676
60101
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
@@ -59679,13 +60104,14 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59679
60104
|
destructive,
|
|
59680
60105
|
...credentials ? { credentials } : {},
|
|
59681
60106
|
...card !== undefined ? { alwaysAsk: true, card } : {},
|
|
59682
|
-
...gated.hookAsked ? { hookAsk: true } : {}
|
|
60107
|
+
...gated.hookAsked ? { hookAsk: true } : {},
|
|
60108
|
+
...untrustedOrigin ? { untrustedOrigin: true } : {}
|
|
59683
60109
|
});
|
|
59684
60110
|
if (!isApprovalFor(response, fingerprint)) {
|
|
59685
60111
|
if (confirmationToken !== undefined)
|
|
59686
60112
|
tool.confirmationDeclined?.(confirmationToken);
|
|
59687
60113
|
return {
|
|
59688
|
-
output: card !== undefined ? `${call.name} not confirmed by the operator; nothing was written or installed` : `patch not approved by the user; not executed`,
|
|
60114
|
+
output: untrustedOrigin ? untrustedDenial : card !== undefined ? `${call.name} not confirmed by the operator; nothing was written or installed` : `patch not approved by the user; not executed`,
|
|
59689
60115
|
isError: true
|
|
59690
60116
|
};
|
|
59691
60117
|
}
|
|
@@ -59698,11 +60124,11 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59698
60124
|
if (gated.decision === "deny") {
|
|
59699
60125
|
return { output: gated.denyMessage ?? `tool "${call.name}" refused by a policy hook`, isError: true };
|
|
59700
60126
|
}
|
|
59701
|
-
if (gated.decision === "ask") {
|
|
60127
|
+
if (gated.decision === "ask" || untrustedOrigin) {
|
|
59702
60128
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
59703
|
-
const response = requestApproval === undefined ? false : await requestApproval(call.name, call.input, { fingerprint, destructive: false, hookAsk: true });
|
|
60129
|
+
const response = requestApproval === undefined ? false : await requestApproval(call.name, call.input, { fingerprint, destructive: false, ...gated.hookAsked ? { hookAsk: true } : {}, ...untrustedOrigin ? { untrustedOrigin: true } : {} });
|
|
59704
60130
|
if (!isApprovalFor(response, fingerprint)) {
|
|
59705
|
-
return { output: `${call.name} not approved by the user; not executed`, isError: true };
|
|
60131
|
+
return { output: untrustedOrigin ? untrustedDenial : `${call.name} not approved by the user; not executed`, isError: true };
|
|
59706
60132
|
}
|
|
59707
60133
|
}
|
|
59708
60134
|
} else {
|
|
@@ -112158,6 +112584,7 @@ function resolveFqn(catalog, fqn) {
|
|
|
112158
112584
|
}
|
|
112159
112585
|
|
|
112160
112586
|
// src/mcp-servers/http-headers.ts
|
|
112587
|
+
init_service4();
|
|
112161
112588
|
var VAR_PATTERN2 = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}/;
|
|
112162
112589
|
var VAR_PATTERN_ALL = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
112163
112590
|
function unresolvedVariables(raw, env) {
|
|
@@ -112275,6 +112702,14 @@ function elideCredentialText(raw) {
|
|
|
112275
112702
|
const trimmed = query === -1 ? raw : `${raw.slice(0, query)}?\u2026`;
|
|
112276
112703
|
return trimmed.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:)?\/\/[^/@]*@/, (_m, scheme) => `${scheme ?? ""}//\u2026@`);
|
|
112277
112704
|
}
|
|
112705
|
+
var MASKED_SEGMENT_MIN_LENGTH = 16;
|
|
112706
|
+
function maskSecretShapedPathSegments(pathname) {
|
|
112707
|
+
return pathname.split("/").map((segment) => {
|
|
112708
|
+
if (segment.length < MASKED_SEGMENT_MIN_LENGTH)
|
|
112709
|
+
return segment;
|
|
112710
|
+
return detectSecrets(segment).length > 0 || looksSecretShaped(segment) ? "\u2026" : segment;
|
|
112711
|
+
}).join("/");
|
|
112712
|
+
}
|
|
112278
112713
|
function displayUrl(raw) {
|
|
112279
112714
|
if (raw === undefined)
|
|
112280
112715
|
return "";
|
|
@@ -112284,7 +112719,8 @@ function displayUrl(raw) {
|
|
|
112284
112719
|
const parsed = new URL(raw);
|
|
112285
112720
|
const query = parsed.search === "" ? "" : "?\u2026";
|
|
112286
112721
|
const auth = parsed.username === "" ? "" : "\u2026@";
|
|
112287
|
-
|
|
112722
|
+
const pathname = maskSecretShapedPathSegments(parsed.pathname);
|
|
112723
|
+
return `${parsed.protocol}//${auth}${parsed.host}${pathname}${query}`;
|
|
112288
112724
|
} catch {
|
|
112289
112725
|
return elideCredentialText(raw);
|
|
112290
112726
|
}
|
|
@@ -113695,139 +114131,10 @@ function fileForScope(scope, targets) {
|
|
|
113695
114131
|
return projectConfigFile(targets.projectRoot);
|
|
113696
114132
|
}
|
|
113697
114133
|
|
|
113698
|
-
// src/harness/external/env-deny.ts
|
|
113699
|
-
var EXTERNAL_ENV_DENY = [
|
|
113700
|
-
"ANTHROPIC_API_KEY",
|
|
113701
|
-
"ANTHROPIC_AUTH_TOKEN",
|
|
113702
|
-
"ANTHROPIC_BASE_URL",
|
|
113703
|
-
"ANTHROPIC_MODEL",
|
|
113704
|
-
"CLAUDE_CONFIG_DIR",
|
|
113705
|
-
"CODEX_HOME",
|
|
113706
|
-
"CLAUDECODE"
|
|
113707
|
-
];
|
|
113708
|
-
var EXTERNAL_ENV_PREFIX_SWEEPS = ["CLAUDE_CODE_", "KERYX_"];
|
|
113709
|
-
|
|
113710
114134
|
// src/mcp-servers/spawn-env.ts
|
|
114135
|
+
init_service4();
|
|
113711
114136
|
init_shell_config();
|
|
113712
|
-
|
|
113713
|
-
var MCP_ENV_DENY = [
|
|
113714
|
-
"SSH_AUTH_SOCK",
|
|
113715
|
-
"SSH_AGENT_PID",
|
|
113716
|
-
"GPG_AGENT_INFO",
|
|
113717
|
-
"GNUPGHOME",
|
|
113718
|
-
"GNOME_KEYRING_CONTROL",
|
|
113719
|
-
"GNOME_KEYRING_PID",
|
|
113720
|
-
"KDE_FULL_SESSION",
|
|
113721
|
-
"GIT_ASKPASS",
|
|
113722
|
-
"SSH_ASKPASS",
|
|
113723
|
-
"SUDO_ASKPASS",
|
|
113724
|
-
"GIT_CREDENTIAL_HELPER",
|
|
113725
|
-
"GIT_SSH_COMMAND",
|
|
113726
|
-
"KUBECONFIG",
|
|
113727
|
-
"NETRC",
|
|
113728
|
-
"PGPASSFILE",
|
|
113729
|
-
"PGSERVICEFILE",
|
|
113730
|
-
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
113731
|
-
"CLOUDSDK_CONFIG",
|
|
113732
|
-
"DOCKER_CONFIG",
|
|
113733
|
-
"AWS_SHARED_CREDENTIALS_FILE",
|
|
113734
|
-
"AWS_CONFIG_FILE",
|
|
113735
|
-
"BOTO_CONFIG",
|
|
113736
|
-
"AZURE_CONFIG_DIR",
|
|
113737
|
-
"CURLRC",
|
|
113738
|
-
"WGETRC",
|
|
113739
|
-
"NPMRC",
|
|
113740
|
-
"NPM_CONFIG_USERCONFIG",
|
|
113741
|
-
"PIP_CONFIG_FILE",
|
|
113742
|
-
"RCLONE_CONFIG",
|
|
113743
|
-
"MAVEN_SETTINGS",
|
|
113744
|
-
"GIT_CONFIG",
|
|
113745
|
-
"GIT_CONFIG_GLOBAL",
|
|
113746
|
-
"HGRCPATH",
|
|
113747
|
-
"ANSIBLE_VAULT_PASSWORD_FILE",
|
|
113748
|
-
"AZURE_CLIENT_CERTIFICATE_PATH",
|
|
113749
|
-
"ARM_CLIENT_CERTIFICATE_PATH",
|
|
113750
|
-
"VAULT_CLIENT_CERT",
|
|
113751
|
-
"VAULT_CLIENT_KEY",
|
|
113752
|
-
"SSL_CLIENT_CERT_PATH",
|
|
113753
|
-
"SSL_CLIENT_KEY_FILE",
|
|
113754
|
-
"IDENTITY_FILE",
|
|
113755
|
-
"SSH_IDENTITY_FILE",
|
|
113756
|
-
"GCP_SERVICE_ACCOUNT",
|
|
113757
|
-
"XAUTHORITY",
|
|
113758
|
-
"OPENAI_API_KEY",
|
|
113759
|
-
"GEMINI_API_KEY",
|
|
113760
|
-
"GOOGLE_API_KEY",
|
|
113761
|
-
"GROQ_API_KEY",
|
|
113762
|
-
"MISTRAL_API_KEY",
|
|
113763
|
-
"XAI_API_KEY",
|
|
113764
|
-
"DEEPSEEK_API_KEY",
|
|
113765
|
-
"OPENROUTER_API_KEY",
|
|
113766
|
-
"TOGETHER_API_KEY",
|
|
113767
|
-
"PERPLEXITY_API_KEY"
|
|
113768
|
-
];
|
|
113769
|
-
var MCP_ENV_PREFIX_SWEEPS = [
|
|
113770
|
-
"OP_SESSION",
|
|
113771
|
-
"BW_SESSION"
|
|
113772
|
-
];
|
|
113773
|
-
var SECRET_SEGMENTS = [
|
|
113774
|
-
"KEY",
|
|
113775
|
-
"KEYS",
|
|
113776
|
-
"TOKEN",
|
|
113777
|
-
"TOKENS",
|
|
113778
|
-
"SECRET",
|
|
113779
|
-
"SECRETS",
|
|
113780
|
-
"PASSWORD",
|
|
113781
|
-
"PASSWD",
|
|
113782
|
-
"PWD",
|
|
113783
|
-
"PASS",
|
|
113784
|
-
"PASSPHRASE",
|
|
113785
|
-
"PASSCODE",
|
|
113786
|
-
"PASSKEY",
|
|
113787
|
-
"CREDENTIAL",
|
|
113788
|
-
"CREDENTIALS",
|
|
113789
|
-
"CREDS",
|
|
113790
|
-
"AUTH",
|
|
113791
|
-
"AUTHORIZATION",
|
|
113792
|
-
"BEARER",
|
|
113793
|
-
"JWT",
|
|
113794
|
-
"COOKIE",
|
|
113795
|
-
"SIGNATURE",
|
|
113796
|
-
"APIKEY",
|
|
113797
|
-
"SECRETKEY",
|
|
113798
|
-
"ACCESSKEY",
|
|
113799
|
-
"AUTHTOKEN",
|
|
113800
|
-
"PAT",
|
|
113801
|
-
"WEBHOOK"
|
|
113802
|
-
];
|
|
113803
|
-
var SECRET_SEGMENT_RE = new RegExp(`(^|_)(${SECRET_SEGMENTS.join("|")})($|_)`);
|
|
113804
|
-
var SECRET_SUBSTRING_RE = /(PASSWORD|PASSWD|PASSPHRASE|PASSCODE|SSHPASS|SECRET|CREDENTIAL|APIKEY|KEYFILE|PRIVKEY)/;
|
|
113805
|
-
var GLUED_SECRET_RE = /(PRIVATE|SECRET|ACCESS|REFRESH|SESSION|CLIENT|API|APP)(KEY|TOKEN|SECRET)|DB(PASS|PWD)/;
|
|
113806
|
-
var CONNECTION_STRING_RE = /(^|_)(DSN|CONNECTION_STRING|CONNECTIONSTRING)($|_)/;
|
|
113807
|
-
var CREDENTIAL_IN_VALUE_RE = /:\/\/[^/\s@]*(:[^/\s@]*)?@/;
|
|
113808
|
-
var VALUE_DEPENDENT = {
|
|
113809
|
-
DOCKER_HOST: (value) => !value.startsWith("unix://") && !value.startsWith("fd://")
|
|
113810
|
-
};
|
|
113811
|
-
function isDeniedForMcpChild(name, value) {
|
|
113812
|
-
if (value !== undefined && CREDENTIAL_IN_VALUE_RE.test(value))
|
|
113813
|
-
return true;
|
|
113814
|
-
const upper = name.toUpperCase();
|
|
113815
|
-
if (MCP_ENV_ALLOW_EXACT.includes(upper))
|
|
113816
|
-
return false;
|
|
113817
|
-
const conditional = VALUE_DEPENDENT[upper];
|
|
113818
|
-
if (conditional !== undefined) {
|
|
113819
|
-
return value === undefined ? true : conditional(value);
|
|
113820
|
-
}
|
|
113821
|
-
if (EXTERNAL_ENV_DENY.some((denied) => denied.toUpperCase() === upper))
|
|
113822
|
-
return true;
|
|
113823
|
-
if (MCP_ENV_DENY.includes(upper))
|
|
113824
|
-
return true;
|
|
113825
|
-
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix.toUpperCase())))
|
|
113826
|
-
return true;
|
|
113827
|
-
if (MCP_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix)))
|
|
113828
|
-
return true;
|
|
113829
|
-
return SECRET_SEGMENT_RE.test(upper) || SECRET_SUBSTRING_RE.test(upper) || GLUED_SECRET_RE.test(upper) || CONNECTION_STRING_RE.test(upper);
|
|
113830
|
-
}
|
|
114137
|
+
init_service4();
|
|
113831
114138
|
function buildMcpChildEnv(input) {
|
|
113832
114139
|
const saved = input.savedCredentialKeys ?? new Set([...savedCredentialEnvKeys(), ...declaredCredentialEnvKeys(input.configDir)]);
|
|
113833
114140
|
const env = {};
|
|
@@ -128843,6 +129150,70 @@ function createSystemWebWorkerRunner() {
|
|
|
128843
129150
|
return new SystemWebWorkerRunner({ workspace: process.cwd(), home: homedir10() });
|
|
128844
129151
|
}
|
|
128845
129152
|
|
|
129153
|
+
// src/harness/web/outbound-secret.ts
|
|
129154
|
+
init_service4();
|
|
129155
|
+
function safeDecodeComponent(value) {
|
|
129156
|
+
try {
|
|
129157
|
+
return decodeURIComponent(value);
|
|
129158
|
+
} catch {
|
|
129159
|
+
return value;
|
|
129160
|
+
}
|
|
129161
|
+
}
|
|
129162
|
+
function outboundUrlComponents(text) {
|
|
129163
|
+
let url;
|
|
129164
|
+
try {
|
|
129165
|
+
url = new URL(text);
|
|
129166
|
+
} catch {
|
|
129167
|
+
return [];
|
|
129168
|
+
}
|
|
129169
|
+
const components = [];
|
|
129170
|
+
for (const segment of url.pathname.split("/")) {
|
|
129171
|
+
if (segment.length > 0) {
|
|
129172
|
+
components.push(safeDecodeComponent(segment));
|
|
129173
|
+
}
|
|
129174
|
+
}
|
|
129175
|
+
for (const [, value] of url.searchParams) {
|
|
129176
|
+
if (value.length > 0) {
|
|
129177
|
+
components.push(value);
|
|
129178
|
+
}
|
|
129179
|
+
}
|
|
129180
|
+
if (url.hash.length > 1) {
|
|
129181
|
+
components.push(safeDecodeComponent(url.hash.slice(1)));
|
|
129182
|
+
}
|
|
129183
|
+
return components;
|
|
129184
|
+
}
|
|
129185
|
+
function anyComponentLooksSecret(text) {
|
|
129186
|
+
const entropyGateOpen = isEntropyBackendEnabled();
|
|
129187
|
+
for (const component of outboundUrlComponents(text)) {
|
|
129188
|
+
if (detectSecrets(component).length > 0) {
|
|
129189
|
+
return true;
|
|
129190
|
+
}
|
|
129191
|
+
if (entropyGateOpen && looksSecretShapedIn(component)) {
|
|
129192
|
+
return true;
|
|
129193
|
+
}
|
|
129194
|
+
}
|
|
129195
|
+
return false;
|
|
129196
|
+
}
|
|
129197
|
+
function containsOutboundSecret(text) {
|
|
129198
|
+
if (detectSecrets(text).length > 0) {
|
|
129199
|
+
return true;
|
|
129200
|
+
}
|
|
129201
|
+
if (isEntropyBackendEnabled() && detectEntropy(text).length > 0) {
|
|
129202
|
+
return true;
|
|
129203
|
+
}
|
|
129204
|
+
return anyComponentLooksSecret(text);
|
|
129205
|
+
}
|
|
129206
|
+
async function recordOutboundSecretFinding(cwd, tool, subject) {
|
|
129207
|
+
try {
|
|
129208
|
+
await appendIncident(cwd, {
|
|
129209
|
+
at: new Date().toISOString(),
|
|
129210
|
+
type: "egress-blocked",
|
|
129211
|
+
message: "outbound secret-shaped content",
|
|
129212
|
+
details: { policyId: "egress.outbound-secret", tool, subject }
|
|
129213
|
+
});
|
|
129214
|
+
} catch {}
|
|
129215
|
+
}
|
|
129216
|
+
|
|
128846
129217
|
// src/harness/tool/builtin/web-fetch-tool.ts
|
|
128847
129218
|
function transportFor(deps) {
|
|
128848
129219
|
if (deps.transport !== undefined)
|
|
@@ -128866,6 +129237,12 @@ function webFetchTool(deps = {}) {
|
|
|
128866
129237
|
if (typeof input.url !== "string") {
|
|
128867
129238
|
return { output: "web_fetch: url must be an absolute HTTPS URL without credentials", isError: true };
|
|
128868
129239
|
}
|
|
129240
|
+
if (containsOutboundSecret(input.url)) {
|
|
129241
|
+
if (deps.cwd !== undefined) {
|
|
129242
|
+
await recordOutboundSecretFinding(deps.cwd, "web_fetch", "url");
|
|
129243
|
+
}
|
|
129244
|
+
return { output: "web_fetch: outbound secret-shaped content", isError: true };
|
|
129245
|
+
}
|
|
128869
129246
|
const result = await transport.fetchPage({ url: input.url, providerId: "web_fetch" });
|
|
128870
129247
|
return result.ok ? { output: result.value.text, isError: false, untrusted: true } : { output: `web_fetch: ${result.reason}`, isError: true };
|
|
128871
129248
|
}
|
|
@@ -128896,7 +129273,7 @@ ${result.canonicalUrl}`;
|
|
|
128896
129273
|
return lines.join(`
|
|
128897
129274
|
`).trim();
|
|
128898
129275
|
}
|
|
128899
|
-
function webSearchTool(service) {
|
|
129276
|
+
function webSearchTool(service, deps = {}) {
|
|
128900
129277
|
return {
|
|
128901
129278
|
definition: {
|
|
128902
129279
|
name: "web_search",
|
|
@@ -128908,7 +129285,14 @@ function webSearchTool(service) {
|
|
|
128908
129285
|
if (typeof input.query !== "string" || input.query.trim().length === 0) {
|
|
128909
129286
|
return { output: "web_search: query must be a non-empty string", isError: true };
|
|
128910
129287
|
}
|
|
128911
|
-
const
|
|
129288
|
+
const query = input.query.trim();
|
|
129289
|
+
if (containsOutboundSecret(query)) {
|
|
129290
|
+
if (deps.cwd !== undefined) {
|
|
129291
|
+
await recordOutboundSecretFinding(deps.cwd, "web_search", "query");
|
|
129292
|
+
}
|
|
129293
|
+
return { output: "web_search: outbound secret-shaped content", isError: true };
|
|
129294
|
+
}
|
|
129295
|
+
const response = await service.search(query);
|
|
128912
129296
|
if (!response.ok) {
|
|
128913
129297
|
return {
|
|
128914
129298
|
output: response.reason === "no-active-provider" ? "web_search: no active connected provider. Use /search-provider to configure one, test it, then use /search-connect to select it." : response.reason === "search-failed" ? `web_search: search failed${response.detail !== undefined && response.detail.length > 0 ? ` (${response.detail})` : ""}. Wait a few minutes before trying again, and do not switch providers yourself.` : "web_search: active provider is unavailable; reconnect it with /search-provider before retrying.",
|
|
@@ -129404,8 +129788,8 @@ function buildInteractiveAgentTools(input) {
|
|
|
129404
129788
|
const built = [
|
|
129405
129789
|
...builtinReadOnlyTools(input.cwd),
|
|
129406
129790
|
...metaprojectTools,
|
|
129407
|
-
webFetchTool(),
|
|
129408
|
-
webSearchTool(input.searchController),
|
|
129791
|
+
webFetchTool({ cwd: input.cwd }),
|
|
129792
|
+
webSearchTool(input.searchController, { cwd: input.cwd }),
|
|
129409
129793
|
shellExecTool(input.cwd, undefined, jobRegistry),
|
|
129410
129794
|
...jobRegistry !== undefined ? [
|
|
129411
129795
|
shellTaskOutputTool(jobRegistry, { observer: "main" }),
|
|
@@ -129878,12 +130262,13 @@ function evaluateShellApproval(input) {
|
|
|
129878
130262
|
const publishLease = input.meta?.publishLease === true;
|
|
129879
130263
|
const publishLeaseDetail = input.meta?.publishLeaseDetail;
|
|
129880
130264
|
const hookAsk = input.meta?.hookAsk === true;
|
|
130265
|
+
const untrustedOrigin = input.meta?.untrustedOrigin === true;
|
|
129881
130266
|
const audit = io.loadAudit();
|
|
129882
130267
|
for (const pattern of audit.permissions.allow) {
|
|
129883
130268
|
input.sessionAllow.add(pattern);
|
|
129884
130269
|
}
|
|
129885
130270
|
const tampered = io.fingerprint() !== input.fingerprintAtStart;
|
|
129886
|
-
const autoApprove = !destructive && !credentials && !sacReviewConfirmation && !publishLease && !hookAsk && isShellCommandAllowed(command, [...input.sessionAllow]);
|
|
130271
|
+
const autoApprove = !destructive && !credentials && !sacReviewConfirmation && !publishLease && !hookAsk && !untrustedOrigin && isShellCommandAllowed(command, [...input.sessionAllow]);
|
|
129887
130272
|
return {
|
|
129888
130273
|
command,
|
|
129889
130274
|
destructive,
|
|
@@ -129892,6 +130277,7 @@ function evaluateShellApproval(input) {
|
|
|
129892
130277
|
publishLease,
|
|
129893
130278
|
...publishLeaseDetail !== undefined ? { publishLeaseDetail } : {},
|
|
129894
130279
|
hookAsk,
|
|
130280
|
+
untrustedOrigin,
|
|
129895
130281
|
autoApprove,
|
|
129896
130282
|
rejected: audit.rejected,
|
|
129897
130283
|
tampered
|
|
@@ -129925,6 +130311,9 @@ function formatShellApprovalHints(evaled) {
|
|
|
129925
130311
|
if (evaled.hookAsk) {
|
|
129926
130312
|
lines.push("a policy hook asked about this call \u2014 will not be remembered");
|
|
129927
130313
|
}
|
|
130314
|
+
if (evaled.untrustedOrigin) {
|
|
130315
|
+
lines.push("follows untrusted external content \u2014 will not be remembered");
|
|
130316
|
+
}
|
|
129928
130317
|
if (evaled.publishLease) {
|
|
129929
130318
|
lines.push(evaled.publishLeaseDetail !== undefined ? `a git-publish lease applies \u2014 ${evaled.publishLeaseDetail} \u2014 will not be remembered` : "a peer's git-publish lease applies \u2014 will not be remembered");
|
|
129930
130319
|
}
|
|
@@ -131998,6 +132387,12 @@ function readRuntimeBlock(dispatch) {
|
|
|
131998
132387
|
return raw;
|
|
131999
132388
|
}
|
|
132000
132389
|
|
|
132390
|
+
// src/harness/external/env.ts
|
|
132391
|
+
init_service4();
|
|
132392
|
+
|
|
132393
|
+
// src/harness/external/env-deny.ts
|
|
132394
|
+
init_service4();
|
|
132395
|
+
|
|
132001
132396
|
// src/harness/external/env.ts
|
|
132002
132397
|
var EXTERNAL_RUNTIME_CREDENTIAL_ALLOW = {
|
|
132003
132398
|
"codex-cli": ["OPENAI_API_KEY"],
|
|
@@ -132005,17 +132400,18 @@ var EXTERNAL_RUNTIME_CREDENTIAL_ALLOW = {
|
|
|
132005
132400
|
};
|
|
132006
132401
|
var ENV_EXTERNAL_DEPTH = "KERYX_EXTERNAL_DEPTH";
|
|
132007
132402
|
function buildExternalChildEnv(input) {
|
|
132008
|
-
const denied = new Set(EXTERNAL_ENV_DENY);
|
|
132403
|
+
const denied = new Set(EXTERNAL_ENV_DENY.map((name) => name.toUpperCase()));
|
|
132009
132404
|
const allowedForThisRuntime = new Set((EXTERNAL_RUNTIME_CREDENTIAL_ALLOW[input.runtimeId] ?? []).map((name) => name.toUpperCase()));
|
|
132010
132405
|
const env = {};
|
|
132011
132406
|
for (const [key, value] of Object.entries(input.parent)) {
|
|
132012
132407
|
if (value === undefined)
|
|
132013
132408
|
continue;
|
|
132014
|
-
|
|
132409
|
+
const upper = key.toUpperCase();
|
|
132410
|
+
if (denied.has(upper))
|
|
132015
132411
|
continue;
|
|
132016
|
-
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) =>
|
|
132412
|
+
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix.toUpperCase())))
|
|
132017
132413
|
continue;
|
|
132018
|
-
if (!allowedForThisRuntime.has(
|
|
132414
|
+
if (!allowedForThisRuntime.has(upper) && isDeniedForMcpChild(key, value))
|
|
132019
132415
|
continue;
|
|
132020
132416
|
env[key] = value;
|
|
132021
132417
|
}
|
|
@@ -148088,7 +148484,7 @@ function ollamaShowAllowed(baseUrl) {
|
|
|
148088
148484
|
} catch {
|
|
148089
148485
|
return false;
|
|
148090
148486
|
}
|
|
148091
|
-
if (isPrivateEgressHost(host) && !
|
|
148487
|
+
if (isPrivateEgressHost(host) && !isLoopbackHost2(host)) {
|
|
148092
148488
|
return false;
|
|
148093
148489
|
}
|
|
148094
148490
|
return true;
|
|
@@ -155602,8 +155998,8 @@ async function recordTurnTaskCostBestEffort(input) {
|
|
|
155602
155998
|
}, input.userConfigDir);
|
|
155603
155999
|
} catch {}
|
|
155604
156000
|
}
|
|
155605
|
-
function shouldAutoApproveReadOnlySpawn(mode, hookAsk) {
|
|
155606
|
-
return mode === "read_only" && hookAsk !== true;
|
|
156001
|
+
function shouldAutoApproveReadOnlySpawn(mode, hookAsk, untrustedOrigin) {
|
|
156002
|
+
return mode === "read_only" && hookAsk !== true && untrustedOrigin !== true;
|
|
155607
156003
|
}
|
|
155608
156004
|
function isUserPromptApprovalCard(tool) {
|
|
155609
156005
|
return tool === "user_prompt";
|
|
@@ -157819,7 +158215,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
157819
158215
|
}
|
|
157820
158216
|
}
|
|
157821
158217
|
} catch {}
|
|
157822
|
-
if (shouldAutoApproveReadOnlySpawn(mode, meta?.hookAsk)) {
|
|
158218
|
+
if (shouldAutoApproveReadOnlySpawn(mode, meta?.hookAsk, meta?.untrustedOrigin)) {
|
|
157823
158219
|
transcript.add(new otui.TextRenderable(r, {
|
|
157824
158220
|
id: `ap${uid++}`,
|
|
157825
158221
|
content: otui.t`${roleChunk(otui, "accent", "\u25C7 subagent auto-approved")} ${dimChunk(otui, `mode=read_only (no shell) \xB7 ${taskPreview}`)}`
|
|
@@ -157829,7 +158225,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
157829
158225
|
chrome.hideMenu();
|
|
157830
158226
|
setMainAgent("blocked", "approval");
|
|
157831
158227
|
const id = await chrome.withOverlay(() => showComposerChoice(otui, r, chrome.dock, {
|
|
157832
|
-
title: mode === "read_only" ? "Spawn read-only subagent? (policy hook asked)" : "Spawn general subagent?",
|
|
158228
|
+
title: mode === "read_only" ? meta?.hookAsk === true ? "Spawn read-only subagent? (policy hook asked)" : "Spawn read-only subagent? (follows untrusted content)" : "Spawn general subagent?",
|
|
157833
158229
|
subtitle: taskPreview,
|
|
157834
158230
|
cancelId: "deny",
|
|
157835
158231
|
onOpen: () => chrome.blurComposer(),
|
|
@@ -158024,7 +158420,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
158024
158420
|
const choice = await chrome.withOverlay(() => pickShellApproval(otui, r, chrome.dock, cmd, approvalContext, destructive, meta?.credentials === true, {
|
|
158025
158421
|
onOpen: () => chrome.blurComposer(),
|
|
158026
158422
|
signal: foregroundOperation.signal
|
|
158027
|
-
}, ev.publishLease, ev.publishLeaseDetail, ev.hookAsk));
|
|
158423
|
+
}, ev.publishLease, ev.publishLeaseDetail, ev.hookAsk || ev.untrustedOrigin));
|
|
158028
158424
|
input.focus();
|
|
158029
158425
|
if (choice === "deny") {
|
|
158030
158426
|
transcript.add(new otui.TextRenderable(r, {
|
|
@@ -162300,7 +162696,7 @@ ${indentBlock(style.dim(context), GUTTER)}`);
|
|
|
162300
162696
|
out(`${GUTTER}${style.yellow(hint)}
|
|
162301
162697
|
`);
|
|
162302
162698
|
}
|
|
162303
|
-
const rememberable = !evaled.destructive && !evaled.credentials && !evaled.sacReviewConfirmation && !evaled.publishLease && !evaled.hookAsk;
|
|
162699
|
+
const rememberable = !evaled.destructive && !evaled.credentials && !evaled.sacReviewConfirmation && !evaled.publishLease && !evaled.hookAsk && !evaled.untrustedOrigin;
|
|
162304
162700
|
const prompt = rememberable ? "[y/N/A=always] " : "[y/N] ";
|
|
162305
162701
|
out(`
|
|
162306
162702
|
${GUTTER}${style.yellow(`Run: ${evaled.command}`)} ${style.dim(prompt)}`);
|