@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/core.js
CHANGED
|
@@ -4028,6 +4028,333 @@ var init_config2 = __esm(() => {
|
|
|
4028
4028
|
]);
|
|
4029
4029
|
});
|
|
4030
4030
|
|
|
4031
|
+
// src/security/detect/entropy.ts
|
|
4032
|
+
function isHexBlob(value) {
|
|
4033
|
+
return HEX_BLOB.test(value) && /[a-f]/i.test(value);
|
|
4034
|
+
}
|
|
4035
|
+
function isAllowShapedValue(value) {
|
|
4036
|
+
return FULL_GIT_SHA_RE.test(value) || UUID_RE.test(value) || INTEGRITY_RE.test(value);
|
|
4037
|
+
}
|
|
4038
|
+
function lastLineOf(before) {
|
|
4039
|
+
const newline = before.lastIndexOf(`
|
|
4040
|
+
`);
|
|
4041
|
+
return newline < 0 ? before : before.slice(newline + 1);
|
|
4042
|
+
}
|
|
4043
|
+
function isPlainCasedWord(letters) {
|
|
4044
|
+
return /^[a-z]+$/.test(letters) || /^[A-Z]+$/.test(letters) || /^[A-Z][a-z]*$/.test(letters);
|
|
4045
|
+
}
|
|
4046
|
+
function isSingleTransitionTag(segment) {
|
|
4047
|
+
const letterThenDigit = /^([A-Za-z]+)([0-9]+)$/.exec(segment);
|
|
4048
|
+
if (letterThenDigit !== null) {
|
|
4049
|
+
const letters = letterThenDigit[1];
|
|
4050
|
+
return letters.length <= TAG_MAX_LETTER_RUN && isPlainCasedWord(letters);
|
|
4051
|
+
}
|
|
4052
|
+
const digitThenLetter = /^([0-9]+)([A-Za-z]+)$/.exec(segment);
|
|
4053
|
+
if (digitThenLetter !== null) {
|
|
4054
|
+
const letters = digitThenLetter[2];
|
|
4055
|
+
return letters.length <= TAG_MAX_LETTER_RUN && isPlainCasedWord(letters);
|
|
4056
|
+
}
|
|
4057
|
+
return false;
|
|
4058
|
+
}
|
|
4059
|
+
function isSlugHexTail(segment) {
|
|
4060
|
+
return SLUG_HEX_TAIL_RE.test(segment);
|
|
4061
|
+
}
|
|
4062
|
+
function isWordSlug(value) {
|
|
4063
|
+
const segments = value.split(/[-_]/);
|
|
4064
|
+
if (segments.length < 3) {
|
|
4065
|
+
return false;
|
|
4066
|
+
}
|
|
4067
|
+
let words = 0;
|
|
4068
|
+
for (let i = 0;i < segments.length; i += 1) {
|
|
4069
|
+
const segment = segments[i];
|
|
4070
|
+
const isDigits = /^[0-9]+$/.test(segment);
|
|
4071
|
+
if (isDigits) {
|
|
4072
|
+
continue;
|
|
4073
|
+
}
|
|
4074
|
+
if (isPlainCasedWord(segment)) {
|
|
4075
|
+
if (segment.length >= 3) {
|
|
4076
|
+
words += 1;
|
|
4077
|
+
}
|
|
4078
|
+
continue;
|
|
4079
|
+
}
|
|
4080
|
+
if (isSingleTransitionTag(segment)) {
|
|
4081
|
+
continue;
|
|
4082
|
+
}
|
|
4083
|
+
if (i === segments.length - 1 && isSlugHexTail(segment)) {
|
|
4084
|
+
continue;
|
|
4085
|
+
}
|
|
4086
|
+
return false;
|
|
4087
|
+
}
|
|
4088
|
+
return words >= 1;
|
|
4089
|
+
}
|
|
4090
|
+
function shannonEntropy(value) {
|
|
4091
|
+
const counts = new Map;
|
|
4092
|
+
for (const ch of value) {
|
|
4093
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
4094
|
+
}
|
|
4095
|
+
let entropy = 0;
|
|
4096
|
+
for (const count of counts.values()) {
|
|
4097
|
+
const p = count / value.length;
|
|
4098
|
+
entropy -= p * Math.log2(p);
|
|
4099
|
+
}
|
|
4100
|
+
return entropy;
|
|
4101
|
+
}
|
|
4102
|
+
function bareShapeQualifies(head) {
|
|
4103
|
+
if (!/[0-9]/.test(head) && !/[+=]/.test(head)) {
|
|
4104
|
+
return { qualifies: false, entropy: 0 };
|
|
4105
|
+
}
|
|
4106
|
+
if (isWordSlug(head)) {
|
|
4107
|
+
return { qualifies: false, entropy: 0 };
|
|
4108
|
+
}
|
|
4109
|
+
const entropy = shannonEntropy(head);
|
|
4110
|
+
const hexBlob = isHexBlob(head);
|
|
4111
|
+
if (entropy < 3.6 && !hexBlob) {
|
|
4112
|
+
return { qualifies: false, entropy };
|
|
4113
|
+
}
|
|
4114
|
+
return { qualifies: true, entropy };
|
|
4115
|
+
}
|
|
4116
|
+
function shapeQualifiesAsSecret(value) {
|
|
4117
|
+
if (isAllowShapedValue(value)) {
|
|
4118
|
+
return { qualifies: false, entropy: 0 };
|
|
4119
|
+
}
|
|
4120
|
+
return bareShapeQualifies(value);
|
|
4121
|
+
}
|
|
4122
|
+
function looksSecretShaped(value) {
|
|
4123
|
+
return shapeQualifiesAsSecret(value).qualifies;
|
|
4124
|
+
}
|
|
4125
|
+
function buildMatch(start, end, value, entropy) {
|
|
4126
|
+
const confidence = Math.max(0.4, Math.min(0.7, 0.4 + (entropy - 3.6) * 0.21));
|
|
4127
|
+
return {
|
|
4128
|
+
category: "secret",
|
|
4129
|
+
policyId: "secrets.high-entropy",
|
|
4130
|
+
severity: "medium",
|
|
4131
|
+
confidence: Number(confidence.toFixed(2)),
|
|
4132
|
+
start,
|
|
4133
|
+
end,
|
|
4134
|
+
value,
|
|
4135
|
+
mask: "entropy",
|
|
4136
|
+
remediation: "Verify this high-entropy value is not a live credential."
|
|
4137
|
+
};
|
|
4138
|
+
}
|
|
4139
|
+
function findUrlSpans(content) {
|
|
4140
|
+
const spans = [];
|
|
4141
|
+
URL_SPAN_RE.lastIndex = 0;
|
|
4142
|
+
let m;
|
|
4143
|
+
while ((m = URL_SPAN_RE.exec(content)) !== null) {
|
|
4144
|
+
let raw = m[0];
|
|
4145
|
+
while (raw.length > 0 && URL_TRAILING_PUNCTUATION_RE.test(raw)) {
|
|
4146
|
+
raw = raw.slice(0, -1);
|
|
4147
|
+
}
|
|
4148
|
+
if (raw.length > 0) {
|
|
4149
|
+
spans.push({ start: m.index, end: m.index + raw.length, raw });
|
|
4150
|
+
}
|
|
4151
|
+
if (m.index === URL_SPAN_RE.lastIndex) {
|
|
4152
|
+
URL_SPAN_RE.lastIndex += 1;
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
return spans;
|
|
4156
|
+
}
|
|
4157
|
+
function urlComponentSpans(raw) {
|
|
4158
|
+
const spans = [];
|
|
4159
|
+
const schemeMatch = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.exec(raw);
|
|
4160
|
+
if (schemeMatch === null) {
|
|
4161
|
+
return spans;
|
|
4162
|
+
}
|
|
4163
|
+
let i = schemeMatch[0].length;
|
|
4164
|
+
while (i < raw.length && raw[i] !== "/" && raw[i] !== "?" && raw[i] !== "#") {
|
|
4165
|
+
i += 1;
|
|
4166
|
+
}
|
|
4167
|
+
let pathEnd = i;
|
|
4168
|
+
while (pathEnd < raw.length && raw[pathEnd] !== "?" && raw[pathEnd] !== "#") {
|
|
4169
|
+
pathEnd += 1;
|
|
4170
|
+
}
|
|
4171
|
+
let segStart = i;
|
|
4172
|
+
for (let j = i;j <= pathEnd; j += 1) {
|
|
4173
|
+
if (j === pathEnd || raw[j] === "/") {
|
|
4174
|
+
if (j > segStart) {
|
|
4175
|
+
spans.push({ start: segStart, end: j });
|
|
4176
|
+
}
|
|
4177
|
+
segStart = j + 1;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
let cursor = pathEnd;
|
|
4181
|
+
if (cursor < raw.length && raw[cursor] === "?") {
|
|
4182
|
+
let queryEnd = cursor + 1;
|
|
4183
|
+
while (queryEnd < raw.length && raw[queryEnd] !== "#") {
|
|
4184
|
+
queryEnd += 1;
|
|
4185
|
+
}
|
|
4186
|
+
let pairStart = cursor + 1;
|
|
4187
|
+
for (let j = cursor + 1;j <= queryEnd; j += 1) {
|
|
4188
|
+
if (j === queryEnd || raw[j] === "&") {
|
|
4189
|
+
const pair = raw.slice(pairStart, j);
|
|
4190
|
+
const eq = pair.indexOf("=");
|
|
4191
|
+
if (eq !== -1 && eq + 1 < pair.length) {
|
|
4192
|
+
spans.push({ start: pairStart + eq + 1, end: j });
|
|
4193
|
+
} else if (eq === -1 && pair.length > 0) {
|
|
4194
|
+
spans.push({ start: pairStart, end: j });
|
|
4195
|
+
}
|
|
4196
|
+
pairStart = j + 1;
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
cursor = queryEnd;
|
|
4200
|
+
}
|
|
4201
|
+
if (cursor < raw.length && raw[cursor] === "#" && cursor + 1 < raw.length) {
|
|
4202
|
+
spans.push({ start: cursor + 1, end: raw.length });
|
|
4203
|
+
}
|
|
4204
|
+
return spans;
|
|
4205
|
+
}
|
|
4206
|
+
function secretShapedCandidatesIn(text) {
|
|
4207
|
+
const normalized = text.replace(/\+/g, " ");
|
|
4208
|
+
const out = [];
|
|
4209
|
+
TOKEN.lastIndex = 0;
|
|
4210
|
+
let m;
|
|
4211
|
+
while ((m = TOKEN.exec(normalized)) !== null) {
|
|
4212
|
+
const value = m[0];
|
|
4213
|
+
if (value.length < URL_COMPONENT_MIN_LENGTH) {
|
|
4214
|
+
continue;
|
|
4215
|
+
}
|
|
4216
|
+
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
4217
|
+
const { qualifies, entropy } = shapeQualifiesAsSecret(head);
|
|
4218
|
+
if (!qualifies) {
|
|
4219
|
+
continue;
|
|
4220
|
+
}
|
|
4221
|
+
const start = m.index;
|
|
4222
|
+
const end = start + value.length;
|
|
4223
|
+
out.push({ start, end, value: text.slice(start, end), entropy });
|
|
4224
|
+
}
|
|
4225
|
+
return out;
|
|
4226
|
+
}
|
|
4227
|
+
function looksSecretShapedIn(text) {
|
|
4228
|
+
return secretShapedCandidatesIn(text).length > 0;
|
|
4229
|
+
}
|
|
4230
|
+
function labelledPieceQualifies(piece) {
|
|
4231
|
+
const base = bareShapeQualifies(piece);
|
|
4232
|
+
if (base.qualifies) {
|
|
4233
|
+
return { entropy: base.entropy };
|
|
4234
|
+
}
|
|
4235
|
+
if (isAllowShapedValue(piece)) {
|
|
4236
|
+
return { entropy: base.entropy };
|
|
4237
|
+
}
|
|
4238
|
+
return null;
|
|
4239
|
+
}
|
|
4240
|
+
function firstQualifyingSlashPiece(head) {
|
|
4241
|
+
let offset = 0;
|
|
4242
|
+
for (const piece of head.split("/")) {
|
|
4243
|
+
if (piece.length > 0) {
|
|
4244
|
+
const found = labelledPieceQualifies(piece);
|
|
4245
|
+
if (found !== null) {
|
|
4246
|
+
return { offset, length: piece.length, entropy: found.entropy };
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
offset += piece.length + 1;
|
|
4250
|
+
}
|
|
4251
|
+
return null;
|
|
4252
|
+
}
|
|
4253
|
+
function detectEntropy(content) {
|
|
4254
|
+
const matches = [];
|
|
4255
|
+
const urlSpans = findUrlSpans(content);
|
|
4256
|
+
for (const url of urlSpans) {
|
|
4257
|
+
for (const comp of urlComponentSpans(url.raw)) {
|
|
4258
|
+
const compText = url.raw.slice(comp.start, comp.end);
|
|
4259
|
+
for (const cand of secretShapedCandidatesIn(compText)) {
|
|
4260
|
+
const absStart = url.start + comp.start + cand.start;
|
|
4261
|
+
const absEnd = url.start + comp.start + cand.end;
|
|
4262
|
+
matches.push(buildMatch(absStart, absEnd, cand.value, cand.entropy));
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
LABEL_ASSIGNMENT_RE.lastIndex = 0;
|
|
4267
|
+
let am;
|
|
4268
|
+
while ((am = LABEL_ASSIGNMENT_RE.exec(content)) !== null) {
|
|
4269
|
+
const value = am[1];
|
|
4270
|
+
const end = am.index + am[0].length;
|
|
4271
|
+
const start = end - value.length;
|
|
4272
|
+
if (urlSpans.some((u) => start < u.end && end > u.start)) {
|
|
4273
|
+
continue;
|
|
4274
|
+
}
|
|
4275
|
+
const { qualifies, entropy } = bareShapeQualifies(value);
|
|
4276
|
+
if (qualifies) {
|
|
4277
|
+
matches.push(buildMatch(start, end, value, entropy));
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
TOKEN.lastIndex = 0;
|
|
4281
|
+
let m;
|
|
4282
|
+
while ((m = TOKEN.exec(content)) !== null) {
|
|
4283
|
+
const value = m[0];
|
|
4284
|
+
const start = m.index;
|
|
4285
|
+
const end = start + value.length;
|
|
4286
|
+
if (urlSpans.some((u) => start < u.end && end > u.start)) {
|
|
4287
|
+
continue;
|
|
4288
|
+
}
|
|
4289
|
+
if (matches.some((existing) => start < existing.end && end > existing.start)) {
|
|
4290
|
+
continue;
|
|
4291
|
+
}
|
|
4292
|
+
const before = lastLineOf(content.slice(Math.max(0, start - LABEL_WINDOW), start));
|
|
4293
|
+
if (!ADJACENT_LABEL.test(before)) {
|
|
4294
|
+
continue;
|
|
4295
|
+
}
|
|
4296
|
+
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
4297
|
+
if (head.includes("/")) {
|
|
4298
|
+
const piece = firstQualifyingSlashPiece(head);
|
|
4299
|
+
if (piece === null) {
|
|
4300
|
+
continue;
|
|
4301
|
+
}
|
|
4302
|
+
const pieceStart = start + piece.offset;
|
|
4303
|
+
const pieceEnd = pieceStart + piece.length;
|
|
4304
|
+
matches.push(buildMatch(pieceStart, pieceEnd, content.slice(pieceStart, pieceEnd), piece.entropy));
|
|
4305
|
+
continue;
|
|
4306
|
+
}
|
|
4307
|
+
const found = labelledPieceQualifies(head);
|
|
4308
|
+
if (found === null) {
|
|
4309
|
+
continue;
|
|
4310
|
+
}
|
|
4311
|
+
matches.push(buildMatch(start, end, value, found.entropy));
|
|
4312
|
+
}
|
|
4313
|
+
return matches.sort((a, b) => a.start - b.start);
|
|
4314
|
+
}
|
|
4315
|
+
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;
|
|
4316
|
+
var init_entropy = __esm(() => {
|
|
4317
|
+
ADJACENT_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*["']?\\s*[:=]?\\s*["']?$`);
|
|
4318
|
+
LABEL_ASSIGNMENT_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${SENSITIVE_LABEL_WORDS})[A-Za-z0-9_-]*"?\\s*=\\s*"?([A-Za-z0-9+/_=-]{6,})`, "gi");
|
|
4319
|
+
TOKEN = /[A-Za-z0-9+/=_-]{20,}(?:\.[A-Za-z0-9+/=_-]{6,})*/g;
|
|
4320
|
+
TOKEN_HEAD = /^[A-Za-z0-9+/=_-]+/;
|
|
4321
|
+
HEX_BLOB = /^[0-9a-f]{24,}$/i;
|
|
4322
|
+
FULL_GIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
4323
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4324
|
+
INTEGRITY_RE = /^(?:sha256|sha512)-[A-Za-z0-9+/]+=*$/;
|
|
4325
|
+
SLUG_HEX_TAIL_RE = /^[0-9a-f]{10,16}$/i;
|
|
4326
|
+
URL_SPAN_RE = /\bhttps?:\/\/[^\s<>"'\\]+/g;
|
|
4327
|
+
URL_TRAILING_PUNCTUATION_RE = /[.,;:!?)\]}]$/;
|
|
4328
|
+
});
|
|
4329
|
+
|
|
4330
|
+
// src/security/entropy-gate.ts
|
|
4331
|
+
function ensureLoading() {
|
|
4332
|
+
if (loadStarted) {
|
|
4333
|
+
return;
|
|
4334
|
+
}
|
|
4335
|
+
loadStarted = true;
|
|
4336
|
+
loadSecurityConfig(process.cwd()).then((config) => {
|
|
4337
|
+
cachedEnabled = config.backends.entropy.enabled;
|
|
4338
|
+
}).catch(() => {});
|
|
4339
|
+
}
|
|
4340
|
+
function isEntropyBackendEnabled() {
|
|
4341
|
+
ensureLoading();
|
|
4342
|
+
return cachedEnabled;
|
|
4343
|
+
}
|
|
4344
|
+
function setEntropyBackendEnabledForTests(enabled) {
|
|
4345
|
+
loadStarted = true;
|
|
4346
|
+
cachedEnabled = enabled;
|
|
4347
|
+
}
|
|
4348
|
+
function resetEntropyGateForTests() {
|
|
4349
|
+
loadStarted = false;
|
|
4350
|
+
cachedEnabled = DEFAULT_SECURITY_CONFIG.backends.entropy.enabled;
|
|
4351
|
+
}
|
|
4352
|
+
var cachedEnabled, loadStarted = false;
|
|
4353
|
+
var init_entropy_gate = __esm(() => {
|
|
4354
|
+
init_config2();
|
|
4355
|
+
cachedEnabled = DEFAULT_SECURITY_CONFIG.backends.entropy.enabled;
|
|
4356
|
+
});
|
|
4357
|
+
|
|
4031
4358
|
// src/security/redact.ts
|
|
4032
4359
|
import path17 from "path";
|
|
4033
4360
|
import { createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
@@ -4097,7 +4424,12 @@ function redactSensitiveText(text) {
|
|
|
4097
4424
|
if (text.length === 0) {
|
|
4098
4425
|
return text;
|
|
4099
4426
|
}
|
|
4100
|
-
const matches = [
|
|
4427
|
+
const matches = [
|
|
4428
|
+
...detectSecrets(text),
|
|
4429
|
+
...detectPii(text),
|
|
4430
|
+
...detectExfil(text),
|
|
4431
|
+
...isEntropyBackendEnabled() ? detectEntropy(text) : []
|
|
4432
|
+
];
|
|
4101
4433
|
if (matches.length === 0) {
|
|
4102
4434
|
return text;
|
|
4103
4435
|
}
|
|
@@ -4134,6 +4466,8 @@ var init_redact = __esm(() => {
|
|
|
4134
4466
|
init_secrets();
|
|
4135
4467
|
init_pii();
|
|
4136
4468
|
init_exfil();
|
|
4469
|
+
init_entropy();
|
|
4470
|
+
init_entropy_gate();
|
|
4137
4471
|
});
|
|
4138
4472
|
|
|
4139
4473
|
// src/security/output-validation.ts
|
|
@@ -4874,102 +5208,33 @@ var init_seam = __esm(() => {
|
|
|
4874
5208
|
init_warn_once();
|
|
4875
5209
|
});
|
|
4876
5210
|
|
|
4877
|
-
// src/security/detect/
|
|
4878
|
-
function
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
function isWordSlug(value) {
|
|
4884
|
-
const segments = value.split(/[-_]/);
|
|
4885
|
-
if (segments.length < 3) {
|
|
4886
|
-
return false;
|
|
4887
|
-
}
|
|
4888
|
-
let words = 0;
|
|
4889
|
-
for (const segment of segments) {
|
|
4890
|
-
const isAlpha = /^[A-Za-z]+$/.test(segment);
|
|
4891
|
-
const isDigits = /^[0-9]+$/.test(segment);
|
|
4892
|
-
if (!isAlpha && !isDigits) {
|
|
4893
|
-
return false;
|
|
4894
|
-
}
|
|
4895
|
-
if (isAlpha && segment.length >= 3) {
|
|
4896
|
-
words += 1;
|
|
4897
|
-
}
|
|
4898
|
-
}
|
|
4899
|
-
return words >= 2;
|
|
4900
|
-
}
|
|
4901
|
-
function shannonEntropy(value) {
|
|
4902
|
-
const counts = new Map;
|
|
4903
|
-
for (const ch of value) {
|
|
4904
|
-
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
4905
|
-
}
|
|
4906
|
-
let entropy = 0;
|
|
4907
|
-
for (const count of counts.values()) {
|
|
4908
|
-
const p = count / value.length;
|
|
4909
|
-
entropy -= p * Math.log2(p);
|
|
4910
|
-
}
|
|
4911
|
-
return entropy;
|
|
4912
|
-
}
|
|
4913
|
-
function detectEntropy(content) {
|
|
4914
|
-
const matches = [];
|
|
4915
|
-
TOKEN.lastIndex = 0;
|
|
4916
|
-
let m;
|
|
4917
|
-
while ((m = TOKEN.exec(content)) !== null) {
|
|
4918
|
-
const value = m[0];
|
|
4919
|
-
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
4920
|
-
if (!/[0-9]/.test(head) && !/[+=]/.test(head)) {
|
|
4921
|
-
continue;
|
|
4922
|
-
}
|
|
4923
|
-
if (isWordSlug(head)) {
|
|
4924
|
-
continue;
|
|
4925
|
-
}
|
|
4926
|
-
const entropy = shannonEntropy(head);
|
|
4927
|
-
const hexBlob = HEX_BLOB.test(head);
|
|
4928
|
-
if (entropy < 3.6 && !hexBlob) {
|
|
4929
|
-
continue;
|
|
4930
|
-
}
|
|
4931
|
-
const before = lastLineOf(content.slice(Math.max(0, m.index - LABEL_WINDOW), m.index));
|
|
4932
|
-
if (!SENSITIVE_LABEL.test(before)) {
|
|
4933
|
-
continue;
|
|
4934
|
-
}
|
|
4935
|
-
const confidence = Math.max(0.4, Math.min(0.7, 0.4 + (entropy - 3.6) * 0.21));
|
|
4936
|
-
matches.push({
|
|
4937
|
-
category: "secret",
|
|
4938
|
-
policyId: "secrets.high-entropy",
|
|
4939
|
-
severity: "medium",
|
|
4940
|
-
confidence: Number(confidence.toFixed(2)),
|
|
4941
|
-
start: m.index,
|
|
4942
|
-
end: m.index + value.length,
|
|
4943
|
-
value,
|
|
4944
|
-
mask: "secret",
|
|
4945
|
-
remediation: "Verify this high-entropy value is not a live credential."
|
|
4946
|
-
});
|
|
5211
|
+
// src/security/detect/injection.ts
|
|
5212
|
+
function foldForMatching(content) {
|
|
5213
|
+
let folded = "";
|
|
5214
|
+
for (let i = 0;i < content.length; i += 1) {
|
|
5215
|
+
const ch = content[i];
|
|
5216
|
+
folded += CONFUSABLES[ch] ?? ch;
|
|
4947
5217
|
}
|
|
4948
|
-
|
|
5218
|
+
const nfkc = folded.normalize("NFKC");
|
|
5219
|
+
return nfkc.length === folded.length ? nfkc : folded;
|
|
4949
5220
|
}
|
|
4950
|
-
var SENSITIVE_LABEL, TOKEN, TOKEN_HEAD, LABEL_WINDOW = 40, HEX_BLOB;
|
|
4951
|
-
var init_entropy = __esm(() => {
|
|
4952
|
-
SENSITIVE_LABEL = /(key|secret|token|password|passwd|api|credential|auth)/i;
|
|
4953
|
-
TOKEN = /[A-Za-z0-9+/=_-]{20,}(?:\.[A-Za-z0-9+/=_-]{6,})*/g;
|
|
4954
|
-
TOKEN_HEAD = /^[A-Za-z0-9+/=_-]+/;
|
|
4955
|
-
HEX_BLOB = /^[0-9a-f]{24,}$/i;
|
|
4956
|
-
});
|
|
4957
|
-
|
|
4958
|
-
// src/security/detect/injection.ts
|
|
4959
5221
|
function detectInjection(content) {
|
|
4960
5222
|
const matches = [];
|
|
5223
|
+
const folded = foldForMatching(content);
|
|
4961
5224
|
for (const pattern of PATTERNS) {
|
|
4962
5225
|
pattern.regex.lastIndex = 0;
|
|
4963
5226
|
let m;
|
|
4964
|
-
while ((m = pattern.regex.exec(
|
|
5227
|
+
while ((m = pattern.regex.exec(folded)) !== null) {
|
|
5228
|
+
const start = Math.min(m.index, content.length);
|
|
5229
|
+
const end = Math.min(m.index + m[0].length, content.length);
|
|
4965
5230
|
matches.push({
|
|
4966
5231
|
category: "prompt-injection",
|
|
4967
5232
|
policyId: pattern.policyId,
|
|
4968
5233
|
severity: "low",
|
|
4969
5234
|
confidence: pattern.confidence,
|
|
4970
|
-
start
|
|
4971
|
-
end
|
|
4972
|
-
value:
|
|
5235
|
+
start,
|
|
5236
|
+
end,
|
|
5237
|
+
value: content.slice(start, end),
|
|
4973
5238
|
remediation: "Treat external content as data, not instruction; require human review."
|
|
4974
5239
|
});
|
|
4975
5240
|
if (m.index === pattern.regex.lastIndex) {
|
|
@@ -4979,27 +5244,69 @@ function detectInjection(content) {
|
|
|
4979
5244
|
}
|
|
4980
5245
|
return matches;
|
|
4981
5246
|
}
|
|
4982
|
-
var PATTERNS;
|
|
5247
|
+
var CONFUSABLES, PATTERNS;
|
|
4983
5248
|
var init_injection = __esm(() => {
|
|
5249
|
+
CONFUSABLES = {
|
|
5250
|
+
"\u0430": "a",
|
|
5251
|
+
"\u0410": "A",
|
|
5252
|
+
"\u0435": "e",
|
|
5253
|
+
"\u0415": "E",
|
|
5254
|
+
"\u043E": "o",
|
|
5255
|
+
"\u041E": "O",
|
|
5256
|
+
"\u0440": "p",
|
|
5257
|
+
"\u0420": "P",
|
|
5258
|
+
"\u0441": "c",
|
|
5259
|
+
"\u0421": "C",
|
|
5260
|
+
"\u0445": "x",
|
|
5261
|
+
"\u0425": "X",
|
|
5262
|
+
"\u0443": "y",
|
|
5263
|
+
"\u0423": "Y",
|
|
5264
|
+
"\u0455": "s",
|
|
5265
|
+
"\u0405": "S",
|
|
5266
|
+
"\u0456": "o",
|
|
5267
|
+
"\u0406": "O",
|
|
5268
|
+
"\u0458": "j",
|
|
5269
|
+
"\u0408": "J",
|
|
5270
|
+
"\u03B1": "a",
|
|
5271
|
+
"\u0391": "A",
|
|
5272
|
+
"\u03B2": "b",
|
|
5273
|
+
"\u0392": "B",
|
|
5274
|
+
"\u03B5": "e",
|
|
5275
|
+
"\u0395": "E",
|
|
5276
|
+
"\u03B9": "i",
|
|
5277
|
+
"\u0399": "I",
|
|
5278
|
+
"\u03BA": "k",
|
|
5279
|
+
"\u039A": "K",
|
|
5280
|
+
"\u03BF": "o",
|
|
5281
|
+
"\u039F": "O",
|
|
5282
|
+
"\u03C1": "p",
|
|
5283
|
+
"\u03A1": "P",
|
|
5284
|
+
"\u03C4": "t",
|
|
5285
|
+
"\u03A4": "T",
|
|
5286
|
+
"\u03C5": "u",
|
|
5287
|
+
"\u03A5": "Y",
|
|
5288
|
+
"\u03C7": "x",
|
|
5289
|
+
"\u03A7": "X"
|
|
5290
|
+
};
|
|
4984
5291
|
PATTERNS = [
|
|
4985
5292
|
{
|
|
4986
5293
|
policyId: "prompt-injection.ignore-instructions",
|
|
4987
|
-
regex: /\b(ignore|disregard|forget|override)\b[
|
|
5294
|
+
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,
|
|
4988
5295
|
confidence: 0.4
|
|
4989
5296
|
},
|
|
4990
5297
|
{
|
|
4991
5298
|
policyId: "prompt-injection.reveal-context",
|
|
4992
|
-
regex: /\b(reveal|show|print|expose|leak|dump|repeat)\b[
|
|
5299
|
+
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,
|
|
4993
5300
|
confidence: 0.45
|
|
4994
5301
|
},
|
|
4995
5302
|
{
|
|
4996
5303
|
policyId: "prompt-injection.role-override",
|
|
4997
|
-
regex: /\b(you\s+are\s+now|from\s+now\s+on|act\s+as|pretend\s+to\s+be)\b[
|
|
5304
|
+
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,
|
|
4998
5305
|
confidence: 0.4
|
|
4999
5306
|
},
|
|
5000
5307
|
{
|
|
5001
5308
|
policyId: "prompt-injection.priority-override",
|
|
5002
|
-
regex: /\b(treat|follow|obey)\b[
|
|
5309
|
+
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,
|
|
5003
5310
|
confidence: 0.35
|
|
5004
5311
|
}
|
|
5005
5312
|
];
|
|
@@ -6149,6 +6456,140 @@ var init_read_source = __esm(() => {
|
|
|
6149
6456
|
init_contained_path();
|
|
6150
6457
|
});
|
|
6151
6458
|
|
|
6459
|
+
// src/security/credential-shape.ts
|
|
6460
|
+
function isDeniedForMcpChild(name, value) {
|
|
6461
|
+
if (value !== undefined && CREDENTIAL_IN_VALUE_RE.test(value))
|
|
6462
|
+
return true;
|
|
6463
|
+
const upper = name.toUpperCase();
|
|
6464
|
+
if (MCP_ENV_ALLOW_EXACT.includes(upper))
|
|
6465
|
+
return false;
|
|
6466
|
+
const conditional = VALUE_DEPENDENT[upper];
|
|
6467
|
+
if (conditional !== undefined) {
|
|
6468
|
+
return value === undefined ? true : conditional(value);
|
|
6469
|
+
}
|
|
6470
|
+
if (EXTERNAL_ENV_DENY.some((denied) => denied.toUpperCase() === upper))
|
|
6471
|
+
return true;
|
|
6472
|
+
if (MCP_ENV_DENY.includes(upper))
|
|
6473
|
+
return true;
|
|
6474
|
+
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix.toUpperCase())))
|
|
6475
|
+
return true;
|
|
6476
|
+
if (MCP_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix)))
|
|
6477
|
+
return true;
|
|
6478
|
+
return SECRET_SEGMENT_RE.test(upper) || SECRET_SUBSTRING_RE.test(upper) || GLUED_SECRET_RE.test(upper) || CONNECTION_STRING_RE.test(upper);
|
|
6479
|
+
}
|
|
6480
|
+
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;
|
|
6481
|
+
var init_credential_shape = __esm(() => {
|
|
6482
|
+
EXTERNAL_ENV_DENY = [
|
|
6483
|
+
"ANTHROPIC_API_KEY",
|
|
6484
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
6485
|
+
"ANTHROPIC_BASE_URL",
|
|
6486
|
+
"ANTHROPIC_MODEL",
|
|
6487
|
+
"CLAUDE_CONFIG_DIR",
|
|
6488
|
+
"CODEX_HOME",
|
|
6489
|
+
"CLAUDECODE"
|
|
6490
|
+
];
|
|
6491
|
+
EXTERNAL_ENV_PREFIX_SWEEPS = ["CLAUDE_CODE_", "KERYX_"];
|
|
6492
|
+
MCP_ENV_ALLOW_EXACT = ["PWD", "OLDPWD"];
|
|
6493
|
+
MCP_ENV_DENY = [
|
|
6494
|
+
"SSH_AUTH_SOCK",
|
|
6495
|
+
"SSH_AGENT_PID",
|
|
6496
|
+
"GPG_AGENT_INFO",
|
|
6497
|
+
"GNUPGHOME",
|
|
6498
|
+
"GNOME_KEYRING_CONTROL",
|
|
6499
|
+
"GNOME_KEYRING_PID",
|
|
6500
|
+
"KDE_FULL_SESSION",
|
|
6501
|
+
"GIT_ASKPASS",
|
|
6502
|
+
"SSH_ASKPASS",
|
|
6503
|
+
"SUDO_ASKPASS",
|
|
6504
|
+
"GIT_CREDENTIAL_HELPER",
|
|
6505
|
+
"GIT_SSH_COMMAND",
|
|
6506
|
+
"KUBECONFIG",
|
|
6507
|
+
"NETRC",
|
|
6508
|
+
"PGPASSFILE",
|
|
6509
|
+
"PGSERVICEFILE",
|
|
6510
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
6511
|
+
"CLOUDSDK_CONFIG",
|
|
6512
|
+
"DOCKER_CONFIG",
|
|
6513
|
+
"AWS_SHARED_CREDENTIALS_FILE",
|
|
6514
|
+
"AWS_CONFIG_FILE",
|
|
6515
|
+
"BOTO_CONFIG",
|
|
6516
|
+
"AZURE_CONFIG_DIR",
|
|
6517
|
+
"CURLRC",
|
|
6518
|
+
"WGETRC",
|
|
6519
|
+
"NPMRC",
|
|
6520
|
+
"NPM_CONFIG_USERCONFIG",
|
|
6521
|
+
"PIP_CONFIG_FILE",
|
|
6522
|
+
"RCLONE_CONFIG",
|
|
6523
|
+
"MAVEN_SETTINGS",
|
|
6524
|
+
"GIT_CONFIG",
|
|
6525
|
+
"GIT_CONFIG_GLOBAL",
|
|
6526
|
+
"HGRCPATH",
|
|
6527
|
+
"ANSIBLE_VAULT_PASSWORD_FILE",
|
|
6528
|
+
"AZURE_CLIENT_CERTIFICATE_PATH",
|
|
6529
|
+
"ARM_CLIENT_CERTIFICATE_PATH",
|
|
6530
|
+
"VAULT_CLIENT_CERT",
|
|
6531
|
+
"VAULT_CLIENT_KEY",
|
|
6532
|
+
"SSL_CLIENT_CERT_PATH",
|
|
6533
|
+
"SSL_CLIENT_KEY_FILE",
|
|
6534
|
+
"IDENTITY_FILE",
|
|
6535
|
+
"SSH_IDENTITY_FILE",
|
|
6536
|
+
"GCP_SERVICE_ACCOUNT",
|
|
6537
|
+
"XAUTHORITY",
|
|
6538
|
+
"OPENAI_API_KEY",
|
|
6539
|
+
"GEMINI_API_KEY",
|
|
6540
|
+
"GOOGLE_API_KEY",
|
|
6541
|
+
"GROQ_API_KEY",
|
|
6542
|
+
"MISTRAL_API_KEY",
|
|
6543
|
+
"XAI_API_KEY",
|
|
6544
|
+
"DEEPSEEK_API_KEY",
|
|
6545
|
+
"OPENROUTER_API_KEY",
|
|
6546
|
+
"TOGETHER_API_KEY",
|
|
6547
|
+
"PERPLEXITY_API_KEY"
|
|
6548
|
+
];
|
|
6549
|
+
MCP_ENV_PREFIX_SWEEPS = [
|
|
6550
|
+
"OP_SESSION",
|
|
6551
|
+
"BW_SESSION"
|
|
6552
|
+
];
|
|
6553
|
+
SECRET_SEGMENTS = [
|
|
6554
|
+
"KEY",
|
|
6555
|
+
"KEYS",
|
|
6556
|
+
"TOKEN",
|
|
6557
|
+
"TOKENS",
|
|
6558
|
+
"SECRET",
|
|
6559
|
+
"SECRETS",
|
|
6560
|
+
"PASSWORD",
|
|
6561
|
+
"PASSWD",
|
|
6562
|
+
"PWD",
|
|
6563
|
+
"PASS",
|
|
6564
|
+
"PASSPHRASE",
|
|
6565
|
+
"PASSCODE",
|
|
6566
|
+
"PASSKEY",
|
|
6567
|
+
"CREDENTIAL",
|
|
6568
|
+
"CREDENTIALS",
|
|
6569
|
+
"CREDS",
|
|
6570
|
+
"AUTH",
|
|
6571
|
+
"AUTHORIZATION",
|
|
6572
|
+
"BEARER",
|
|
6573
|
+
"JWT",
|
|
6574
|
+
"COOKIE",
|
|
6575
|
+
"SIGNATURE",
|
|
6576
|
+
"APIKEY",
|
|
6577
|
+
"SECRETKEY",
|
|
6578
|
+
"ACCESSKEY",
|
|
6579
|
+
"AUTHTOKEN",
|
|
6580
|
+
"PAT",
|
|
6581
|
+
"WEBHOOK"
|
|
6582
|
+
];
|
|
6583
|
+
SECRET_SEGMENT_RE = new RegExp(`(^|_)(${SECRET_SEGMENTS.join("|")})($|_)`);
|
|
6584
|
+
SECRET_SUBSTRING_RE = /(PASSWORD|PASSWD|PASSPHRASE|PASSCODE|SSHPASS|SECRET|CREDENTIAL|APIKEY|KEYFILE|PRIVKEY)/;
|
|
6585
|
+
GLUED_SECRET_RE = /(PRIVATE|SECRET|ACCESS|REFRESH|SESSION|CLIENT|API|APP)(KEY|TOKEN|SECRET)($|_)|DB(PASS|PWD)($|_)/;
|
|
6586
|
+
CONNECTION_STRING_RE = /(^|_)(DSN|CONNECTION_STRING|CONNECTIONSTRING)($|_)/;
|
|
6587
|
+
CREDENTIAL_IN_VALUE_RE = /:\/\/[^/\s@]*(:[^/\s@]*)?@/;
|
|
6588
|
+
VALUE_DEPENDENT = {
|
|
6589
|
+
DOCKER_HOST: (value) => !value.startsWith("unix://") && !value.startsWith("fd://")
|
|
6590
|
+
};
|
|
6591
|
+
});
|
|
6592
|
+
|
|
6152
6593
|
// src/security/audit-harness/codex-toml.ts
|
|
6153
6594
|
function stripComment(line) {
|
|
6154
6595
|
let quoted = false;
|
|
@@ -18357,8 +18798,11 @@ var init_impact_evidence = __esm(() => {
|
|
|
18357
18798
|
// src/security/service.ts
|
|
18358
18799
|
var exports_service2 = {};
|
|
18359
18800
|
__export(exports_service2, {
|
|
18801
|
+
EXTERNAL_ENV_DENY: () => EXTERNAL_ENV_DENY,
|
|
18802
|
+
EXTERNAL_ENV_PREFIX_SWEEPS: () => EXTERNAL_ENV_PREFIX_SWEEPS,
|
|
18360
18803
|
addBaselineEntry: () => addBaselineEntry,
|
|
18361
18804
|
analyze: () => analyze,
|
|
18805
|
+
appendIncident: () => appendIncident,
|
|
18362
18806
|
appendLogRecord: () => appendLogRecord,
|
|
18363
18807
|
applyAuditProposal: () => applyAuditProposal,
|
|
18364
18808
|
auditGate: () => auditGate,
|
|
@@ -18366,13 +18810,20 @@ __export(exports_service2, {
|
|
|
18366
18810
|
createImpactEvidenceProvider: () => createImpactEvidenceProvider,
|
|
18367
18811
|
createSecurityService: () => createSecurityService,
|
|
18368
18812
|
defaultBaselinePath: () => defaultBaselinePath,
|
|
18813
|
+
detectEntropy: () => detectEntropy,
|
|
18814
|
+
detectSecrets: () => detectSecrets,
|
|
18369
18815
|
hostDeliveryStatus: () => hostDeliveryStatus,
|
|
18816
|
+
isDeniedForMcpChild: () => isDeniedForMcpChild,
|
|
18817
|
+
isEntropyBackendEnabled: () => isEntropyBackendEnabled,
|
|
18370
18818
|
loadSecurityConfig: () => loadSecurityConfig,
|
|
18819
|
+
looksSecretShaped: () => looksSecretShaped,
|
|
18820
|
+
looksSecretShapedIn: () => looksSecretShapedIn,
|
|
18371
18821
|
memoizeResolved: () => memoizeResolved,
|
|
18372
18822
|
normalizeRequestFiles: () => normalizeRequestFiles,
|
|
18373
18823
|
readLogRecords: () => readLogRecords,
|
|
18374
18824
|
redactSensitiveText: () => redactSensitiveText,
|
|
18375
18825
|
renderEvidenceBlock: () => renderEvidenceBlock,
|
|
18826
|
+
resetEntropyGateForTests: () => resetEntropyGateForTests,
|
|
18376
18827
|
resolveImpactEvidenceConfig: () => resolveImpactEvidenceConfig,
|
|
18377
18828
|
resolveImpactEvidenceConfigTrusted: () => resolveImpactEvidenceConfigTrusted,
|
|
18378
18829
|
runGate: () => runGate,
|
|
@@ -18381,6 +18832,7 @@ __export(exports_service2, {
|
|
|
18381
18832
|
runScan: () => runScan,
|
|
18382
18833
|
runScanPath: () => runScanPath,
|
|
18383
18834
|
scanContent: () => scanContent,
|
|
18835
|
+
setEntropyBackendEnabledForTests: () => setEntropyBackendEnabledForTests,
|
|
18384
18836
|
sourceForFileRead: () => sourceForFileRead2,
|
|
18385
18837
|
validateSerializedOutput: () => validateSerializedOutput,
|
|
18386
18838
|
verifyConfigChecksum: () => verifyConfigChecksum
|
|
@@ -18626,6 +19078,11 @@ var init_service4 = __esm(() => {
|
|
|
18626
19078
|
init_path_scan();
|
|
18627
19079
|
init_read_source();
|
|
18628
19080
|
init_redact();
|
|
19081
|
+
init_secrets();
|
|
19082
|
+
init_entropy();
|
|
19083
|
+
init_incidents();
|
|
19084
|
+
init_entropy_gate();
|
|
19085
|
+
init_credential_shape();
|
|
18629
19086
|
init_audit_harness();
|
|
18630
19087
|
init_config2();
|
|
18631
19088
|
init_impact_evidence();
|