@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/core.js
CHANGED
|
@@ -4028,6 +4028,277 @@ 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 isSingleTransitionTag(segment) {
|
|
4044
|
+
return /^[A-Za-z]+[0-9]+$/.test(segment) || /^[0-9]+[A-Za-z]+$/.test(segment);
|
|
4045
|
+
}
|
|
4046
|
+
function isWordSlug(value) {
|
|
4047
|
+
const segments = value.split(/[-_]/);
|
|
4048
|
+
if (segments.length < 3) {
|
|
4049
|
+
return false;
|
|
4050
|
+
}
|
|
4051
|
+
let words = 0;
|
|
4052
|
+
for (const segment of segments) {
|
|
4053
|
+
const isAlpha = /^[A-Za-z]+$/.test(segment);
|
|
4054
|
+
const isDigits = /^[0-9]+$/.test(segment);
|
|
4055
|
+
if (!isAlpha && !isDigits && !isSingleTransitionTag(segment)) {
|
|
4056
|
+
return false;
|
|
4057
|
+
}
|
|
4058
|
+
if (isAlpha && segment.length >= 3) {
|
|
4059
|
+
words += 1;
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
return words >= 1;
|
|
4063
|
+
}
|
|
4064
|
+
function shannonEntropy(value) {
|
|
4065
|
+
const counts = new Map;
|
|
4066
|
+
for (const ch of value) {
|
|
4067
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
4068
|
+
}
|
|
4069
|
+
let entropy = 0;
|
|
4070
|
+
for (const count of counts.values()) {
|
|
4071
|
+
const p = count / value.length;
|
|
4072
|
+
entropy -= p * Math.log2(p);
|
|
4073
|
+
}
|
|
4074
|
+
return entropy;
|
|
4075
|
+
}
|
|
4076
|
+
function bareShapeQualifies(head) {
|
|
4077
|
+
if (!/[0-9]/.test(head) && !/[+=]/.test(head)) {
|
|
4078
|
+
return { qualifies: false, entropy: 0 };
|
|
4079
|
+
}
|
|
4080
|
+
if (isWordSlug(head)) {
|
|
4081
|
+
return { qualifies: false, entropy: 0 };
|
|
4082
|
+
}
|
|
4083
|
+
const entropy = shannonEntropy(head);
|
|
4084
|
+
const hexBlob = isHexBlob(head);
|
|
4085
|
+
if (entropy < 3.6 && !hexBlob) {
|
|
4086
|
+
return { qualifies: false, entropy };
|
|
4087
|
+
}
|
|
4088
|
+
return { qualifies: true, entropy };
|
|
4089
|
+
}
|
|
4090
|
+
function shapeQualifiesAsSecret(value) {
|
|
4091
|
+
if (isAllowShapedValue(value)) {
|
|
4092
|
+
return { qualifies: false, entropy: 0 };
|
|
4093
|
+
}
|
|
4094
|
+
return bareShapeQualifies(value);
|
|
4095
|
+
}
|
|
4096
|
+
function looksSecretShaped(value) {
|
|
4097
|
+
return shapeQualifiesAsSecret(value).qualifies;
|
|
4098
|
+
}
|
|
4099
|
+
function buildMatch(start, end, value, entropy) {
|
|
4100
|
+
const confidence = Math.max(0.4, Math.min(0.7, 0.4 + (entropy - 3.6) * 0.21));
|
|
4101
|
+
return {
|
|
4102
|
+
category: "secret",
|
|
4103
|
+
policyId: "secrets.high-entropy",
|
|
4104
|
+
severity: "medium",
|
|
4105
|
+
confidence: Number(confidence.toFixed(2)),
|
|
4106
|
+
start,
|
|
4107
|
+
end,
|
|
4108
|
+
value,
|
|
4109
|
+
mask: "entropy",
|
|
4110
|
+
remediation: "Verify this high-entropy value is not a live credential."
|
|
4111
|
+
};
|
|
4112
|
+
}
|
|
4113
|
+
function findUrlSpans(content) {
|
|
4114
|
+
const spans = [];
|
|
4115
|
+
URL_SPAN_RE.lastIndex = 0;
|
|
4116
|
+
let m;
|
|
4117
|
+
while ((m = URL_SPAN_RE.exec(content)) !== null) {
|
|
4118
|
+
let raw = m[0];
|
|
4119
|
+
while (raw.length > 0 && URL_TRAILING_PUNCTUATION_RE.test(raw)) {
|
|
4120
|
+
raw = raw.slice(0, -1);
|
|
4121
|
+
}
|
|
4122
|
+
if (raw.length > 0) {
|
|
4123
|
+
spans.push({ start: m.index, end: m.index + raw.length, raw });
|
|
4124
|
+
}
|
|
4125
|
+
if (m.index === URL_SPAN_RE.lastIndex) {
|
|
4126
|
+
URL_SPAN_RE.lastIndex += 1;
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
return spans;
|
|
4130
|
+
}
|
|
4131
|
+
function urlComponentSpans(raw) {
|
|
4132
|
+
const spans = [];
|
|
4133
|
+
const schemeMatch = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.exec(raw);
|
|
4134
|
+
if (schemeMatch === null) {
|
|
4135
|
+
return spans;
|
|
4136
|
+
}
|
|
4137
|
+
let i = schemeMatch[0].length;
|
|
4138
|
+
while (i < raw.length && raw[i] !== "/" && raw[i] !== "?" && raw[i] !== "#") {
|
|
4139
|
+
i += 1;
|
|
4140
|
+
}
|
|
4141
|
+
let pathEnd = i;
|
|
4142
|
+
while (pathEnd < raw.length && raw[pathEnd] !== "?" && raw[pathEnd] !== "#") {
|
|
4143
|
+
pathEnd += 1;
|
|
4144
|
+
}
|
|
4145
|
+
let segStart = i;
|
|
4146
|
+
for (let j = i;j <= pathEnd; j += 1) {
|
|
4147
|
+
if (j === pathEnd || raw[j] === "/") {
|
|
4148
|
+
if (j > segStart) {
|
|
4149
|
+
spans.push({ start: segStart, end: j });
|
|
4150
|
+
}
|
|
4151
|
+
segStart = j + 1;
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
let cursor = pathEnd;
|
|
4155
|
+
if (cursor < raw.length && raw[cursor] === "?") {
|
|
4156
|
+
let queryEnd = cursor + 1;
|
|
4157
|
+
while (queryEnd < raw.length && raw[queryEnd] !== "#") {
|
|
4158
|
+
queryEnd += 1;
|
|
4159
|
+
}
|
|
4160
|
+
let pairStart = cursor + 1;
|
|
4161
|
+
for (let j = cursor + 1;j <= queryEnd; j += 1) {
|
|
4162
|
+
if (j === queryEnd || raw[j] === "&") {
|
|
4163
|
+
const pair = raw.slice(pairStart, j);
|
|
4164
|
+
const eq = pair.indexOf("=");
|
|
4165
|
+
if (eq !== -1 && eq + 1 < pair.length) {
|
|
4166
|
+
spans.push({ start: pairStart + eq + 1, end: j });
|
|
4167
|
+
} else if (eq === -1 && pair.length > 0) {
|
|
4168
|
+
spans.push({ start: pairStart, end: j });
|
|
4169
|
+
}
|
|
4170
|
+
pairStart = j + 1;
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
cursor = queryEnd;
|
|
4174
|
+
}
|
|
4175
|
+
if (cursor < raw.length && raw[cursor] === "#" && cursor + 1 < raw.length) {
|
|
4176
|
+
spans.push({ start: cursor + 1, end: raw.length });
|
|
4177
|
+
}
|
|
4178
|
+
return spans;
|
|
4179
|
+
}
|
|
4180
|
+
function secretShapedCandidatesIn(text) {
|
|
4181
|
+
const normalized = text.replace(/\+/g, " ");
|
|
4182
|
+
const out = [];
|
|
4183
|
+
TOKEN.lastIndex = 0;
|
|
4184
|
+
let m;
|
|
4185
|
+
while ((m = TOKEN.exec(normalized)) !== null) {
|
|
4186
|
+
const value = m[0];
|
|
4187
|
+
if (value.length < URL_COMPONENT_MIN_LENGTH) {
|
|
4188
|
+
continue;
|
|
4189
|
+
}
|
|
4190
|
+
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
4191
|
+
const { qualifies, entropy } = shapeQualifiesAsSecret(head);
|
|
4192
|
+
if (!qualifies) {
|
|
4193
|
+
continue;
|
|
4194
|
+
}
|
|
4195
|
+
const start = m.index;
|
|
4196
|
+
const end = start + value.length;
|
|
4197
|
+
out.push({ start, end, value: text.slice(start, end), entropy });
|
|
4198
|
+
}
|
|
4199
|
+
return out;
|
|
4200
|
+
}
|
|
4201
|
+
function looksSecretShapedIn(text) {
|
|
4202
|
+
return secretShapedCandidatesIn(text).length > 0;
|
|
4203
|
+
}
|
|
4204
|
+
function detectEntropy(content) {
|
|
4205
|
+
const matches = [];
|
|
4206
|
+
const urlSpans = findUrlSpans(content);
|
|
4207
|
+
for (const url of urlSpans) {
|
|
4208
|
+
for (const comp of urlComponentSpans(url.raw)) {
|
|
4209
|
+
const compText = url.raw.slice(comp.start, comp.end);
|
|
4210
|
+
for (const cand of secretShapedCandidatesIn(compText)) {
|
|
4211
|
+
const absStart = url.start + comp.start + cand.start;
|
|
4212
|
+
const absEnd = url.start + comp.start + cand.end;
|
|
4213
|
+
matches.push(buildMatch(absStart, absEnd, cand.value, cand.entropy));
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
LABEL_ASSIGNMENT_RE.lastIndex = 0;
|
|
4218
|
+
let am;
|
|
4219
|
+
while ((am = LABEL_ASSIGNMENT_RE.exec(content)) !== null) {
|
|
4220
|
+
const value = am[1];
|
|
4221
|
+
const end = am.index + am[0].length;
|
|
4222
|
+
const start = end - value.length;
|
|
4223
|
+
if (urlSpans.some((u) => start < u.end && end > u.start)) {
|
|
4224
|
+
continue;
|
|
4225
|
+
}
|
|
4226
|
+
const { qualifies, entropy } = bareShapeQualifies(value);
|
|
4227
|
+
if (qualifies) {
|
|
4228
|
+
matches.push(buildMatch(start, end, value, entropy));
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
TOKEN.lastIndex = 0;
|
|
4232
|
+
let m;
|
|
4233
|
+
while ((m = TOKEN.exec(content)) !== null) {
|
|
4234
|
+
const value = m[0];
|
|
4235
|
+
const start = m.index;
|
|
4236
|
+
const end = start + value.length;
|
|
4237
|
+
if (urlSpans.some((u) => start < u.end && end > u.start)) {
|
|
4238
|
+
continue;
|
|
4239
|
+
}
|
|
4240
|
+
if (matches.some((existing) => start < existing.end && end > existing.start)) {
|
|
4241
|
+
continue;
|
|
4242
|
+
}
|
|
4243
|
+
const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
|
|
4244
|
+
const { qualifies, entropy } = bareShapeQualifies(head);
|
|
4245
|
+
if (!qualifies) {
|
|
4246
|
+
continue;
|
|
4247
|
+
}
|
|
4248
|
+
const before = lastLineOf(content.slice(Math.max(0, start - LABEL_WINDOW), start));
|
|
4249
|
+
if (!SENSITIVE_LABEL.test(before)) {
|
|
4250
|
+
continue;
|
|
4251
|
+
}
|
|
4252
|
+
if (isAllowShapedValue(head) && !ADJACENT_LABEL.test(before)) {
|
|
4253
|
+
continue;
|
|
4254
|
+
}
|
|
4255
|
+
matches.push(buildMatch(start, end, value, entropy));
|
|
4256
|
+
}
|
|
4257
|
+
return matches.sort((a, b) => a.start - b.start);
|
|
4258
|
+
}
|
|
4259
|
+
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;
|
|
4260
|
+
var init_entropy = __esm(() => {
|
|
4261
|
+
SENSITIVE_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(${caseless(SENSITIVE_LABEL_WORDS)})(?:$|[^A-Za-z0-9]|(?=[A-Z]))`);
|
|
4262
|
+
ADJACENT_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*["']?\\s*[:=]?\\s*["']?$`);
|
|
4263
|
+
LABEL_ASSIGNMENT_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${SENSITIVE_LABEL_WORDS})[A-Za-z0-9_-]*"?\\s*=\\s*"?([A-Za-z0-9+/_=-]{6,})`, "gi");
|
|
4264
|
+
TOKEN = /[A-Za-z0-9+/=_-]{20,}(?:\.[A-Za-z0-9+/=_-]{6,})*/g;
|
|
4265
|
+
TOKEN_HEAD = /^[A-Za-z0-9+/=_-]+/;
|
|
4266
|
+
HEX_BLOB = /^[0-9a-f]{24,}$/i;
|
|
4267
|
+
FULL_GIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
4268
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4269
|
+
INTEGRITY_RE = /^(?:sha256|sha512)-[A-Za-z0-9+/]+=*$/;
|
|
4270
|
+
URL_SPAN_RE = /\bhttps?:\/\/[^\s<>"'\\]+/g;
|
|
4271
|
+
URL_TRAILING_PUNCTUATION_RE = /[.,;:!?)\]}]$/;
|
|
4272
|
+
});
|
|
4273
|
+
|
|
4274
|
+
// src/security/entropy-gate.ts
|
|
4275
|
+
function ensureLoading() {
|
|
4276
|
+
if (loadStarted) {
|
|
4277
|
+
return;
|
|
4278
|
+
}
|
|
4279
|
+
loadStarted = true;
|
|
4280
|
+
loadSecurityConfig(process.cwd()).then((config) => {
|
|
4281
|
+
cachedEnabled = config.backends.entropy.enabled;
|
|
4282
|
+
}).catch(() => {});
|
|
4283
|
+
}
|
|
4284
|
+
function isEntropyBackendEnabled() {
|
|
4285
|
+
ensureLoading();
|
|
4286
|
+
return cachedEnabled;
|
|
4287
|
+
}
|
|
4288
|
+
function setEntropyBackendEnabledForTests(enabled) {
|
|
4289
|
+
loadStarted = true;
|
|
4290
|
+
cachedEnabled = enabled;
|
|
4291
|
+
}
|
|
4292
|
+
function resetEntropyGateForTests() {
|
|
4293
|
+
loadStarted = false;
|
|
4294
|
+
cachedEnabled = DEFAULT_SECURITY_CONFIG.backends.entropy.enabled;
|
|
4295
|
+
}
|
|
4296
|
+
var cachedEnabled, loadStarted = false;
|
|
4297
|
+
var init_entropy_gate = __esm(() => {
|
|
4298
|
+
init_config2();
|
|
4299
|
+
cachedEnabled = DEFAULT_SECURITY_CONFIG.backends.entropy.enabled;
|
|
4300
|
+
});
|
|
4301
|
+
|
|
4031
4302
|
// src/security/redact.ts
|
|
4032
4303
|
import path17 from "path";
|
|
4033
4304
|
import { createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
@@ -4097,7 +4368,12 @@ function redactSensitiveText(text) {
|
|
|
4097
4368
|
if (text.length === 0) {
|
|
4098
4369
|
return text;
|
|
4099
4370
|
}
|
|
4100
|
-
const matches = [
|
|
4371
|
+
const matches = [
|
|
4372
|
+
...detectSecrets(text),
|
|
4373
|
+
...detectPii(text),
|
|
4374
|
+
...detectExfil(text),
|
|
4375
|
+
...isEntropyBackendEnabled() ? detectEntropy(text) : []
|
|
4376
|
+
];
|
|
4101
4377
|
if (matches.length === 0) {
|
|
4102
4378
|
return text;
|
|
4103
4379
|
}
|
|
@@ -4134,6 +4410,8 @@ var init_redact = __esm(() => {
|
|
|
4134
4410
|
init_secrets();
|
|
4135
4411
|
init_pii();
|
|
4136
4412
|
init_exfil();
|
|
4413
|
+
init_entropy();
|
|
4414
|
+
init_entropy_gate();
|
|
4137
4415
|
});
|
|
4138
4416
|
|
|
4139
4417
|
// src/security/output-validation.ts
|
|
@@ -4874,102 +5152,33 @@ var init_seam = __esm(() => {
|
|
|
4874
5152
|
init_warn_once();
|
|
4875
5153
|
});
|
|
4876
5154
|
|
|
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
|
-
});
|
|
5155
|
+
// src/security/detect/injection.ts
|
|
5156
|
+
function foldForMatching(content) {
|
|
5157
|
+
let folded = "";
|
|
5158
|
+
for (let i = 0;i < content.length; i += 1) {
|
|
5159
|
+
const ch = content[i];
|
|
5160
|
+
folded += CONFUSABLES[ch] ?? ch;
|
|
4947
5161
|
}
|
|
4948
|
-
|
|
5162
|
+
const nfkc = folded.normalize("NFKC");
|
|
5163
|
+
return nfkc.length === folded.length ? nfkc : folded;
|
|
4949
5164
|
}
|
|
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
5165
|
function detectInjection(content) {
|
|
4960
5166
|
const matches = [];
|
|
5167
|
+
const folded = foldForMatching(content);
|
|
4961
5168
|
for (const pattern of PATTERNS) {
|
|
4962
5169
|
pattern.regex.lastIndex = 0;
|
|
4963
5170
|
let m;
|
|
4964
|
-
while ((m = pattern.regex.exec(
|
|
5171
|
+
while ((m = pattern.regex.exec(folded)) !== null) {
|
|
5172
|
+
const start = Math.min(m.index, content.length);
|
|
5173
|
+
const end = Math.min(m.index + m[0].length, content.length);
|
|
4965
5174
|
matches.push({
|
|
4966
5175
|
category: "prompt-injection",
|
|
4967
5176
|
policyId: pattern.policyId,
|
|
4968
5177
|
severity: "low",
|
|
4969
5178
|
confidence: pattern.confidence,
|
|
4970
|
-
start
|
|
4971
|
-
end
|
|
4972
|
-
value:
|
|
5179
|
+
start,
|
|
5180
|
+
end,
|
|
5181
|
+
value: content.slice(start, end),
|
|
4973
5182
|
remediation: "Treat external content as data, not instruction; require human review."
|
|
4974
5183
|
});
|
|
4975
5184
|
if (m.index === pattern.regex.lastIndex) {
|
|
@@ -4979,27 +5188,69 @@ function detectInjection(content) {
|
|
|
4979
5188
|
}
|
|
4980
5189
|
return matches;
|
|
4981
5190
|
}
|
|
4982
|
-
var PATTERNS;
|
|
5191
|
+
var CONFUSABLES, PATTERNS;
|
|
4983
5192
|
var init_injection = __esm(() => {
|
|
5193
|
+
CONFUSABLES = {
|
|
5194
|
+
"\u0430": "a",
|
|
5195
|
+
"\u0410": "A",
|
|
5196
|
+
"\u0435": "e",
|
|
5197
|
+
"\u0415": "E",
|
|
5198
|
+
"\u043E": "o",
|
|
5199
|
+
"\u041E": "O",
|
|
5200
|
+
"\u0440": "p",
|
|
5201
|
+
"\u0420": "P",
|
|
5202
|
+
"\u0441": "c",
|
|
5203
|
+
"\u0421": "C",
|
|
5204
|
+
"\u0445": "x",
|
|
5205
|
+
"\u0425": "X",
|
|
5206
|
+
"\u0443": "y",
|
|
5207
|
+
"\u0423": "Y",
|
|
5208
|
+
"\u0455": "s",
|
|
5209
|
+
"\u0405": "S",
|
|
5210
|
+
"\u0456": "o",
|
|
5211
|
+
"\u0406": "O",
|
|
5212
|
+
"\u0458": "j",
|
|
5213
|
+
"\u0408": "J",
|
|
5214
|
+
"\u03B1": "a",
|
|
5215
|
+
"\u0391": "A",
|
|
5216
|
+
"\u03B2": "b",
|
|
5217
|
+
"\u0392": "B",
|
|
5218
|
+
"\u03B5": "e",
|
|
5219
|
+
"\u0395": "E",
|
|
5220
|
+
"\u03B9": "i",
|
|
5221
|
+
"\u0399": "I",
|
|
5222
|
+
"\u03BA": "k",
|
|
5223
|
+
"\u039A": "K",
|
|
5224
|
+
"\u03BF": "o",
|
|
5225
|
+
"\u039F": "O",
|
|
5226
|
+
"\u03C1": "p",
|
|
5227
|
+
"\u03A1": "P",
|
|
5228
|
+
"\u03C4": "t",
|
|
5229
|
+
"\u03A4": "T",
|
|
5230
|
+
"\u03C5": "u",
|
|
5231
|
+
"\u03A5": "Y",
|
|
5232
|
+
"\u03C7": "x",
|
|
5233
|
+
"\u03A7": "X"
|
|
5234
|
+
};
|
|
4984
5235
|
PATTERNS = [
|
|
4985
5236
|
{
|
|
4986
5237
|
policyId: "prompt-injection.ignore-instructions",
|
|
4987
|
-
regex: /\b(ignore|disregard|forget|override)\b[
|
|
5238
|
+
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
5239
|
confidence: 0.4
|
|
4989
5240
|
},
|
|
4990
5241
|
{
|
|
4991
5242
|
policyId: "prompt-injection.reveal-context",
|
|
4992
|
-
regex: /\b(reveal|show|print|expose|leak|dump|repeat)\b[
|
|
5243
|
+
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
5244
|
confidence: 0.45
|
|
4994
5245
|
},
|
|
4995
5246
|
{
|
|
4996
5247
|
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[
|
|
5248
|
+
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
5249
|
confidence: 0.4
|
|
4999
5250
|
},
|
|
5000
5251
|
{
|
|
5001
5252
|
policyId: "prompt-injection.priority-override",
|
|
5002
|
-
regex: /\b(treat|follow|obey)\b[
|
|
5253
|
+
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
5254
|
confidence: 0.35
|
|
5004
5255
|
}
|
|
5005
5256
|
];
|
|
@@ -6149,6 +6400,140 @@ var init_read_source = __esm(() => {
|
|
|
6149
6400
|
init_contained_path();
|
|
6150
6401
|
});
|
|
6151
6402
|
|
|
6403
|
+
// src/security/credential-shape.ts
|
|
6404
|
+
function isDeniedForMcpChild(name, value) {
|
|
6405
|
+
if (value !== undefined && CREDENTIAL_IN_VALUE_RE.test(value))
|
|
6406
|
+
return true;
|
|
6407
|
+
const upper = name.toUpperCase();
|
|
6408
|
+
if (MCP_ENV_ALLOW_EXACT.includes(upper))
|
|
6409
|
+
return false;
|
|
6410
|
+
const conditional = VALUE_DEPENDENT[upper];
|
|
6411
|
+
if (conditional !== undefined) {
|
|
6412
|
+
return value === undefined ? true : conditional(value);
|
|
6413
|
+
}
|
|
6414
|
+
if (EXTERNAL_ENV_DENY.some((denied) => denied.toUpperCase() === upper))
|
|
6415
|
+
return true;
|
|
6416
|
+
if (MCP_ENV_DENY.includes(upper))
|
|
6417
|
+
return true;
|
|
6418
|
+
if (EXTERNAL_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix.toUpperCase())))
|
|
6419
|
+
return true;
|
|
6420
|
+
if (MCP_ENV_PREFIX_SWEEPS.some((prefix) => upper.startsWith(prefix)))
|
|
6421
|
+
return true;
|
|
6422
|
+
return SECRET_SEGMENT_RE.test(upper) || SECRET_SUBSTRING_RE.test(upper) || GLUED_SECRET_RE.test(upper) || CONNECTION_STRING_RE.test(upper);
|
|
6423
|
+
}
|
|
6424
|
+
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;
|
|
6425
|
+
var init_credential_shape = __esm(() => {
|
|
6426
|
+
EXTERNAL_ENV_DENY = [
|
|
6427
|
+
"ANTHROPIC_API_KEY",
|
|
6428
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
6429
|
+
"ANTHROPIC_BASE_URL",
|
|
6430
|
+
"ANTHROPIC_MODEL",
|
|
6431
|
+
"CLAUDE_CONFIG_DIR",
|
|
6432
|
+
"CODEX_HOME",
|
|
6433
|
+
"CLAUDECODE"
|
|
6434
|
+
];
|
|
6435
|
+
EXTERNAL_ENV_PREFIX_SWEEPS = ["CLAUDE_CODE_", "KERYX_"];
|
|
6436
|
+
MCP_ENV_ALLOW_EXACT = ["PWD", "OLDPWD"];
|
|
6437
|
+
MCP_ENV_DENY = [
|
|
6438
|
+
"SSH_AUTH_SOCK",
|
|
6439
|
+
"SSH_AGENT_PID",
|
|
6440
|
+
"GPG_AGENT_INFO",
|
|
6441
|
+
"GNUPGHOME",
|
|
6442
|
+
"GNOME_KEYRING_CONTROL",
|
|
6443
|
+
"GNOME_KEYRING_PID",
|
|
6444
|
+
"KDE_FULL_SESSION",
|
|
6445
|
+
"GIT_ASKPASS",
|
|
6446
|
+
"SSH_ASKPASS",
|
|
6447
|
+
"SUDO_ASKPASS",
|
|
6448
|
+
"GIT_CREDENTIAL_HELPER",
|
|
6449
|
+
"GIT_SSH_COMMAND",
|
|
6450
|
+
"KUBECONFIG",
|
|
6451
|
+
"NETRC",
|
|
6452
|
+
"PGPASSFILE",
|
|
6453
|
+
"PGSERVICEFILE",
|
|
6454
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
6455
|
+
"CLOUDSDK_CONFIG",
|
|
6456
|
+
"DOCKER_CONFIG",
|
|
6457
|
+
"AWS_SHARED_CREDENTIALS_FILE",
|
|
6458
|
+
"AWS_CONFIG_FILE",
|
|
6459
|
+
"BOTO_CONFIG",
|
|
6460
|
+
"AZURE_CONFIG_DIR",
|
|
6461
|
+
"CURLRC",
|
|
6462
|
+
"WGETRC",
|
|
6463
|
+
"NPMRC",
|
|
6464
|
+
"NPM_CONFIG_USERCONFIG",
|
|
6465
|
+
"PIP_CONFIG_FILE",
|
|
6466
|
+
"RCLONE_CONFIG",
|
|
6467
|
+
"MAVEN_SETTINGS",
|
|
6468
|
+
"GIT_CONFIG",
|
|
6469
|
+
"GIT_CONFIG_GLOBAL",
|
|
6470
|
+
"HGRCPATH",
|
|
6471
|
+
"ANSIBLE_VAULT_PASSWORD_FILE",
|
|
6472
|
+
"AZURE_CLIENT_CERTIFICATE_PATH",
|
|
6473
|
+
"ARM_CLIENT_CERTIFICATE_PATH",
|
|
6474
|
+
"VAULT_CLIENT_CERT",
|
|
6475
|
+
"VAULT_CLIENT_KEY",
|
|
6476
|
+
"SSL_CLIENT_CERT_PATH",
|
|
6477
|
+
"SSL_CLIENT_KEY_FILE",
|
|
6478
|
+
"IDENTITY_FILE",
|
|
6479
|
+
"SSH_IDENTITY_FILE",
|
|
6480
|
+
"GCP_SERVICE_ACCOUNT",
|
|
6481
|
+
"XAUTHORITY",
|
|
6482
|
+
"OPENAI_API_KEY",
|
|
6483
|
+
"GEMINI_API_KEY",
|
|
6484
|
+
"GOOGLE_API_KEY",
|
|
6485
|
+
"GROQ_API_KEY",
|
|
6486
|
+
"MISTRAL_API_KEY",
|
|
6487
|
+
"XAI_API_KEY",
|
|
6488
|
+
"DEEPSEEK_API_KEY",
|
|
6489
|
+
"OPENROUTER_API_KEY",
|
|
6490
|
+
"TOGETHER_API_KEY",
|
|
6491
|
+
"PERPLEXITY_API_KEY"
|
|
6492
|
+
];
|
|
6493
|
+
MCP_ENV_PREFIX_SWEEPS = [
|
|
6494
|
+
"OP_SESSION",
|
|
6495
|
+
"BW_SESSION"
|
|
6496
|
+
];
|
|
6497
|
+
SECRET_SEGMENTS = [
|
|
6498
|
+
"KEY",
|
|
6499
|
+
"KEYS",
|
|
6500
|
+
"TOKEN",
|
|
6501
|
+
"TOKENS",
|
|
6502
|
+
"SECRET",
|
|
6503
|
+
"SECRETS",
|
|
6504
|
+
"PASSWORD",
|
|
6505
|
+
"PASSWD",
|
|
6506
|
+
"PWD",
|
|
6507
|
+
"PASS",
|
|
6508
|
+
"PASSPHRASE",
|
|
6509
|
+
"PASSCODE",
|
|
6510
|
+
"PASSKEY",
|
|
6511
|
+
"CREDENTIAL",
|
|
6512
|
+
"CREDENTIALS",
|
|
6513
|
+
"CREDS",
|
|
6514
|
+
"AUTH",
|
|
6515
|
+
"AUTHORIZATION",
|
|
6516
|
+
"BEARER",
|
|
6517
|
+
"JWT",
|
|
6518
|
+
"COOKIE",
|
|
6519
|
+
"SIGNATURE",
|
|
6520
|
+
"APIKEY",
|
|
6521
|
+
"SECRETKEY",
|
|
6522
|
+
"ACCESSKEY",
|
|
6523
|
+
"AUTHTOKEN",
|
|
6524
|
+
"PAT",
|
|
6525
|
+
"WEBHOOK"
|
|
6526
|
+
];
|
|
6527
|
+
SECRET_SEGMENT_RE = new RegExp(`(^|_)(${SECRET_SEGMENTS.join("|")})($|_)`);
|
|
6528
|
+
SECRET_SUBSTRING_RE = /(PASSWORD|PASSWD|PASSPHRASE|PASSCODE|SSHPASS|SECRET|CREDENTIAL|APIKEY|KEYFILE|PRIVKEY)/;
|
|
6529
|
+
GLUED_SECRET_RE = /(PRIVATE|SECRET|ACCESS|REFRESH|SESSION|CLIENT|API|APP)(KEY|TOKEN|SECRET)($|_)|DB(PASS|PWD)($|_)/;
|
|
6530
|
+
CONNECTION_STRING_RE = /(^|_)(DSN|CONNECTION_STRING|CONNECTIONSTRING)($|_)/;
|
|
6531
|
+
CREDENTIAL_IN_VALUE_RE = /:\/\/[^/\s@]*(:[^/\s@]*)?@/;
|
|
6532
|
+
VALUE_DEPENDENT = {
|
|
6533
|
+
DOCKER_HOST: (value) => !value.startsWith("unix://") && !value.startsWith("fd://")
|
|
6534
|
+
};
|
|
6535
|
+
});
|
|
6536
|
+
|
|
6152
6537
|
// src/security/audit-harness/codex-toml.ts
|
|
6153
6538
|
function stripComment(line) {
|
|
6154
6539
|
let quoted = false;
|
|
@@ -18357,8 +18742,11 @@ var init_impact_evidence = __esm(() => {
|
|
|
18357
18742
|
// src/security/service.ts
|
|
18358
18743
|
var exports_service2 = {};
|
|
18359
18744
|
__export(exports_service2, {
|
|
18745
|
+
EXTERNAL_ENV_DENY: () => EXTERNAL_ENV_DENY,
|
|
18746
|
+
EXTERNAL_ENV_PREFIX_SWEEPS: () => EXTERNAL_ENV_PREFIX_SWEEPS,
|
|
18360
18747
|
addBaselineEntry: () => addBaselineEntry,
|
|
18361
18748
|
analyze: () => analyze,
|
|
18749
|
+
appendIncident: () => appendIncident,
|
|
18362
18750
|
appendLogRecord: () => appendLogRecord,
|
|
18363
18751
|
applyAuditProposal: () => applyAuditProposal,
|
|
18364
18752
|
auditGate: () => auditGate,
|
|
@@ -18366,13 +18754,20 @@ __export(exports_service2, {
|
|
|
18366
18754
|
createImpactEvidenceProvider: () => createImpactEvidenceProvider,
|
|
18367
18755
|
createSecurityService: () => createSecurityService,
|
|
18368
18756
|
defaultBaselinePath: () => defaultBaselinePath,
|
|
18757
|
+
detectEntropy: () => detectEntropy,
|
|
18758
|
+
detectSecrets: () => detectSecrets,
|
|
18369
18759
|
hostDeliveryStatus: () => hostDeliveryStatus,
|
|
18760
|
+
isDeniedForMcpChild: () => isDeniedForMcpChild,
|
|
18761
|
+
isEntropyBackendEnabled: () => isEntropyBackendEnabled,
|
|
18370
18762
|
loadSecurityConfig: () => loadSecurityConfig,
|
|
18763
|
+
looksSecretShaped: () => looksSecretShaped,
|
|
18764
|
+
looksSecretShapedIn: () => looksSecretShapedIn,
|
|
18371
18765
|
memoizeResolved: () => memoizeResolved,
|
|
18372
18766
|
normalizeRequestFiles: () => normalizeRequestFiles,
|
|
18373
18767
|
readLogRecords: () => readLogRecords,
|
|
18374
18768
|
redactSensitiveText: () => redactSensitiveText,
|
|
18375
18769
|
renderEvidenceBlock: () => renderEvidenceBlock,
|
|
18770
|
+
resetEntropyGateForTests: () => resetEntropyGateForTests,
|
|
18376
18771
|
resolveImpactEvidenceConfig: () => resolveImpactEvidenceConfig,
|
|
18377
18772
|
resolveImpactEvidenceConfigTrusted: () => resolveImpactEvidenceConfigTrusted,
|
|
18378
18773
|
runGate: () => runGate,
|
|
@@ -18381,6 +18776,7 @@ __export(exports_service2, {
|
|
|
18381
18776
|
runScan: () => runScan,
|
|
18382
18777
|
runScanPath: () => runScanPath,
|
|
18383
18778
|
scanContent: () => scanContent,
|
|
18779
|
+
setEntropyBackendEnabledForTests: () => setEntropyBackendEnabledForTests,
|
|
18384
18780
|
sourceForFileRead: () => sourceForFileRead2,
|
|
18385
18781
|
validateSerializedOutput: () => validateSerializedOutput,
|
|
18386
18782
|
verifyConfigChecksum: () => verifyConfigChecksum
|
|
@@ -18626,6 +19022,11 @@ var init_service4 = __esm(() => {
|
|
|
18626
19022
|
init_path_scan();
|
|
18627
19023
|
init_read_source();
|
|
18628
19024
|
init_redact();
|
|
19025
|
+
init_secrets();
|
|
19026
|
+
init_entropy();
|
|
19027
|
+
init_incidents();
|
|
19028
|
+
init_entropy_gate();
|
|
19029
|
+
init_credential_shape();
|
|
18629
19030
|
init_audit_harness();
|
|
18630
19031
|
init_config2();
|
|
18631
19032
|
init_impact_evidence();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.19",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|