(file, content)
| 494 | const freq = new Map(); |
| 495 | for (const ch of value) freq.set(ch, (freq.get(ch) || 0) + 1); |
| 496 | let bits = 0; |
| 497 | for (const count of freq.values()) { |
| 498 | const p = count / value.length; |
| 499 | bits -= p * Math.log2(p); |
| 500 | } |
| 501 | return bits; |
| 502 | } |
| 503 | |
| 504 | function lensSecrets(file, content) { |
| 505 | // Defense in depth: docs describing token formats must never block. |
| 506 | if (/\.md$/.test(file)) return []; |
| 507 | |
| 508 | const violations = []; |
| 509 | const seenClasses = new Set(); |
| 510 | |
| 511 | for (const line of content.split('\n')) { |
| 512 | if (SECRET_PLACEHOLDER_HINT.test(line)) continue; |
| 513 | |
| 514 | for (const pattern of SECRET_PATTERNS) { |
| 515 | if (seenClasses.has(pattern.cls)) continue; |
| 516 | if (pattern.regex.test(line)) { |
| 517 | seenClasses.add(pattern.cls); |
| 518 | violations.push({ |
| 519 | file, |
| 520 | lens: 'secrets', |
| 521 | rule: pattern.cls, |
| 522 | message: `Possible ${pattern.label} (class: ${pattern.cls}) - move it to environment configuration and rotate it if real`, |
| 523 | }); |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | if (!seenClasses.has('high-entropy-assignment')) { |
| 528 | const match = SECRET_GENERIC_ASSIGN.exec(line); |
| 529 | if (match) { |
| 530 | const value = match[1]; |
| 531 | const looksLikeProse = value.includes(' '); |
| 532 | if (!looksLikeProse && |
| 533 | !SECRET_PLACEHOLDER_HINT.test(value) && |
| 534 | shannonEntropy(value) >= SECRET_ENTROPY_THRESHOLD) { |
| 535 | seenClasses.add('high-entropy-assignment'); |
| 536 | violations.push({ |
| 537 | file, |
| 538 | lens: 'secrets', |
| 539 | rule: 'high-entropy-assignment', |
| 540 | message: 'Secret-like key assigned a high-entropy literal (class: high-entropy-assignment) - load it from environment configuration instead', |
| 541 | }); |
| 542 | } |
| 543 | } |
no test coverage detected