@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.
Files changed (3) hide show
  1. package/dist/cli.js +645 -305
  2. package/dist/core.js +491 -90
  3. 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 = [...detectSecrets(text), ...detectPii(text), ...detectExfil(text)];
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/entropy.ts
4878
- function lastLineOf(before) {
4879
- const newline = before.lastIndexOf(`
4880
- `);
4881
- return newline < 0 ? before : before.slice(newline + 1);
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
- return matches;
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(content)) !== null) {
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: m.index,
4971
- end: m.index + m[0].length,
4972
- value: m[0],
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[^.\n]{0,40}\b(all\s+)?(previous|prior|above|earlier|system|developer)\b[^.\n]{0,20}\b(instructions?|prompts?|rules?|context)\b/gi,
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[^.\n]{0,30}\b(your\s+)?(system\s+prompt|instructions?|memory|secrets?|hidden\s+context|api\s*keys?)\b/gi,
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[^.\n]{0,40}\b(unrestricted|jailbroken|dan|admin|developer\s+mode|no\s+rules)\b/gi,
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[^.\n]{0,30}\b(the\s+)?(following|external|below)\b[^.\n]{0,20}\b(as\s+)?(instructions?|higher\s+priority|commands?)\b/gi,
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.18",
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": {