@mrciphersmith/keryx 0.3.18 → 0.3.19
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 +645 -305
- package/dist/core.js +491 -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,269 @@ 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 isSingleTransitionTag(segment) {
|
|
9095
|
+
return /^[A-Za-z]+[0-9]+$/.test(segment) || /^[0-9]+[A-Za-z]+$/.test(segment);
|
|
9096
|
+
}
|
|
9097
|
+
function isWordSlug(value) {
|
|
9098
|
+
const segments = value.split(/[-_]/);
|
|
9099
|
+
if (segments.length < 3) {
|
|
9100
|
+
return false;
|
|
9101
|
+
}
|
|
9102
|
+
let words = 0;
|
|
9103
|
+
for (const segment of segments) {
|
|
9104
|
+
const isAlpha = /^[A-Za-z]+$/.test(segment);
|
|
9105
|
+
const isDigits = /^[0-9]+$/.test(segment);
|
|
9106
|
+
if (!isAlpha && !isDigits && !isSingleTransitionTag(segment)) {
|
|
9107
|
+
return false;
|
|
9108
|
+
}
|
|
9109
|
+
if (isAlpha && segment.length >= 3) {
|
|
9110
|
+
words += 1;
|
|
9111
|
+
}
|
|
9112
|
+
}
|
|
9113
|
+
return words >= 1;
|
|
9114
|
+
}
|
|
9115
|
+
function shannonEntropy(value) {
|
|
9116
|
+
const counts = new Map;
|
|
9117
|
+
for (const ch of value) {
|
|
9118
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
9119
|
+
}
|
|
9120
|
+
let entropy = 0;
|
|
9121
|
+
for (const count of counts.values()) {
|
|
9122
|
+
const p = count / value.length;
|
|
9123
|
+
entropy -= p * Math.log2(p);
|
|
9124
|
+
}
|
|
9125
|
+
return entropy;
|
|
9126
|
+
}
|
|
9127
|
+
function bareShapeQualifies(head) {
|
|
9128
|
+
if (!/[0-9]/.test(head) && !/[+=]/.test(head)) {
|
|
9129
|
+
return { qualifies: false, entropy: 0 };
|
|
9130
|
+
}
|
|
9131
|
+
if (isWordSlug(head)) {
|
|
9132
|
+
return { qualifies: false, entropy: 0 };
|
|
9133
|
+
}
|
|
9134
|
+
const entropy = shannonEntropy(head);
|
|
9135
|
+
const hexBlob = isHexBlob(head);
|
|
9136
|
+
if (entropy < 3.6 && !hexBlob) {
|
|
9137
|
+
return { qualifies: false, entropy };
|
|
9138
|
+
}
|
|
9139
|
+
return { qualifies: true, entropy };
|
|
9140
|
+
}
|
|
9141
|
+
function shapeQualifiesAsSecret(value) {
|
|
9142
|
+
if (isAllowShapedValue(value)) {
|
|
9143
|
+
return { qualifies: false, entropy: 0 };
|
|
9144
|
+
}
|
|
9145
|
+
return bareShapeQualifies(value);
|
|
9146
|
+
}
|
|
9147
|
+
function looksSecretShaped(value) {
|
|
9148
|
+
return shapeQualifiesAsSecret(value).qualifies;
|
|
9149
|
+
}
|
|
9150
|
+
function buildMatch(start, end, value, entropy) {
|
|
9151
|
+
const confidence = Math.max(0.4, Math.min(0.7, 0.4 + (entropy - 3.6) * 0.21));
|
|
9152
|
+
return {
|
|
9153
|
+
category: "secret",
|
|
9154
|
+
policyId: "secrets.high-entropy",
|
|
9155
|
+
severity: "medium",
|
|
9156
|
+
confidence: Number(confidence.toFixed(2)),
|
|
9157
|
+
start,
|
|
9158
|
+
end,
|
|
9159
|
+
value,
|
|
9160
|
+
mask: "entropy",
|
|
9161
|
+
remediation: "Verify this high-entropy value is not a live credential."
|
|
9162
|
+
};
|
|
9163
|
+
}
|
|
9164
|
+
function findUrlSpans(content) {
|
|
9165
|
+
const spans = [];
|
|
9166
|
+
URL_SPAN_RE.lastIndex = 0;
|
|
9167
|
+
let m;
|
|
9168
|
+
while ((m = URL_SPAN_RE.exec(content)) !== null) {
|
|
9169
|
+
let raw = m[0];
|
|
9170
|
+
while (raw.length > 0 && URL_TRAILING_PUNCTUATION_RE.test(raw)) {
|
|
9171
|
+
raw = raw.slice(0, -1);
|
|
9172
|
+
}
|
|
9173
|
+
if (raw.length > 0) {
|
|
9174
|
+
spans.push({ start: m.index, end: m.index + raw.length, raw });
|
|
9175
|
+
}
|
|
9176
|
+
if (m.index === URL_SPAN_RE.lastIndex) {
|
|
9177
|
+
URL_SPAN_RE.lastIndex += 1;
|
|
9178
|
+
}
|
|
9179
|
+
}
|
|
9180
|
+
return spans;
|
|
9181
|
+
}
|
|
9182
|
+
function urlComponentSpans(raw) {
|
|
9183
|
+
const spans = [];
|
|
9184
|
+
const schemeMatch = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.exec(raw);
|
|
9185
|
+
if (schemeMatch === null) {
|
|
9186
|
+
return spans;
|
|
9187
|
+
}
|
|
9188
|
+
let i = schemeMatch[0].length;
|
|
9189
|
+
while (i < raw.length && raw[i] !== "/" && raw[i] !== "?" && raw[i] !== "#") {
|
|
9190
|
+
i += 1;
|
|
9191
|
+
}
|
|
9192
|
+
let pathEnd = i;
|
|
9193
|
+
while (pathEnd < raw.length && raw[pathEnd] !== "?" && raw[pathEnd] !== "#") {
|
|
9194
|
+
pathEnd += 1;
|
|
9195
|
+
}
|
|
9196
|
+
let segStart = i;
|
|
9197
|
+
for (let j = i;j <= pathEnd; j += 1) {
|
|
9198
|
+
if (j === pathEnd || raw[j] === "/") {
|
|
9199
|
+
if (j > segStart) {
|
|
9200
|
+
spans.push({ start: segStart, end: j });
|
|
9201
|
+
}
|
|
9202
|
+
segStart = j + 1;
|
|
9203
|
+
}
|
|
9204
|
+
}
|
|
9205
|
+
let cursor = pathEnd;
|
|
9206
|
+
if (cursor < raw.length && raw[cursor] === "?") {
|
|
9207
|
+
let queryEnd = cursor + 1;
|
|
9208
|
+
while (queryEnd < raw.length && raw[queryEnd] !== "#") {
|
|
9209
|
+
queryEnd += 1;
|
|
9210
|
+
}
|
|
9211
|
+
let pairStart = cursor + 1;
|
|
9212
|
+
for (let j = cursor + 1;j <= queryEnd; j += 1) {
|
|
9213
|
+
if (j === queryEnd || raw[j] === "&") {
|
|
9214
|
+
const pair = raw.slice(pairStart, j);
|
|
9215
|
+
const eq = pair.indexOf("=");
|
|
9216
|
+
if (eq !== -1 && eq + 1 < pair.length) {
|
|
9217
|
+
spans.push({ start: pairStart + eq + 1, end: j });
|
|
9218
|
+
} else if (eq === -1 && pair.length > 0) {
|
|
9219
|
+
spans.push({ start: pairStart, end: j });
|
|
9220
|
+
}
|
|
9221
|
+
pairStart = j + 1;
|
|
9222
|
+
}
|
|
9223
|
+
}
|
|
9224
|
+
cursor = queryEnd;
|
|
9225
|
+
}
|
|
9226
|
+
if (cursor < raw.length && raw[cursor] === "#" && cursor + 1 < raw.length) {
|
|
9227
|
+
spans.push({ start: cursor + 1, end: raw.length });
|
|
9228
|
+
}
|
|
9229
|
+
return spans;
|
|
9230
|
+
}
|
|
9231
|
+
function secretShapedCandidatesIn(text) {
|
|
9232
|
+
const normalized = text.replace(/\+/g, " ");
|
|
9233
|
+
const out = [];
|
|
9234
|
+
TOKEN.lastIndex = 0;
|
|
9235
|
+
let m;
|
|
9236
|
+
while ((m = TOKEN.exec(normalized)) !== null) {
|
|
9237
|
+
const value = m[0];
|
|
9238
|
+
if (value.length < URL_COMPONENT_MIN_LENGTH) {
|
|
9239
|
+
continue;
|
|
9240
|
+
}
|
|
9241
|
+
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
9242
|
+
const { qualifies, entropy } = shapeQualifiesAsSecret(head);
|
|
9243
|
+
if (!qualifies) {
|
|
9244
|
+
continue;
|
|
9245
|
+
}
|
|
9246
|
+
const start = m.index;
|
|
9247
|
+
const end = start + value.length;
|
|
9248
|
+
out.push({ start, end, value: text.slice(start, end), entropy });
|
|
9249
|
+
}
|
|
9250
|
+
return out;
|
|
9251
|
+
}
|
|
9252
|
+
function looksSecretShapedIn(text) {
|
|
9253
|
+
return secretShapedCandidatesIn(text).length > 0;
|
|
9254
|
+
}
|
|
9255
|
+
function detectEntropy(content) {
|
|
9256
|
+
const matches = [];
|
|
9257
|
+
const urlSpans = findUrlSpans(content);
|
|
9258
|
+
for (const url of urlSpans) {
|
|
9259
|
+
for (const comp of urlComponentSpans(url.raw)) {
|
|
9260
|
+
const compText = url.raw.slice(comp.start, comp.end);
|
|
9261
|
+
for (const cand of secretShapedCandidatesIn(compText)) {
|
|
9262
|
+
const absStart = url.start + comp.start + cand.start;
|
|
9263
|
+
const absEnd = url.start + comp.start + cand.end;
|
|
9264
|
+
matches.push(buildMatch(absStart, absEnd, cand.value, cand.entropy));
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
9267
|
+
}
|
|
9268
|
+
LABEL_ASSIGNMENT_RE.lastIndex = 0;
|
|
9269
|
+
let am;
|
|
9270
|
+
while ((am = LABEL_ASSIGNMENT_RE.exec(content)) !== null) {
|
|
9271
|
+
const value = am[1];
|
|
9272
|
+
const end = am.index + am[0].length;
|
|
9273
|
+
const start = end - value.length;
|
|
9274
|
+
if (urlSpans.some((u) => start < u.end && end > u.start)) {
|
|
9275
|
+
continue;
|
|
9276
|
+
}
|
|
9277
|
+
const { qualifies, entropy } = bareShapeQualifies(value);
|
|
9278
|
+
if (qualifies) {
|
|
9279
|
+
matches.push(buildMatch(start, end, value, entropy));
|
|
9280
|
+
}
|
|
9281
|
+
}
|
|
9282
|
+
TOKEN.lastIndex = 0;
|
|
9283
|
+
let m;
|
|
9284
|
+
while ((m = TOKEN.exec(content)) !== null) {
|
|
9285
|
+
const value = m[0];
|
|
9286
|
+
const start = m.index;
|
|
9287
|
+
const end = start + value.length;
|
|
9288
|
+
if (urlSpans.some((u) => start < u.end && end > u.start)) {
|
|
9289
|
+
continue;
|
|
9290
|
+
}
|
|
9291
|
+
if (matches.some((existing) => start < existing.end && end > existing.start)) {
|
|
9292
|
+
continue;
|
|
9293
|
+
}
|
|
9294
|
+
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
9295
|
+
const { qualifies, entropy } = bareShapeQualifies(head);
|
|
9296
|
+
if (!qualifies) {
|
|
9297
|
+
continue;
|
|
9298
|
+
}
|
|
9299
|
+
const before = lastLineOf(content.slice(Math.max(0, start - LABEL_WINDOW), start));
|
|
9300
|
+
if (!SENSITIVE_LABEL.test(before)) {
|
|
9301
|
+
continue;
|
|
9302
|
+
}
|
|
9303
|
+
if (isAllowShapedValue(head) && !ADJACENT_LABEL.test(before)) {
|
|
9304
|
+
continue;
|
|
9305
|
+
}
|
|
9306
|
+
matches.push(buildMatch(start, end, value, entropy));
|
|
9307
|
+
}
|
|
9308
|
+
return matches.sort((a, b) => a.start - b.start);
|
|
9309
|
+
}
|
|
9310
|
+
var SENSITIVE_LABEL_WORDS = "key|secret|token|password|passwd|api|credential|auth|bearer", caseless = (words) => words.replace(/[a-z]/g, (ch) => `[${ch}${ch.toUpperCase()}]`), SENSITIVE_LABEL, ADJACENT_LABEL, LABEL_ASSIGNMENT_RE, TOKEN, TOKEN_HEAD, LABEL_WINDOW = 40, HEX_BLOB, FULL_GIT_SHA_RE, UUID_RE, INTEGRITY_RE, URL_SPAN_RE, URL_TRAILING_PUNCTUATION_RE, URL_COMPONENT_MIN_LENGTH = 20;
|
|
9311
|
+
var init_entropy = __esm(() => {
|
|
9312
|
+
SENSITIVE_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(${caseless(SENSITIVE_LABEL_WORDS)})(?:$|[^A-Za-z0-9]|(?=[A-Z]))`);
|
|
9313
|
+
ADJACENT_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*["']?\\s*[:=]?\\s*["']?$`);
|
|
9314
|
+
LABEL_ASSIGNMENT_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${SENSITIVE_LABEL_WORDS})[A-Za-z0-9_-]*"?\\s*=\\s*"?([A-Za-z0-9+/_=-]{6,})`, "gi");
|
|
9315
|
+
TOKEN = /[A-Za-z0-9+/=_-]{20,}(?:\.[A-Za-z0-9+/=_-]{6,})*/g;
|
|
9316
|
+
TOKEN_HEAD = /^[A-Za-z0-9+/=_-]+/;
|
|
9317
|
+
HEX_BLOB = /^[0-9a-f]{24,}$/i;
|
|
9318
|
+
FULL_GIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
9319
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9320
|
+
INTEGRITY_RE = /^(?:sha256|sha512)-[A-Za-z0-9+/]+=*$/;
|
|
9321
|
+
URL_SPAN_RE = /\bhttps?:\/\/[^\s<>"'\\]+/g;
|
|
9322
|
+
URL_TRAILING_PUNCTUATION_RE = /[.,;:!?)\]}]$/;
|
|
9323
|
+
});
|
|
9324
|
+
|
|
9325
|
+
// src/security/entropy-gate.ts
|
|
9326
|
+
function ensureLoading() {
|
|
9327
|
+
if (loadStarted) {
|
|
9328
|
+
return;
|
|
9329
|
+
}
|
|
9330
|
+
loadStarted = true;
|
|
9331
|
+
loadSecurityConfig(process.cwd()).then((config) => {
|
|
9332
|
+
cachedEnabled = config.backends.entropy.enabled;
|
|
9333
|
+
}).catch(() => {});
|
|
9334
|
+
}
|
|
9335
|
+
function isEntropyBackendEnabled() {
|
|
9336
|
+
ensureLoading();
|
|
9337
|
+
return cachedEnabled;
|
|
9338
|
+
}
|
|
9339
|
+
var cachedEnabled, loadStarted = false;
|
|
9340
|
+
var init_entropy_gate = __esm(() => {
|
|
9341
|
+
init_config();
|
|
9342
|
+
cachedEnabled = DEFAULT_SECURITY_CONFIG.backends.entropy.enabled;
|
|
9343
|
+
});
|
|
9344
|
+
|
|
9063
9345
|
// src/security/redact.ts
|
|
9064
9346
|
import path14 from "path";
|
|
9065
9347
|
import { createHmac, randomBytes } from "crypto";
|
|
@@ -9129,7 +9411,12 @@ function redactSensitiveText(text) {
|
|
|
9129
9411
|
if (text.length === 0) {
|
|
9130
9412
|
return text;
|
|
9131
9413
|
}
|
|
9132
|
-
const matches = [
|
|
9414
|
+
const matches = [
|
|
9415
|
+
...detectSecrets(text),
|
|
9416
|
+
...detectPii(text),
|
|
9417
|
+
...detectExfil(text),
|
|
9418
|
+
...isEntropyBackendEnabled() ? detectEntropy(text) : []
|
|
9419
|
+
];
|
|
9133
9420
|
if (matches.length === 0) {
|
|
9134
9421
|
return text;
|
|
9135
9422
|
}
|
|
@@ -9166,6 +9453,8 @@ var init_redact = __esm(() => {
|
|
|
9166
9453
|
init_secrets();
|
|
9167
9454
|
init_pii();
|
|
9168
9455
|
init_exfil();
|
|
9456
|
+
init_entropy();
|
|
9457
|
+
init_entropy_gate();
|
|
9169
9458
|
});
|
|
9170
9459
|
|
|
9171
9460
|
// src/security/output-validation.ts
|
|
@@ -9909,102 +10198,33 @@ var init_seam = __esm(() => {
|
|
|
9909
10198
|
init_warn_once();
|
|
9910
10199
|
});
|
|
9911
10200
|
|
|
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
|
-
});
|
|
10201
|
+
// src/security/detect/injection.ts
|
|
10202
|
+
function foldForMatching(content) {
|
|
10203
|
+
let folded = "";
|
|
10204
|
+
for (let i = 0;i < content.length; i += 1) {
|
|
10205
|
+
const ch = content[i];
|
|
10206
|
+
folded += CONFUSABLES[ch] ?? ch;
|
|
9982
10207
|
}
|
|
9983
|
-
|
|
10208
|
+
const nfkc = folded.normalize("NFKC");
|
|
10209
|
+
return nfkc.length === folded.length ? nfkc : folded;
|
|
9984
10210
|
}
|
|
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
10211
|
function detectInjection(content) {
|
|
9995
10212
|
const matches = [];
|
|
10213
|
+
const folded = foldForMatching(content);
|
|
9996
10214
|
for (const pattern of PATTERNS) {
|
|
9997
10215
|
pattern.regex.lastIndex = 0;
|
|
9998
10216
|
let m;
|
|
9999
|
-
while ((m = pattern.regex.exec(
|
|
10217
|
+
while ((m = pattern.regex.exec(folded)) !== null) {
|
|
10218
|
+
const start = Math.min(m.index, content.length);
|
|
10219
|
+
const end = Math.min(m.index + m[0].length, content.length);
|
|
10000
10220
|
matches.push({
|
|
10001
10221
|
category: "prompt-injection",
|
|
10002
10222
|
policyId: pattern.policyId,
|
|
10003
10223
|
severity: "low",
|
|
10004
10224
|
confidence: pattern.confidence,
|
|
10005
|
-
start
|
|
10006
|
-
end
|
|
10007
|
-
value:
|
|
10225
|
+
start,
|
|
10226
|
+
end,
|
|
10227
|
+
value: content.slice(start, end),
|
|
10008
10228
|
remediation: "Treat external content as data, not instruction; require human review."
|
|
10009
10229
|
});
|
|
10010
10230
|
if (m.index === pattern.regex.lastIndex) {
|
|
@@ -10014,27 +10234,69 @@ function detectInjection(content) {
|
|
|
10014
10234
|
}
|
|
10015
10235
|
return matches;
|
|
10016
10236
|
}
|
|
10017
|
-
var PATTERNS;
|
|
10237
|
+
var CONFUSABLES, PATTERNS;
|
|
10018
10238
|
var init_injection = __esm(() => {
|
|
10239
|
+
CONFUSABLES = {
|
|
10240
|
+
"\u0430": "a",
|
|
10241
|
+
"\u0410": "A",
|
|
10242
|
+
"\u0435": "e",
|
|
10243
|
+
"\u0415": "E",
|
|
10244
|
+
"\u043E": "o",
|
|
10245
|
+
"\u041E": "O",
|
|
10246
|
+
"\u0440": "p",
|
|
10247
|
+
"\u0420": "P",
|
|
10248
|
+
"\u0441": "c",
|
|
10249
|
+
"\u0421": "C",
|
|
10250
|
+
"\u0445": "x",
|
|
10251
|
+
"\u0425": "X",
|
|
10252
|
+
"\u0443": "y",
|
|
10253
|
+
"\u0423": "Y",
|
|
10254
|
+
"\u0455": "s",
|
|
10255
|
+
"\u0405": "S",
|
|
10256
|
+
"\u0456": "o",
|
|
10257
|
+
"\u0406": "O",
|
|
10258
|
+
"\u0458": "j",
|
|
10259
|
+
"\u0408": "J",
|
|
10260
|
+
"\u03B1": "a",
|
|
10261
|
+
"\u0391": "A",
|
|
10262
|
+
"\u03B2": "b",
|
|
10263
|
+
"\u0392": "B",
|
|
10264
|
+
"\u03B5": "e",
|
|
10265
|
+
"\u0395": "E",
|
|
10266
|
+
"\u03B9": "i",
|
|
10267
|
+
"\u0399": "I",
|
|
10268
|
+
"\u03BA": "k",
|
|
10269
|
+
"\u039A": "K",
|
|
10270
|
+
"\u03BF": "o",
|
|
10271
|
+
"\u039F": "O",
|
|
10272
|
+
"\u03C1": "p",
|
|
10273
|
+
"\u03A1": "P",
|
|
10274
|
+
"\u03C4": "t",
|
|
10275
|
+
"\u03A4": "T",
|
|
10276
|
+
"\u03C5": "u",
|
|
10277
|
+
"\u03A5": "Y",
|
|
10278
|
+
"\u03C7": "x",
|
|
10279
|
+
"\u03A7": "X"
|
|
10280
|
+
};
|
|
10019
10281
|
PATTERNS = [
|
|
10020
10282
|
{
|
|
10021
10283
|
policyId: "prompt-injection.ignore-instructions",
|
|
10022
|
-
regex: /\b(ignore|disregard|forget|override)\b[
|
|
10284
|
+
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
10285
|
confidence: 0.4
|
|
10024
10286
|
},
|
|
10025
10287
|
{
|
|
10026
10288
|
policyId: "prompt-injection.reveal-context",
|
|
10027
|
-
regex: /\b(reveal|show|print|expose|leak|dump|repeat)\b[
|
|
10289
|
+
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
10290
|
confidence: 0.45
|
|
10029
10291
|
},
|
|
10030
10292
|
{
|
|
10031
10293
|
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[
|
|
10294
|
+
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
10295
|
confidence: 0.4
|
|
10034
10296
|
},
|
|
10035
10297
|
{
|
|
10036
10298
|
policyId: "prompt-injection.priority-override",
|
|
10037
|
-
regex: /\b(treat|follow|obey)\b[
|
|
10299
|
+
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
10300
|
confidence: 0.35
|
|
10039
10301
|
}
|
|
10040
10302
|
];
|
|
@@ -11204,6 +11466,140 @@ var init_read_source = __esm(() => {
|
|
|
11204
11466
|
init_contained_path();
|
|
11205
11467
|
});
|
|
11206
11468
|
|
|
11469
|
+
// src/security/credential-shape.ts
|
|
11470
|
+
function isDeniedForMcpChild(name, value) {
|
|
11471
|
+
if (value !== undefined && CREDENTIAL_IN_VALUE_RE.test(value))
|
|
11472
|
+
return true;
|
|
11473
|
+
const upper = name.toUpperCase();
|
|
11474
|
+
if (MCP_ENV_ALLOW_EXACT.includes(upper))
|
|
11475
|
+
return false;
|
|
11476
|
+
const conditional = VALUE_DEPENDENT[upper];
|
|
11477
|
+
if (conditional !== undefined) {
|
|
11478
|
+
return value === undefined ? true : conditional(value);
|
|
11479
|
+
}
|
|
11480
|
+
if (EXTERNAL_ENV_DENY.some((denied) => denied.toUpperCase() === upper))
|
|
11481
|
+
return true;
|
|
11482
|
+
if (MCP_ENV_DENY.includes(upper))
|
|
11483
|
+
return true;
|
|
11484
|
+
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix.toUpperCase())))
|
|
11485
|
+
return true;
|
|
11486
|
+
if (MCP_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix)))
|
|
11487
|
+
return true;
|
|
11488
|
+
return SECRET_SEGMENT_RE.test(upper) || SECRET_SUBSTRING_RE.test(upper) || GLUED_SECRET_RE.test(upper) || CONNECTION_STRING_RE.test(upper);
|
|
11489
|
+
}
|
|
11490
|
+
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;
|
|
11491
|
+
var init_credential_shape = __esm(() => {
|
|
11492
|
+
EXTERNAL_ENV_DENY = [
|
|
11493
|
+
"ANTHROPIC_API_KEY",
|
|
11494
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
11495
|
+
"ANTHROPIC_BASE_URL",
|
|
11496
|
+
"ANTHROPIC_MODEL",
|
|
11497
|
+
"CLAUDE_CONFIG_DIR",
|
|
11498
|
+
"CODEX_HOME",
|
|
11499
|
+
"CLAUDECODE"
|
|
11500
|
+
];
|
|
11501
|
+
EXTERNAL_ENV_PREFIX_SWEEPS = ["CLAUDE_CODE_", "KERYX_"];
|
|
11502
|
+
MCP_ENV_ALLOW_EXACT = ["PWD", "OLDPWD"];
|
|
11503
|
+
MCP_ENV_DENY = [
|
|
11504
|
+
"SSH_AUTH_SOCK",
|
|
11505
|
+
"SSH_AGENT_PID",
|
|
11506
|
+
"GPG_AGENT_INFO",
|
|
11507
|
+
"GNUPGHOME",
|
|
11508
|
+
"GNOME_KEYRING_CONTROL",
|
|
11509
|
+
"GNOME_KEYRING_PID",
|
|
11510
|
+
"KDE_FULL_SESSION",
|
|
11511
|
+
"GIT_ASKPASS",
|
|
11512
|
+
"SSH_ASKPASS",
|
|
11513
|
+
"SUDO_ASKPASS",
|
|
11514
|
+
"GIT_CREDENTIAL_HELPER",
|
|
11515
|
+
"GIT_SSH_COMMAND",
|
|
11516
|
+
"KUBECONFIG",
|
|
11517
|
+
"NETRC",
|
|
11518
|
+
"PGPASSFILE",
|
|
11519
|
+
"PGSERVICEFILE",
|
|
11520
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
11521
|
+
"CLOUDSDK_CONFIG",
|
|
11522
|
+
"DOCKER_CONFIG",
|
|
11523
|
+
"AWS_SHARED_CREDENTIALS_FILE",
|
|
11524
|
+
"AWS_CONFIG_FILE",
|
|
11525
|
+
"BOTO_CONFIG",
|
|
11526
|
+
"AZURE_CONFIG_DIR",
|
|
11527
|
+
"CURLRC",
|
|
11528
|
+
"WGETRC",
|
|
11529
|
+
"NPMRC",
|
|
11530
|
+
"NPM_CONFIG_USERCONFIG",
|
|
11531
|
+
"PIP_CONFIG_FILE",
|
|
11532
|
+
"RCLONE_CONFIG",
|
|
11533
|
+
"MAVEN_SETTINGS",
|
|
11534
|
+
"GIT_CONFIG",
|
|
11535
|
+
"GIT_CONFIG_GLOBAL",
|
|
11536
|
+
"HGRCPATH",
|
|
11537
|
+
"ANSIBLE_VAULT_PASSWORD_FILE",
|
|
11538
|
+
"AZURE_CLIENT_CERTIFICATE_PATH",
|
|
11539
|
+
"ARM_CLIENT_CERTIFICATE_PATH",
|
|
11540
|
+
"VAULT_CLIENT_CERT",
|
|
11541
|
+
"VAULT_CLIENT_KEY",
|
|
11542
|
+
"SSL_CLIENT_CERT_PATH",
|
|
11543
|
+
"SSL_CLIENT_KEY_FILE",
|
|
11544
|
+
"IDENTITY_FILE",
|
|
11545
|
+
"SSH_IDENTITY_FILE",
|
|
11546
|
+
"GCP_SERVICE_ACCOUNT",
|
|
11547
|
+
"XAUTHORITY",
|
|
11548
|
+
"OPENAI_API_KEY",
|
|
11549
|
+
"GEMINI_API_KEY",
|
|
11550
|
+
"GOOGLE_API_KEY",
|
|
11551
|
+
"GROQ_API_KEY",
|
|
11552
|
+
"MISTRAL_API_KEY",
|
|
11553
|
+
"XAI_API_KEY",
|
|
11554
|
+
"DEEPSEEK_API_KEY",
|
|
11555
|
+
"OPENROUTER_API_KEY",
|
|
11556
|
+
"TOGETHER_API_KEY",
|
|
11557
|
+
"PERPLEXITY_API_KEY"
|
|
11558
|
+
];
|
|
11559
|
+
MCP_ENV_PREFIX_SWEEPS = [
|
|
11560
|
+
"OP_SESSION",
|
|
11561
|
+
"BW_SESSION"
|
|
11562
|
+
];
|
|
11563
|
+
SECRET_SEGMENTS = [
|
|
11564
|
+
"KEY",
|
|
11565
|
+
"KEYS",
|
|
11566
|
+
"TOKEN",
|
|
11567
|
+
"TOKENS",
|
|
11568
|
+
"SECRET",
|
|
11569
|
+
"SECRETS",
|
|
11570
|
+
"PASSWORD",
|
|
11571
|
+
"PASSWD",
|
|
11572
|
+
"PWD",
|
|
11573
|
+
"PASS",
|
|
11574
|
+
"PASSPHRASE",
|
|
11575
|
+
"PASSCODE",
|
|
11576
|
+
"PASSKEY",
|
|
11577
|
+
"CREDENTIAL",
|
|
11578
|
+
"CREDENTIALS",
|
|
11579
|
+
"CREDS",
|
|
11580
|
+
"AUTH",
|
|
11581
|
+
"AUTHORIZATION",
|
|
11582
|
+
"BEARER",
|
|
11583
|
+
"JWT",
|
|
11584
|
+
"COOKIE",
|
|
11585
|
+
"SIGNATURE",
|
|
11586
|
+
"APIKEY",
|
|
11587
|
+
"SECRETKEY",
|
|
11588
|
+
"ACCESSKEY",
|
|
11589
|
+
"AUTHTOKEN",
|
|
11590
|
+
"PAT",
|
|
11591
|
+
"WEBHOOK"
|
|
11592
|
+
];
|
|
11593
|
+
SECRET_SEGMENT_RE = new RegExp(`(^|_)(${SECRET_SEGMENTS.join("|")})($|_)`);
|
|
11594
|
+
SECRET_SUBSTRING_RE = /(PASSWORD|PASSWD|PASSPHRASE|PASSCODE|SSHPASS|SECRET|CREDENTIAL|APIKEY|KEYFILE|PRIVKEY)/;
|
|
11595
|
+
GLUED_SECRET_RE = /(PRIVATE|SECRET|ACCESS|REFRESH|SESSION|CLIENT|API|APP)(KEY|TOKEN|SECRET)($|_)|DB(PASS|PWD)($|_)/;
|
|
11596
|
+
CONNECTION_STRING_RE = /(^|_)(DSN|CONNECTION_STRING|CONNECTIONSTRING)($|_)/;
|
|
11597
|
+
CREDENTIAL_IN_VALUE_RE = /:\/\/[^/\s@]*(:[^/\s@]*)?@/;
|
|
11598
|
+
VALUE_DEPENDENT = {
|
|
11599
|
+
DOCKER_HOST: (value) => !value.startsWith("unix://") && !value.startsWith("fd://")
|
|
11600
|
+
};
|
|
11601
|
+
});
|
|
11602
|
+
|
|
11207
11603
|
// src/security/audit-harness/codex-toml.ts
|
|
11208
11604
|
function stripComment(line) {
|
|
11209
11605
|
let quoted = false;
|
|
@@ -17641,7 +18037,7 @@ var package_default;
|
|
|
17641
18037
|
var init_package = __esm(() => {
|
|
17642
18038
|
package_default = {
|
|
17643
18039
|
name: "@mrciphersmith/keryx",
|
|
17644
|
-
version: "0.3.
|
|
18040
|
+
version: "0.3.19",
|
|
17645
18041
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
17646
18042
|
private: false,
|
|
17647
18043
|
publishConfig: {
|
|
@@ -28787,6 +29183,11 @@ var init_service4 = __esm(() => {
|
|
|
28787
29183
|
init_path_scan();
|
|
28788
29184
|
init_read_source();
|
|
28789
29185
|
init_redact();
|
|
29186
|
+
init_secrets();
|
|
29187
|
+
init_entropy();
|
|
29188
|
+
init_incidents();
|
|
29189
|
+
init_entropy_gate();
|
|
29190
|
+
init_credential_shape();
|
|
28790
29191
|
init_audit_harness();
|
|
28791
29192
|
init_config();
|
|
28792
29193
|
init_impact_evidence();
|
|
@@ -29144,7 +29545,7 @@ class OpenAiCompatEngine {
|
|
|
29144
29545
|
} catch {
|
|
29145
29546
|
host = baseUrl;
|
|
29146
29547
|
}
|
|
29147
|
-
const permitted = !isPrivateEgressHost(host) || grant.allowLoopback === true &&
|
|
29548
|
+
const permitted = !isPrivateEgressHost(host) || grant.allowLoopback === true && isLoopbackHost2(host) || grant.allowPrivateLan === true && isPrivateLanHost(host);
|
|
29148
29549
|
if (!permitted) {
|
|
29149
29550
|
yield errorEvent({
|
|
29150
29551
|
kind: "invalid_request",
|
|
@@ -58893,53 +59294,18 @@ ${shown.join(`
|
|
|
58893
59294
|
}
|
|
58894
59295
|
const risk = toolByName.get(call.name)?.definition.risk;
|
|
58895
59296
|
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
|
-
}
|
|
59297
|
+
const untrustedOrigin = !isPureReadTool && untrustedContentSeen;
|
|
59298
|
+
if (untrustedOrigin && deps.unattended === true) {
|
|
59299
|
+
const result = {
|
|
59300
|
+
output: "tool blocked: external web content cannot authorize further tool calls in this turn",
|
|
59301
|
+
isError: true
|
|
59302
|
+
};
|
|
59303
|
+
io.onUnattendedDenial?.(call.name, "untrusted external content in this turn cannot authorize the call");
|
|
59304
|
+
io.onToolResult?.(call.name, result);
|
|
59305
|
+
history.push({ role: "tool", content: withBudgetWarning(result.output), provenance: "tool", toolCallId: call.id, ts: now() });
|
|
59306
|
+
io.onHistoryChange?.("tool");
|
|
59307
|
+
gateBlockedAny = true;
|
|
59308
|
+
continue;
|
|
58943
59309
|
}
|
|
58944
59310
|
io.onToolCall?.(call.name, call.input);
|
|
58945
59311
|
const reservation = reservationByCallId.get(call.id) ?? reserveToolAttempt(budget, call.name, call.input);
|
|
@@ -58961,7 +59327,7 @@ ${shown.join(`
|
|
|
58961
59327
|
result = precomputedResult;
|
|
58962
59328
|
} else {
|
|
58963
59329
|
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);
|
|
59330
|
+
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
59331
|
} catch (err) {
|
|
58966
59332
|
const message = err instanceof Error ? err.message : String(err);
|
|
58967
59333
|
result = { output: `${call.name} failed: ${message}`, isError: true };
|
|
@@ -59531,7 +59897,7 @@ function composeWithHook(toolName, rawDecision, hookResult, interactive) {
|
|
|
59531
59897
|
}
|
|
59532
59898
|
return { decision, hookTightened: true, hookAsked, ...denyMessage !== undefined ? { denyMessage } : {} };
|
|
59533
59899
|
}
|
|
59534
|
-
async function executeCall(call, toolByName, requestApproval, permissionMode, readOnly, onAutoApproved, hasInvocationCapacity, reserveInvocation, maxToolCalls, signal, busLeases, hardDeny, hooks) {
|
|
59900
|
+
async function executeCall(call, toolByName, requestApproval, permissionMode, readOnly, onAutoApproved, hasInvocationCapacity, reserveInvocation, maxToolCalls, signal, busLeases, hardDeny, hooks, untrustedOrigin = false) {
|
|
59535
59901
|
const tool = toolByName.get(call.name);
|
|
59536
59902
|
if (tool === undefined) {
|
|
59537
59903
|
return { output: `unknown tool: ${call.name}`, isError: true };
|
|
@@ -59557,6 +59923,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59557
59923
|
}
|
|
59558
59924
|
}
|
|
59559
59925
|
const mode = permissionMode?.() ?? DEFAULT_PERMISSION_MODE;
|
|
59926
|
+
const untrustedDenial = "tool blocked: external web content cannot authorize this call, and the user did not authorize it either";
|
|
59560
59927
|
const isReadOnly = readOnly?.() ?? false;
|
|
59561
59928
|
let hookResult;
|
|
59562
59929
|
if (hooks !== undefined) {
|
|
@@ -59597,7 +59964,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59597
59964
|
isError: true
|
|
59598
59965
|
};
|
|
59599
59966
|
}
|
|
59600
|
-
if (gated.decision === "auto") {
|
|
59967
|
+
if (gated.decision === "auto" && !untrustedOrigin) {
|
|
59601
59968
|
onAutoApproved?.(call.name, call.input, { destructive, credentials });
|
|
59602
59969
|
} else {
|
|
59603
59970
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
@@ -59607,10 +59974,11 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59607
59974
|
...credentials ? { credentials } : {},
|
|
59608
59975
|
...publishLease ? { publishLease } : {},
|
|
59609
59976
|
...publishLease && publishLeaseHolder !== undefined ? { publishLeaseDetail: `held by @${publishLeaseHolder.name} \u2014 "${publishLeaseHolder.reason}"` } : {},
|
|
59610
|
-
...gated.hookAsked ? { hookAsk: true } : {}
|
|
59977
|
+
...gated.hookAsked ? { hookAsk: true } : {},
|
|
59978
|
+
...untrustedOrigin ? { untrustedOrigin: true } : {}
|
|
59611
59979
|
});
|
|
59612
59980
|
if (!isApprovalFor(response, fingerprint)) {
|
|
59613
|
-
return { output:
|
|
59981
|
+
return { output: untrustedOrigin ? untrustedDenial : "command not approved by the user; not executed", isError: true };
|
|
59614
59982
|
}
|
|
59615
59983
|
}
|
|
59616
59984
|
} else if (risk === "delegate") {
|
|
@@ -59629,17 +59997,18 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59629
59997
|
isError: true
|
|
59630
59998
|
};
|
|
59631
59999
|
}
|
|
59632
|
-
if (gated.decision === "auto") {
|
|
60000
|
+
if (gated.decision === "auto" && !untrustedOrigin) {
|
|
59633
60001
|
onAutoApproved?.(call.name, call.input, { destructive: false, credentials: false });
|
|
59634
60002
|
} else {
|
|
59635
60003
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
59636
60004
|
const response = requestApproval === undefined ? false : await requestApproval(call.name, call.input, {
|
|
59637
60005
|
fingerprint,
|
|
59638
60006
|
destructive: false,
|
|
59639
|
-
...gated.hookAsked ? { hookAsk: true } : {}
|
|
60007
|
+
...gated.hookAsked ? { hookAsk: true } : {},
|
|
60008
|
+
...untrustedOrigin ? { untrustedOrigin: true } : {}
|
|
59640
60009
|
});
|
|
59641
60010
|
if (!isApprovalFor(response, fingerprint)) {
|
|
59642
|
-
return { output:
|
|
60011
|
+
return { output: untrustedOrigin ? untrustedDenial : "subagent spawn not approved by the user; not executed", isError: true };
|
|
59643
60012
|
}
|
|
59644
60013
|
}
|
|
59645
60014
|
} else if (risk === "write") {
|
|
@@ -59670,7 +60039,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59670
60039
|
isError: true
|
|
59671
60040
|
};
|
|
59672
60041
|
}
|
|
59673
|
-
if (gated.decision === "auto") {
|
|
60042
|
+
if (gated.decision === "auto" && !untrustedOrigin) {
|
|
59674
60043
|
onAutoApproved?.(call.name, call.input, { destructive, credentials });
|
|
59675
60044
|
} else {
|
|
59676
60045
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
@@ -59679,13 +60048,14 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59679
60048
|
destructive,
|
|
59680
60049
|
...credentials ? { credentials } : {},
|
|
59681
60050
|
...card !== undefined ? { alwaysAsk: true, card } : {},
|
|
59682
|
-
...gated.hookAsked ? { hookAsk: true } : {}
|
|
60051
|
+
...gated.hookAsked ? { hookAsk: true } : {},
|
|
60052
|
+
...untrustedOrigin ? { untrustedOrigin: true } : {}
|
|
59683
60053
|
});
|
|
59684
60054
|
if (!isApprovalFor(response, fingerprint)) {
|
|
59685
60055
|
if (confirmationToken !== undefined)
|
|
59686
60056
|
tool.confirmationDeclined?.(confirmationToken);
|
|
59687
60057
|
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`,
|
|
60058
|
+
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
60059
|
isError: true
|
|
59690
60060
|
};
|
|
59691
60061
|
}
|
|
@@ -59698,11 +60068,11 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
59698
60068
|
if (gated.decision === "deny") {
|
|
59699
60069
|
return { output: gated.denyMessage ?? `tool "${call.name}" refused by a policy hook`, isError: true };
|
|
59700
60070
|
}
|
|
59701
|
-
if (gated.decision === "ask") {
|
|
60071
|
+
if (gated.decision === "ask" || untrustedOrigin) {
|
|
59702
60072
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
59703
|
-
const response = requestApproval === undefined ? false : await requestApproval(call.name, call.input, { fingerprint, destructive: false, hookAsk: true });
|
|
60073
|
+
const response = requestApproval === undefined ? false : await requestApproval(call.name, call.input, { fingerprint, destructive: false, ...gated.hookAsked ? { hookAsk: true } : {}, ...untrustedOrigin ? { untrustedOrigin: true } : {} });
|
|
59704
60074
|
if (!isApprovalFor(response, fingerprint)) {
|
|
59705
|
-
return { output: `${call.name} not approved by the user; not executed`, isError: true };
|
|
60075
|
+
return { output: untrustedOrigin ? untrustedDenial : `${call.name} not approved by the user; not executed`, isError: true };
|
|
59706
60076
|
}
|
|
59707
60077
|
}
|
|
59708
60078
|
} else {
|
|
@@ -112158,6 +112528,7 @@ function resolveFqn(catalog, fqn) {
|
|
|
112158
112528
|
}
|
|
112159
112529
|
|
|
112160
112530
|
// src/mcp-servers/http-headers.ts
|
|
112531
|
+
init_service4();
|
|
112161
112532
|
var VAR_PATTERN2 = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}/;
|
|
112162
112533
|
var VAR_PATTERN_ALL = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
112163
112534
|
function unresolvedVariables(raw, env) {
|
|
@@ -112275,6 +112646,14 @@ function elideCredentialText(raw) {
|
|
|
112275
112646
|
const trimmed = query === -1 ? raw : `${raw.slice(0, query)}?\u2026`;
|
|
112276
112647
|
return trimmed.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:)?\/\/[^/@]*@/, (_m, scheme) => `${scheme ?? ""}//\u2026@`);
|
|
112277
112648
|
}
|
|
112649
|
+
var MASKED_SEGMENT_MIN_LENGTH = 16;
|
|
112650
|
+
function maskSecretShapedPathSegments(pathname) {
|
|
112651
|
+
return pathname.split("/").map((segment) => {
|
|
112652
|
+
if (segment.length < MASKED_SEGMENT_MIN_LENGTH)
|
|
112653
|
+
return segment;
|
|
112654
|
+
return detectSecrets(segment).length > 0 || looksSecretShaped(segment) ? "\u2026" : segment;
|
|
112655
|
+
}).join("/");
|
|
112656
|
+
}
|
|
112278
112657
|
function displayUrl(raw) {
|
|
112279
112658
|
if (raw === undefined)
|
|
112280
112659
|
return "";
|
|
@@ -112284,7 +112663,8 @@ function displayUrl(raw) {
|
|
|
112284
112663
|
const parsed = new URL(raw);
|
|
112285
112664
|
const query = parsed.search === "" ? "" : "?\u2026";
|
|
112286
112665
|
const auth = parsed.username === "" ? "" : "\u2026@";
|
|
112287
|
-
|
|
112666
|
+
const pathname = maskSecretShapedPathSegments(parsed.pathname);
|
|
112667
|
+
return `${parsed.protocol}//${auth}${parsed.host}${pathname}${query}`;
|
|
112288
112668
|
} catch {
|
|
112289
112669
|
return elideCredentialText(raw);
|
|
112290
112670
|
}
|
|
@@ -113695,139 +114075,10 @@ function fileForScope(scope, targets) {
|
|
|
113695
114075
|
return projectConfigFile(targets.projectRoot);
|
|
113696
114076
|
}
|
|
113697
114077
|
|
|
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
114078
|
// src/mcp-servers/spawn-env.ts
|
|
114079
|
+
init_service4();
|
|
113711
114080
|
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
|
-
}
|
|
114081
|
+
init_service4();
|
|
113831
114082
|
function buildMcpChildEnv(input) {
|
|
113832
114083
|
const saved = input.savedCredentialKeys ?? new Set([...savedCredentialEnvKeys(), ...declaredCredentialEnvKeys(input.configDir)]);
|
|
113833
114084
|
const env = {};
|
|
@@ -128843,6 +129094,70 @@ function createSystemWebWorkerRunner() {
|
|
|
128843
129094
|
return new SystemWebWorkerRunner({ workspace: process.cwd(), home: homedir10() });
|
|
128844
129095
|
}
|
|
128845
129096
|
|
|
129097
|
+
// src/harness/web/outbound-secret.ts
|
|
129098
|
+
init_service4();
|
|
129099
|
+
function safeDecodeComponent(value) {
|
|
129100
|
+
try {
|
|
129101
|
+
return decodeURIComponent(value);
|
|
129102
|
+
} catch {
|
|
129103
|
+
return value;
|
|
129104
|
+
}
|
|
129105
|
+
}
|
|
129106
|
+
function outboundUrlComponents(text) {
|
|
129107
|
+
let url;
|
|
129108
|
+
try {
|
|
129109
|
+
url = new URL(text);
|
|
129110
|
+
} catch {
|
|
129111
|
+
return [];
|
|
129112
|
+
}
|
|
129113
|
+
const components = [];
|
|
129114
|
+
for (const segment of url.pathname.split("/")) {
|
|
129115
|
+
if (segment.length > 0) {
|
|
129116
|
+
components.push(safeDecodeComponent(segment));
|
|
129117
|
+
}
|
|
129118
|
+
}
|
|
129119
|
+
for (const [, value] of url.searchParams) {
|
|
129120
|
+
if (value.length > 0) {
|
|
129121
|
+
components.push(value);
|
|
129122
|
+
}
|
|
129123
|
+
}
|
|
129124
|
+
if (url.hash.length > 1) {
|
|
129125
|
+
components.push(safeDecodeComponent(url.hash.slice(1)));
|
|
129126
|
+
}
|
|
129127
|
+
return components;
|
|
129128
|
+
}
|
|
129129
|
+
function anyComponentLooksSecret(text) {
|
|
129130
|
+
const entropyGateOpen = isEntropyBackendEnabled();
|
|
129131
|
+
for (const component of outboundUrlComponents(text)) {
|
|
129132
|
+
if (detectSecrets(component).length > 0) {
|
|
129133
|
+
return true;
|
|
129134
|
+
}
|
|
129135
|
+
if (entropyGateOpen && looksSecretShapedIn(component)) {
|
|
129136
|
+
return true;
|
|
129137
|
+
}
|
|
129138
|
+
}
|
|
129139
|
+
return false;
|
|
129140
|
+
}
|
|
129141
|
+
function containsOutboundSecret(text) {
|
|
129142
|
+
if (detectSecrets(text).length > 0) {
|
|
129143
|
+
return true;
|
|
129144
|
+
}
|
|
129145
|
+
if (isEntropyBackendEnabled() && detectEntropy(text).length > 0) {
|
|
129146
|
+
return true;
|
|
129147
|
+
}
|
|
129148
|
+
return anyComponentLooksSecret(text);
|
|
129149
|
+
}
|
|
129150
|
+
async function recordOutboundSecretFinding(cwd, tool, subject) {
|
|
129151
|
+
try {
|
|
129152
|
+
await appendIncident(cwd, {
|
|
129153
|
+
at: new Date().toISOString(),
|
|
129154
|
+
type: "egress-blocked",
|
|
129155
|
+
message: "outbound secret-shaped content",
|
|
129156
|
+
details: { policyId: "egress.outbound-secret", tool, subject }
|
|
129157
|
+
});
|
|
129158
|
+
} catch {}
|
|
129159
|
+
}
|
|
129160
|
+
|
|
128846
129161
|
// src/harness/tool/builtin/web-fetch-tool.ts
|
|
128847
129162
|
function transportFor(deps) {
|
|
128848
129163
|
if (deps.transport !== undefined)
|
|
@@ -128866,6 +129181,12 @@ function webFetchTool(deps = {}) {
|
|
|
128866
129181
|
if (typeof input.url !== "string") {
|
|
128867
129182
|
return { output: "web_fetch: url must be an absolute HTTPS URL without credentials", isError: true };
|
|
128868
129183
|
}
|
|
129184
|
+
if (containsOutboundSecret(input.url)) {
|
|
129185
|
+
if (deps.cwd !== undefined) {
|
|
129186
|
+
await recordOutboundSecretFinding(deps.cwd, "web_fetch", "url");
|
|
129187
|
+
}
|
|
129188
|
+
return { output: "web_fetch: outbound secret-shaped content", isError: true };
|
|
129189
|
+
}
|
|
128869
129190
|
const result = await transport.fetchPage({ url: input.url, providerId: "web_fetch" });
|
|
128870
129191
|
return result.ok ? { output: result.value.text, isError: false, untrusted: true } : { output: `web_fetch: ${result.reason}`, isError: true };
|
|
128871
129192
|
}
|
|
@@ -128896,7 +129217,7 @@ ${result.canonicalUrl}`;
|
|
|
128896
129217
|
return lines.join(`
|
|
128897
129218
|
`).trim();
|
|
128898
129219
|
}
|
|
128899
|
-
function webSearchTool(service) {
|
|
129220
|
+
function webSearchTool(service, deps = {}) {
|
|
128900
129221
|
return {
|
|
128901
129222
|
definition: {
|
|
128902
129223
|
name: "web_search",
|
|
@@ -128908,7 +129229,14 @@ function webSearchTool(service) {
|
|
|
128908
129229
|
if (typeof input.query !== "string" || input.query.trim().length === 0) {
|
|
128909
129230
|
return { output: "web_search: query must be a non-empty string", isError: true };
|
|
128910
129231
|
}
|
|
128911
|
-
const
|
|
129232
|
+
const query = input.query.trim();
|
|
129233
|
+
if (containsOutboundSecret(query)) {
|
|
129234
|
+
if (deps.cwd !== undefined) {
|
|
129235
|
+
await recordOutboundSecretFinding(deps.cwd, "web_search", "query");
|
|
129236
|
+
}
|
|
129237
|
+
return { output: "web_search: outbound secret-shaped content", isError: true };
|
|
129238
|
+
}
|
|
129239
|
+
const response = await service.search(query);
|
|
128912
129240
|
if (!response.ok) {
|
|
128913
129241
|
return {
|
|
128914
129242
|
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 +129732,8 @@ function buildInteractiveAgentTools(input) {
|
|
|
129404
129732
|
const built = [
|
|
129405
129733
|
...builtinReadOnlyTools(input.cwd),
|
|
129406
129734
|
...metaprojectTools,
|
|
129407
|
-
webFetchTool(),
|
|
129408
|
-
webSearchTool(input.searchController),
|
|
129735
|
+
webFetchTool({ cwd: input.cwd }),
|
|
129736
|
+
webSearchTool(input.searchController, { cwd: input.cwd }),
|
|
129409
129737
|
shellExecTool(input.cwd, undefined, jobRegistry),
|
|
129410
129738
|
...jobRegistry !== undefined ? [
|
|
129411
129739
|
shellTaskOutputTool(jobRegistry, { observer: "main" }),
|
|
@@ -129878,12 +130206,13 @@ function evaluateShellApproval(input) {
|
|
|
129878
130206
|
const publishLease = input.meta?.publishLease === true;
|
|
129879
130207
|
const publishLeaseDetail = input.meta?.publishLeaseDetail;
|
|
129880
130208
|
const hookAsk = input.meta?.hookAsk === true;
|
|
130209
|
+
const untrustedOrigin = input.meta?.untrustedOrigin === true;
|
|
129881
130210
|
const audit = io.loadAudit();
|
|
129882
130211
|
for (const pattern of audit.permissions.allow) {
|
|
129883
130212
|
input.sessionAllow.add(pattern);
|
|
129884
130213
|
}
|
|
129885
130214
|
const tampered = io.fingerprint() !== input.fingerprintAtStart;
|
|
129886
|
-
const autoApprove = !destructive && !credentials && !sacReviewConfirmation && !publishLease && !hookAsk && isShellCommandAllowed(command, [...input.sessionAllow]);
|
|
130215
|
+
const autoApprove = !destructive && !credentials && !sacReviewConfirmation && !publishLease && !hookAsk && !untrustedOrigin && isShellCommandAllowed(command, [...input.sessionAllow]);
|
|
129887
130216
|
return {
|
|
129888
130217
|
command,
|
|
129889
130218
|
destructive,
|
|
@@ -129892,6 +130221,7 @@ function evaluateShellApproval(input) {
|
|
|
129892
130221
|
publishLease,
|
|
129893
130222
|
...publishLeaseDetail !== undefined ? { publishLeaseDetail } : {},
|
|
129894
130223
|
hookAsk,
|
|
130224
|
+
untrustedOrigin,
|
|
129895
130225
|
autoApprove,
|
|
129896
130226
|
rejected: audit.rejected,
|
|
129897
130227
|
tampered
|
|
@@ -129925,6 +130255,9 @@ function formatShellApprovalHints(evaled) {
|
|
|
129925
130255
|
if (evaled.hookAsk) {
|
|
129926
130256
|
lines.push("a policy hook asked about this call \u2014 will not be remembered");
|
|
129927
130257
|
}
|
|
130258
|
+
if (evaled.untrustedOrigin) {
|
|
130259
|
+
lines.push("follows untrusted external content \u2014 will not be remembered");
|
|
130260
|
+
}
|
|
129928
130261
|
if (evaled.publishLease) {
|
|
129929
130262
|
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
130263
|
}
|
|
@@ -131998,6 +132331,12 @@ function readRuntimeBlock(dispatch) {
|
|
|
131998
132331
|
return raw;
|
|
131999
132332
|
}
|
|
132000
132333
|
|
|
132334
|
+
// src/harness/external/env.ts
|
|
132335
|
+
init_service4();
|
|
132336
|
+
|
|
132337
|
+
// src/harness/external/env-deny.ts
|
|
132338
|
+
init_service4();
|
|
132339
|
+
|
|
132001
132340
|
// src/harness/external/env.ts
|
|
132002
132341
|
var EXTERNAL_RUNTIME_CREDENTIAL_ALLOW = {
|
|
132003
132342
|
"codex-cli": ["OPENAI_API_KEY"],
|
|
@@ -132005,17 +132344,18 @@ var EXTERNAL_RUNTIME_CREDENTIAL_ALLOW = {
|
|
|
132005
132344
|
};
|
|
132006
132345
|
var ENV_EXTERNAL_DEPTH = "KERYX_EXTERNAL_DEPTH";
|
|
132007
132346
|
function buildExternalChildEnv(input) {
|
|
132008
|
-
const denied = new Set(EXTERNAL_ENV_DENY);
|
|
132347
|
+
const denied = new Set(EXTERNAL_ENV_DENY.map((name) => name.toUpperCase()));
|
|
132009
132348
|
const allowedForThisRuntime = new Set((EXTERNAL_RUNTIME_CREDENTIAL_ALLOW[input.runtimeId] ?? []).map((name) => name.toUpperCase()));
|
|
132010
132349
|
const env = {};
|
|
132011
132350
|
for (const [key, value] of Object.entries(input.parent)) {
|
|
132012
132351
|
if (value === undefined)
|
|
132013
132352
|
continue;
|
|
132014
|
-
|
|
132353
|
+
const upper = key.toUpperCase();
|
|
132354
|
+
if (denied.has(upper))
|
|
132015
132355
|
continue;
|
|
132016
|
-
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) =>
|
|
132356
|
+
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix.toUpperCase())))
|
|
132017
132357
|
continue;
|
|
132018
|
-
if (!allowedForThisRuntime.has(
|
|
132358
|
+
if (!allowedForThisRuntime.has(upper) && isDeniedForMcpChild(key, value))
|
|
132019
132359
|
continue;
|
|
132020
132360
|
env[key] = value;
|
|
132021
132361
|
}
|
|
@@ -148088,7 +148428,7 @@ function ollamaShowAllowed(baseUrl) {
|
|
|
148088
148428
|
} catch {
|
|
148089
148429
|
return false;
|
|
148090
148430
|
}
|
|
148091
|
-
if (isPrivateEgressHost(host) && !
|
|
148431
|
+
if (isPrivateEgressHost(host) && !isLoopbackHost2(host)) {
|
|
148092
148432
|
return false;
|
|
148093
148433
|
}
|
|
148094
148434
|
return true;
|
|
@@ -155602,8 +155942,8 @@ async function recordTurnTaskCostBestEffort(input) {
|
|
|
155602
155942
|
}, input.userConfigDir);
|
|
155603
155943
|
} catch {}
|
|
155604
155944
|
}
|
|
155605
|
-
function shouldAutoApproveReadOnlySpawn(mode, hookAsk) {
|
|
155606
|
-
return mode === "read_only" && hookAsk !== true;
|
|
155945
|
+
function shouldAutoApproveReadOnlySpawn(mode, hookAsk, untrustedOrigin) {
|
|
155946
|
+
return mode === "read_only" && hookAsk !== true && untrustedOrigin !== true;
|
|
155607
155947
|
}
|
|
155608
155948
|
function isUserPromptApprovalCard(tool) {
|
|
155609
155949
|
return tool === "user_prompt";
|
|
@@ -157819,7 +158159,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
157819
158159
|
}
|
|
157820
158160
|
}
|
|
157821
158161
|
} catch {}
|
|
157822
|
-
if (shouldAutoApproveReadOnlySpawn(mode, meta?.hookAsk)) {
|
|
158162
|
+
if (shouldAutoApproveReadOnlySpawn(mode, meta?.hookAsk, meta?.untrustedOrigin)) {
|
|
157823
158163
|
transcript.add(new otui.TextRenderable(r, {
|
|
157824
158164
|
id: `ap${uid++}`,
|
|
157825
158165
|
content: otui.t`${roleChunk(otui, "accent", "\u25C7 subagent auto-approved")} ${dimChunk(otui, `mode=read_only (no shell) \xB7 ${taskPreview}`)}`
|
|
@@ -157829,7 +158169,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
157829
158169
|
chrome.hideMenu();
|
|
157830
158170
|
setMainAgent("blocked", "approval");
|
|
157831
158171
|
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?",
|
|
158172
|
+
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
158173
|
subtitle: taskPreview,
|
|
157834
158174
|
cancelId: "deny",
|
|
157835
158175
|
onOpen: () => chrome.blurComposer(),
|
|
@@ -158024,7 +158364,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
158024
158364
|
const choice = await chrome.withOverlay(() => pickShellApproval(otui, r, chrome.dock, cmd, approvalContext, destructive, meta?.credentials === true, {
|
|
158025
158365
|
onOpen: () => chrome.blurComposer(),
|
|
158026
158366
|
signal: foregroundOperation.signal
|
|
158027
|
-
}, ev.publishLease, ev.publishLeaseDetail, ev.hookAsk));
|
|
158367
|
+
}, ev.publishLease, ev.publishLeaseDetail, ev.hookAsk || ev.untrustedOrigin));
|
|
158028
158368
|
input.focus();
|
|
158029
158369
|
if (choice === "deny") {
|
|
158030
158370
|
transcript.add(new otui.TextRenderable(r, {
|
|
@@ -162300,7 +162640,7 @@ ${indentBlock(style.dim(context), GUTTER)}`);
|
|
|
162300
162640
|
out(`${GUTTER}${style.yellow(hint)}
|
|
162301
162641
|
`);
|
|
162302
162642
|
}
|
|
162303
|
-
const rememberable = !evaled.destructive && !evaled.credentials && !evaled.sacReviewConfirmation && !evaled.publishLease && !evaled.hookAsk;
|
|
162643
|
+
const rememberable = !evaled.destructive && !evaled.credentials && !evaled.sacReviewConfirmation && !evaled.publishLease && !evaled.hookAsk && !evaled.untrustedOrigin;
|
|
162304
162644
|
const prompt = rememberable ? "[y/N/A=always] " : "[y/N] ";
|
|
162305
162645
|
out(`
|
|
162306
162646
|
${GUTTER}${style.yellow(`Run: ${evaled.command}`)} ${style.dim(prompt)}`);
|