@kevinrabun/judges 3.28.0 → 3.29.1
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/CHANGELOG.md +27 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/calibration-dashboard.d.ts +2 -0
- package/dist/commands/calibration-dashboard.d.ts.map +1 -0
- package/dist/commands/calibration-dashboard.js +97 -0
- package/dist/commands/calibration-dashboard.js.map +1 -0
- package/dist/commands/community-patterns.d.ts +2 -0
- package/dist/commands/community-patterns.d.ts.map +1 -0
- package/dist/commands/community-patterns.js +132 -0
- package/dist/commands/community-patterns.js.map +1 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +91 -0
- package/dist/commands/diff.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -1
- package/dist/evaluators/api-contract.d.ts +10 -0
- package/dist/evaluators/api-contract.d.ts.map +1 -0
- package/dist/evaluators/api-contract.js +181 -0
- package/dist/evaluators/api-contract.js.map +1 -0
- package/dist/evaluators/index.d.ts.map +1 -1
- package/dist/evaluators/index.js +33 -1
- package/dist/evaluators/index.js.map +1 -1
- package/dist/evaluators/intent-alignment.d.ts +15 -0
- package/dist/evaluators/intent-alignment.d.ts.map +1 -0
- package/dist/evaluators/intent-alignment.js +233 -0
- package/dist/evaluators/intent-alignment.js.map +1 -0
- package/dist/evaluators/model-fingerprint.d.ts +3 -0
- package/dist/evaluators/model-fingerprint.d.ts.map +1 -0
- package/dist/evaluators/model-fingerprint.js +152 -0
- package/dist/evaluators/model-fingerprint.js.map +1 -0
- package/dist/evaluators/multi-turn-coherence.d.ts +14 -0
- package/dist/evaluators/multi-turn-coherence.d.ts.map +1 -0
- package/dist/evaluators/multi-turn-coherence.js +171 -0
- package/dist/evaluators/multi-turn-coherence.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/judges/api-contract.d.ts +3 -0
- package/dist/judges/api-contract.d.ts.map +1 -0
- package/dist/judges/api-contract.js +28 -0
- package/dist/judges/api-contract.js.map +1 -0
- package/dist/judges/index.d.ts.map +1 -1
- package/dist/judges/index.js +16 -0
- package/dist/judges/index.js.map +1 -1
- package/dist/judges/intent-alignment.d.ts +3 -0
- package/dist/judges/intent-alignment.d.ts.map +1 -0
- package/dist/judges/intent-alignment.js +28 -0
- package/dist/judges/intent-alignment.js.map +1 -0
- package/dist/judges/model-fingerprint.d.ts +3 -0
- package/dist/judges/model-fingerprint.d.ts.map +1 -0
- package/dist/judges/model-fingerprint.js +30 -0
- package/dist/judges/model-fingerprint.js.map +1 -0
- package/dist/judges/multi-turn-coherence.d.ts +3 -0
- package/dist/judges/multi-turn-coherence.d.ts.map +1 -0
- package/dist/judges/multi-turn-coherence.js +27 -0
- package/dist/judges/multi-turn-coherence.js.map +1 -0
- package/dist/patches/index.d.ts.map +1 -1
- package/dist/patches/index.js +372 -0
- package/dist/patches/index.js.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +76 -0
- package/dist/presets.js.map +1 -1
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/server.json +3 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const modelFingerprintJudge = {
|
|
2
|
+
id: "model-fingerprint",
|
|
3
|
+
name: "Model Fingerprint Detection",
|
|
4
|
+
domain: "AI Code Provenance & Model Attribution",
|
|
5
|
+
description: "Detects stylistic fingerprints characteristic of specific AI code generators " +
|
|
6
|
+
"(ChatGPT/GPT-4, Claude, Copilot, Gemini) to flag code that may carry " +
|
|
7
|
+
"model-specific biases, hallucinations, or blind spots.",
|
|
8
|
+
rulePrefix: "MFPR",
|
|
9
|
+
systemPrompt: `You are Judge Model Fingerprint Detection — an expert in identifying stylistic signatures of AI-generated code.
|
|
10
|
+
|
|
11
|
+
YOUR EVALUATION CRITERIA:
|
|
12
|
+
1. **ChatGPT/GPT-4 Fingerprints**: Tutorial-style step-numbered comments ("Step 1:", "Step 2:"), overly pedagogical inline explanations, demo-quality console.log statements.
|
|
13
|
+
2. **Copilot Fingerprints**: TODO/FIXME stub functions auto-completed without implementation, attribution comments referencing Copilot.
|
|
14
|
+
3. **Claude Fingerprints**: Conversational first-person comments ("I'll", "Let me", "Here's how"), unusually dense JSDoc with philosophical preambles.
|
|
15
|
+
4. **Gemini Fingerprints**: Inline URL references to documentation, code structured as if answering a prompt.
|
|
16
|
+
5. **Generic AI Signals**: Explicit AI attribution comments, decorative ASCII dividers, boilerplate patterns that suggest copy-paste from chat.
|
|
17
|
+
|
|
18
|
+
SEVERITY MAPPING:
|
|
19
|
+
- **info**: All model fingerprint detections — these are informational, not errors
|
|
20
|
+
|
|
21
|
+
FALSE POSITIVE AVOIDANCE:
|
|
22
|
+
- Require at least two distinct signal types before flagging.
|
|
23
|
+
- Do NOT flag well-written documentation simply because it is thorough.
|
|
24
|
+
- Single generic comments are not sufficient evidence.
|
|
25
|
+
|
|
26
|
+
ADVERSARIAL MANDATE:
|
|
27
|
+
- Flag AI-generated code that may carry model-specific biases or blind spots.
|
|
28
|
+
- Treat provenance transparency as a code quality concern.`,
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=model-fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-fingerprint.js","sourceRoot":"","sources":["../../src/judges/model-fingerprint.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAoB;IACpD,EAAE,EAAE,mBAAmB;IACvB,IAAI,EAAE,6BAA6B;IACnC,MAAM,EAAE,wCAAwC;IAChD,WAAW,EACT,+EAA+E;QAC/E,uEAAuE;QACvE,wDAAwD;IAC1D,UAAU,EAAE,MAAM;IAClB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;2DAmB2C;CAC1D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-turn-coherence.d.ts","sourceRoot":"","sources":["../../src/judges/multi-turn-coherence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,uBAAuB,EAAE,eA0BrC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const multiTurnCoherenceJudge = {
|
|
2
|
+
id: "multi-turn-coherence",
|
|
3
|
+
name: "Multi-Turn Coherence",
|
|
4
|
+
description: "Detects self-contradicting patterns: duplicate function definitions, contradictory " +
|
|
5
|
+
"boolean assignments, dead code after returns, conflicting configs, and TODO density.",
|
|
6
|
+
domain: "Code Coherence & Consistency",
|
|
7
|
+
rulePrefix: "COH",
|
|
8
|
+
systemPrompt: `You are Judge Multi-Turn Coherence — an expert in detecting self-contradicting and incoherent code patterns.
|
|
9
|
+
|
|
10
|
+
YOUR EVALUATION CRITERIA:
|
|
11
|
+
1. **Duplicate Definitions**: Multiple function/class/variable declarations with the same name in the same scope.
|
|
12
|
+
2. **Contradictory Assignments**: Boolean or config variables assigned opposite values in close proximity without branching logic.
|
|
13
|
+
3. **Dead Code After Returns**: Unreachable statements after return/throw/break/continue.
|
|
14
|
+
4. **Conflicting Configuration**: Config objects that set contradictory options (e.g., debug: true and production: true simultaneously).
|
|
15
|
+
5. **TODO Density**: Files where more than 20% of functions contain TODO/FIXME/HACK comments indicating incomplete implementation.
|
|
16
|
+
|
|
17
|
+
SEVERITY MAPPING:
|
|
18
|
+
- **critical**: Contradictory security settings (e.g., auth enabled and bypassed simultaneously)
|
|
19
|
+
- **high**: Duplicate function definitions that shadow each other, dead code after returns
|
|
20
|
+
- **medium**: Contradictory boolean assignments, conflicting configuration
|
|
21
|
+
- **low**: Excessive TODO density, minor style inconsistencies
|
|
22
|
+
|
|
23
|
+
ADVERSARIAL MANDATE:
|
|
24
|
+
- Treat every contradiction as a potential logic bug.
|
|
25
|
+
- Do NOT assume dead code is intentionally left for debugging.`,
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=multi-turn-coherence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-turn-coherence.js","sourceRoot":"","sources":["../../src/judges/multi-turn-coherence.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,uBAAuB,GAAoB;IACtD,EAAE,EAAE,sBAAsB;IAC1B,IAAI,EAAE,sBAAsB;IAC5B,WAAW,EACT,qFAAqF;QACrF,sFAAsF;IACxF,MAAM,EAAE,8BAA8B;IACtC,UAAU,EAAE,KAAK;IACjB,YAAY,EAAE;;;;;;;;;;;;;;;;;+DAiB+C;CAC9D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patches/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patches/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAkqE3C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAyC9E"}
|
package/dist/patches/index.js
CHANGED
|
@@ -1484,6 +1484,378 @@ const PATCH_RULES = [
|
|
|
1484
1484
|
return { oldText: m[0], newText: `${m[1]}${m[2]}@v4 /* TODO: pin to specific SHA */` };
|
|
1485
1485
|
},
|
|
1486
1486
|
},
|
|
1487
|
+
// ═══ Authentication Patches ═══
|
|
1488
|
+
// AUTH: plaintext password comparison → bcrypt
|
|
1489
|
+
{
|
|
1490
|
+
match: /AUTH-.*|plaintext.*password|password.*comparison.*plain|comparing.*password/i,
|
|
1491
|
+
generate: (line) => {
|
|
1492
|
+
const m = line.match(/(password|passwd)\s*===?\s*(\w+)/i);
|
|
1493
|
+
if (!m)
|
|
1494
|
+
return null;
|
|
1495
|
+
return { oldText: m[0], newText: `await bcrypt.compare(${m[2]}, hashedPassword) /* TODO: use bcrypt */` };
|
|
1496
|
+
},
|
|
1497
|
+
},
|
|
1498
|
+
// AUTH: session without expiry → add maxAge
|
|
1499
|
+
{
|
|
1500
|
+
match: /session.*expir|session.*timeout|session.*no.*expir/i,
|
|
1501
|
+
generate: (line) => {
|
|
1502
|
+
const m = line.match(/(session\s*\(\s*\{[^}]*?)(\})/);
|
|
1503
|
+
if (!m)
|
|
1504
|
+
return null;
|
|
1505
|
+
if (/maxAge|expires/.test(m[1]))
|
|
1506
|
+
return null;
|
|
1507
|
+
return { oldText: m[0], newText: `${m[1]}, maxAge: 1800000 /* 30 min */}` };
|
|
1508
|
+
},
|
|
1509
|
+
},
|
|
1510
|
+
// AUTH: jwt.decode → jwt.verify
|
|
1511
|
+
{
|
|
1512
|
+
match: /jwt.*decode.*verify|unverified.*jwt|jwt.*without.*verif/i,
|
|
1513
|
+
generate: (line) => {
|
|
1514
|
+
const m = line.match(/jwt\.decode\s*\(/);
|
|
1515
|
+
if (!m)
|
|
1516
|
+
return null;
|
|
1517
|
+
return { oldText: m[0], newText: `jwt.verify(` };
|
|
1518
|
+
},
|
|
1519
|
+
},
|
|
1520
|
+
// AUTH: missing rate limit on login
|
|
1521
|
+
{
|
|
1522
|
+
match: /brute.*force|login.*rate.*limit|missing.*rate.*limit.*auth/i,
|
|
1523
|
+
generate: (line) => {
|
|
1524
|
+
const m = line.match(/^(\s*)((?:app|router)\.post\s*\(\s*["']\/(?:login|auth|signin)["'])/);
|
|
1525
|
+
if (!m)
|
|
1526
|
+
return null;
|
|
1527
|
+
return {
|
|
1528
|
+
oldText: m[0],
|
|
1529
|
+
newText: `${m[1]}/* TODO: add rate limiting middleware (e.g., express-rate-limit) */\n${m[1]}${m[2]}`,
|
|
1530
|
+
};
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
// ═══ Data Security Patches ═══
|
|
1534
|
+
// DSEC: storing PII in localStorage
|
|
1535
|
+
{
|
|
1536
|
+
match: /pii.*localStorage|sensitive.*local.*storage|localStorage.*personal/i,
|
|
1537
|
+
generate: (line) => {
|
|
1538
|
+
const m = line.match(/localStorage\.(setItem)\s*\(\s*(["'][^"']*(?:email|ssn|phone|name|address|dob)[^"']*["'])/i);
|
|
1539
|
+
if (!m)
|
|
1540
|
+
return null;
|
|
1541
|
+
return {
|
|
1542
|
+
oldText: `localStorage.${m[1]}`,
|
|
1543
|
+
newText: `sessionStorage.setItem /* TODO: encrypt or use httpOnly cookie instead */`,
|
|
1544
|
+
};
|
|
1545
|
+
},
|
|
1546
|
+
},
|
|
1547
|
+
// DSEC: logging PII fields
|
|
1548
|
+
{
|
|
1549
|
+
match: /pii.*log|logging.*personal|log.*sensitive|LOGPRIV/i,
|
|
1550
|
+
generate: (line) => {
|
|
1551
|
+
const m = line.match(/(console\.log|logger\.\w+)\s*\([^)]*\b(email|ssn|password|creditCard|phoneNumber|socialSecurity)\b/i);
|
|
1552
|
+
if (!m)
|
|
1553
|
+
return null;
|
|
1554
|
+
return { oldText: m[2], newText: `[REDACTED:${m[2]}]` };
|
|
1555
|
+
},
|
|
1556
|
+
},
|
|
1557
|
+
// ═══ Accessibility Patches ═══
|
|
1558
|
+
// A11Y: img without alt → add alt=""
|
|
1559
|
+
{
|
|
1560
|
+
match: /A11Y-.*|missing.*alt|img.*alt|image.*alt/i,
|
|
1561
|
+
generate: (line) => {
|
|
1562
|
+
const m = line.match(/(<img\s+(?:(?!alt=)[^>])*?)(\/?>)/i);
|
|
1563
|
+
if (!m)
|
|
1564
|
+
return null;
|
|
1565
|
+
if (/alt=/i.test(m[1]))
|
|
1566
|
+
return null;
|
|
1567
|
+
return { oldText: m[0], newText: `${m[1]} alt="" /* TODO: add descriptive alt text */${m[2]}` };
|
|
1568
|
+
},
|
|
1569
|
+
},
|
|
1570
|
+
// A11Y: button/link without aria-label (icon-only)
|
|
1571
|
+
{
|
|
1572
|
+
match: /aria.*label|icon.*button.*access|accessible.*name/i,
|
|
1573
|
+
generate: (line) => {
|
|
1574
|
+
const m = line.match(/(<(?:button|a)\s+(?:(?!aria-label)[^>])*?)(>)/i);
|
|
1575
|
+
if (!m)
|
|
1576
|
+
return null;
|
|
1577
|
+
if (/aria-label=/.test(m[1]))
|
|
1578
|
+
return null;
|
|
1579
|
+
if (!/icon|svg|fa-|material-icon/i.test(line))
|
|
1580
|
+
return null;
|
|
1581
|
+
return { oldText: m[0], newText: `${m[1]} aria-label="TODO: describe action"${m[2]}` };
|
|
1582
|
+
},
|
|
1583
|
+
},
|
|
1584
|
+
// A11Y: onClick div → button
|
|
1585
|
+
{
|
|
1586
|
+
match: /interactive.*div|div.*onclick|click.*handler.*div/i,
|
|
1587
|
+
generate: (line) => {
|
|
1588
|
+
const m = line.match(/<div(\s+[^>]*?)onClick/i);
|
|
1589
|
+
if (!m)
|
|
1590
|
+
return null;
|
|
1591
|
+
return { oldText: `<div${m[1]}onClick`, newText: `<button${m[1]}onClick` };
|
|
1592
|
+
},
|
|
1593
|
+
},
|
|
1594
|
+
// A11Y: autocomplete off on login fields
|
|
1595
|
+
{
|
|
1596
|
+
match: /autocomplete.*off.*password|login.*autocomplete/i,
|
|
1597
|
+
generate: (line) => {
|
|
1598
|
+
const m = line.match(/(type=["']password["'][^>]*?)autocomplete=["']off["']/i);
|
|
1599
|
+
if (!m)
|
|
1600
|
+
return null;
|
|
1601
|
+
return { oldText: `autocomplete="off"`, newText: `autocomplete="current-password"` };
|
|
1602
|
+
},
|
|
1603
|
+
},
|
|
1604
|
+
// ═══ AI Code Safety Patches ═══
|
|
1605
|
+
// AICS: user input concatenated into prompt
|
|
1606
|
+
{
|
|
1607
|
+
match: /AICS-.*|prompt.*inject|user.*input.*prompt|llm.*inject/i,
|
|
1608
|
+
generate: (line) => {
|
|
1609
|
+
const m = line.match(/(`[^`]*\$\{(?:req\.body|req\.query|request\.\w+|user[Ii]nput|input)\b[^}]*\}[^`]*`)/);
|
|
1610
|
+
if (!m)
|
|
1611
|
+
return null;
|
|
1612
|
+
return { oldText: m[0], newText: `/* TODO: sanitize user input before LLM prompt */ ${m[0]}` };
|
|
1613
|
+
},
|
|
1614
|
+
},
|
|
1615
|
+
// AICS: LLM output in innerHTML
|
|
1616
|
+
{
|
|
1617
|
+
match: /llm.*output.*html|ai.*output.*innerhtml|inject.*llm.*output/i,
|
|
1618
|
+
generate: (line) => {
|
|
1619
|
+
const m = line.match(/\.innerHTML\s*=\s*(\w+(?:\.(?:response|output|text|completion|content))?)/);
|
|
1620
|
+
if (!m)
|
|
1621
|
+
return null;
|
|
1622
|
+
return { oldText: `.innerHTML = ${m[1]}`, newText: `.textContent = ${m[1]} /* sanitize LLM output */` };
|
|
1623
|
+
},
|
|
1624
|
+
},
|
|
1625
|
+
// ═══ Compliance Patches ═══
|
|
1626
|
+
// COMP: cookie without consent check
|
|
1627
|
+
{
|
|
1628
|
+
match: /cookie.*consent|gdpr.*cookie|tracking.*consent/i,
|
|
1629
|
+
generate: (line) => {
|
|
1630
|
+
const m = line.match(/^(\s*)(document\.cookie\s*=)/);
|
|
1631
|
+
if (!m)
|
|
1632
|
+
return null;
|
|
1633
|
+
return { oldText: m[0], newText: `${m[1]}if (hasUserConsent()) ${m[2]} /* TODO: implement consent check */` };
|
|
1634
|
+
},
|
|
1635
|
+
},
|
|
1636
|
+
// COMP: collecting data without purpose
|
|
1637
|
+
{
|
|
1638
|
+
match: /data.*purpose|purpose.*limitation|gdpr.*purpose/i,
|
|
1639
|
+
generate: (line) => {
|
|
1640
|
+
const m = line.match(/^(\s*)((?:const|let|var)\s+\w+\s*=\s*(?:req\.body|request\.(?:form|json)))/);
|
|
1641
|
+
if (!m)
|
|
1642
|
+
return null;
|
|
1643
|
+
return {
|
|
1644
|
+
oldText: m[0],
|
|
1645
|
+
newText: `${m[1]}/* TODO: validate data collection purpose against privacy policy */\n${m[1]}${m[2]}`,
|
|
1646
|
+
};
|
|
1647
|
+
},
|
|
1648
|
+
},
|
|
1649
|
+
// ═══ Performance Patches ═══
|
|
1650
|
+
// PERF: regex in loop → pre-compile
|
|
1651
|
+
{
|
|
1652
|
+
match: /regex.*loop|regexp.*inside.*loop|pattern.*repeated/i,
|
|
1653
|
+
generate: (line) => {
|
|
1654
|
+
const m = line.match(/(new\s+RegExp\s*\(\s*(["'][^"']+["'])\s*(?:,\s*["'][^"']*["'])?\s*\))/);
|
|
1655
|
+
if (!m)
|
|
1656
|
+
return null;
|
|
1657
|
+
return { oldText: m[0], newText: `/* TODO: pre-compile regex outside loop */ ${m[0]}` };
|
|
1658
|
+
},
|
|
1659
|
+
},
|
|
1660
|
+
// PERF: Array spread in reduce → push
|
|
1661
|
+
{
|
|
1662
|
+
match: /spread.*reduce|array.*spread.*accumul|O\(n.\).*reduce/i,
|
|
1663
|
+
generate: (line) => {
|
|
1664
|
+
const m = line.match(/\[\.\.\.(acc|accumulator|result)\s*,/);
|
|
1665
|
+
if (!m)
|
|
1666
|
+
return null;
|
|
1667
|
+
return { oldText: `[...${m[1]},`, newText: `/* TODO: use push() instead of spread in reduce */ [...${m[1]},` };
|
|
1668
|
+
},
|
|
1669
|
+
},
|
|
1670
|
+
// ═══ Observability Patches ═══
|
|
1671
|
+
// OBS: catch without logging
|
|
1672
|
+
{
|
|
1673
|
+
match: /catch.*no.*log|error.*not.*logged|swallow.*without.*log/i,
|
|
1674
|
+
generate: (line) => {
|
|
1675
|
+
const m = line.match(/^(\s*)\}\s*catch\s*\((\w+)\)\s*\{\s*$/);
|
|
1676
|
+
if (!m)
|
|
1677
|
+
return null;
|
|
1678
|
+
return { oldText: m[0], newText: `${m[0]}\n${m[1]} console.error('Error:', ${m[2]});` };
|
|
1679
|
+
},
|
|
1680
|
+
},
|
|
1681
|
+
// ═══ IaC Security Patches ═══
|
|
1682
|
+
// IaC: Dockerfile USER root → USER node
|
|
1683
|
+
{
|
|
1684
|
+
match: /docker.*root|container.*root|USER.*root/i,
|
|
1685
|
+
generate: (line) => {
|
|
1686
|
+
const m = line.match(/^USER\s+root\s*$/);
|
|
1687
|
+
if (!m)
|
|
1688
|
+
return null;
|
|
1689
|
+
return { oldText: m[0], newText: `USER node` };
|
|
1690
|
+
},
|
|
1691
|
+
},
|
|
1692
|
+
// IaC: Terraform allow all ingress → restrict CIDR
|
|
1693
|
+
{
|
|
1694
|
+
match: /ingress.*0\.0\.0\.0|open.*ingress|unrestricted.*ingress/i,
|
|
1695
|
+
generate: (line) => {
|
|
1696
|
+
const m = line.match(/(cidr_blocks\s*=\s*\[)"0\.0\.0\.0\/0"(\])/);
|
|
1697
|
+
if (!m)
|
|
1698
|
+
return null;
|
|
1699
|
+
return { oldText: m[0], newText: `${m[1]}"10.0.0.0/8"${m[2]} /* TODO: restrict to your CIDR */` };
|
|
1700
|
+
},
|
|
1701
|
+
},
|
|
1702
|
+
// IaC: Kubernetes privileged container → drop privileges
|
|
1703
|
+
{
|
|
1704
|
+
match: /privileged.*true|container.*privileged|security.*context.*privileged/i,
|
|
1705
|
+
generate: (line) => {
|
|
1706
|
+
const m = line.match(/privileged:\s*true/);
|
|
1707
|
+
if (!m)
|
|
1708
|
+
return null;
|
|
1709
|
+
return { oldText: m[0], newText: `privileged: false` };
|
|
1710
|
+
},
|
|
1711
|
+
},
|
|
1712
|
+
// ═══ Database Patches ═══
|
|
1713
|
+
// DB: missing index comment
|
|
1714
|
+
{
|
|
1715
|
+
match: /missing.*index|no.*index.*query|full.*table.*scan/i,
|
|
1716
|
+
generate: (line) => {
|
|
1717
|
+
const m = line.match(/^(\s*)((?:SELECT|FROM|WHERE)\b.*)$/i);
|
|
1718
|
+
if (!m)
|
|
1719
|
+
return null;
|
|
1720
|
+
return { oldText: m[0], newText: `${m[1]}/* TODO: ensure WHERE-clause columns are indexed */ ${m[2]}` };
|
|
1721
|
+
},
|
|
1722
|
+
},
|
|
1723
|
+
// DB: transaction missing rollback
|
|
1724
|
+
{
|
|
1725
|
+
match: /transaction.*rollback|missing.*rollback|no.*rollback/i,
|
|
1726
|
+
generate: (line) => {
|
|
1727
|
+
const m = line.match(/^(\s*)((?:await\s+)?(?:client|conn|db|connection)\.query\s*\(\s*["']BEGIN)/);
|
|
1728
|
+
if (!m)
|
|
1729
|
+
return null;
|
|
1730
|
+
return { oldText: m[0], newText: `${m[1]}/* TODO: wrap in try/catch with ROLLBACK on error */\n${m[1]}${m[2]}` };
|
|
1731
|
+
},
|
|
1732
|
+
},
|
|
1733
|
+
// ═══ Concurrency Patches ═══
|
|
1734
|
+
// CONC: shared mutable variable → atomics hint
|
|
1735
|
+
{
|
|
1736
|
+
match: /race.*condition|shared.*mutable|concurrent.*access/i,
|
|
1737
|
+
generate: (line) => {
|
|
1738
|
+
const m = line.match(/^(\s*)(let|var)\s+(\w+)\s*=\s*(\d+)/);
|
|
1739
|
+
if (!m)
|
|
1740
|
+
return null;
|
|
1741
|
+
return { oldText: m[0], newText: `${m[1]}/* TODO: protect with mutex/lock */ ${m[2]} ${m[3]} = ${m[4]}` };
|
|
1742
|
+
},
|
|
1743
|
+
},
|
|
1744
|
+
// ═══ API Design Patches ═══
|
|
1745
|
+
// API: missing pagination
|
|
1746
|
+
{
|
|
1747
|
+
match: /missing.*pagination|no.*pagina|unbounded.*list/i,
|
|
1748
|
+
generate: (line) => {
|
|
1749
|
+
const m = line.match(/(\.find\s*\(\s*\{[^}]*\})\s*\)/);
|
|
1750
|
+
if (!m)
|
|
1751
|
+
return null;
|
|
1752
|
+
return { oldText: m[0], newText: `${m[1]}).limit(100) /* TODO: add proper pagination */` };
|
|
1753
|
+
},
|
|
1754
|
+
},
|
|
1755
|
+
// API: missing content-type validation
|
|
1756
|
+
{
|
|
1757
|
+
match: /content.*type.*valid|missing.*content.*type/i,
|
|
1758
|
+
generate: (line) => {
|
|
1759
|
+
const m = line.match(/^(\s*)((?:app|router)\.(post|put|patch)\s*\()/);
|
|
1760
|
+
if (!m)
|
|
1761
|
+
return null;
|
|
1762
|
+
return { oldText: m[0], newText: `${m[1]}/* TODO: validate Content-Type header */\n${m[1]}${m[2]}` };
|
|
1763
|
+
},
|
|
1764
|
+
},
|
|
1765
|
+
// ═══ Internationalization Patches ═══
|
|
1766
|
+
// I18N: hardcoded user-facing string → i18n key
|
|
1767
|
+
{
|
|
1768
|
+
match: /I18N-.*|hardcoded.*string|user.*facing.*literal/i,
|
|
1769
|
+
generate: (line) => {
|
|
1770
|
+
const m = line.match(/((?:label|title|message|placeholder|text|heading)\s*[:=]\s*)(["'])([A-Z][a-z].*?)\2/);
|
|
1771
|
+
if (!m)
|
|
1772
|
+
return null;
|
|
1773
|
+
const key = m[3]
|
|
1774
|
+
.toLowerCase()
|
|
1775
|
+
.replace(/\s+/g, "_")
|
|
1776
|
+
.replace(/[^a-z0-9_]/g, "")
|
|
1777
|
+
.slice(0, 30);
|
|
1778
|
+
return {
|
|
1779
|
+
oldText: `${m[1]}${m[2]}${m[3]}${m[2]}`,
|
|
1780
|
+
newText: `${m[1]}t("${key}") /* was: ${m[2]}${m[3]}${m[2]} */`,
|
|
1781
|
+
};
|
|
1782
|
+
},
|
|
1783
|
+
},
|
|
1784
|
+
// ═══ Scalability Patches ═══
|
|
1785
|
+
// SCAL: in-memory session store → comment
|
|
1786
|
+
{
|
|
1787
|
+
match: /in.*memory.*session|session.*memory.*store|express.*session.*default/i,
|
|
1788
|
+
generate: (line) => {
|
|
1789
|
+
const m = line.match(/^(\s*)(app\.use\s*\(\s*session\s*\(\s*\{)/);
|
|
1790
|
+
if (!m)
|
|
1791
|
+
return null;
|
|
1792
|
+
return {
|
|
1793
|
+
oldText: m[0],
|
|
1794
|
+
newText: `${m[1]}/* TODO: use Redis/database session store for multi-instance */ ${m[2]}`,
|
|
1795
|
+
};
|
|
1796
|
+
},
|
|
1797
|
+
},
|
|
1798
|
+
// ═══ Sovereignty Patches ═══
|
|
1799
|
+
// SOV: data sent to external analytics
|
|
1800
|
+
{
|
|
1801
|
+
match: /data.*third.*party|analytics.*external|sov.*data.*transfer/i,
|
|
1802
|
+
generate: (line) => {
|
|
1803
|
+
const m = line.match(/^(\s*)((?:fetch|axios\.\w+|https?\.(?:get|post))\s*\(\s*["']https?:\/\/(?:analytics|tracking|telemetry)\b)/);
|
|
1804
|
+
if (!m)
|
|
1805
|
+
return null;
|
|
1806
|
+
return { oldText: m[0], newText: `${m[1]}/* TODO: verify data residency compliance before sending */ ${m[2]}` };
|
|
1807
|
+
},
|
|
1808
|
+
},
|
|
1809
|
+
// ═══ Framework Safety Patches ═══
|
|
1810
|
+
// FW: Express without helmet → add helmet
|
|
1811
|
+
{
|
|
1812
|
+
match: /missing.*helmet|no.*security.*headers|express.*headers/i,
|
|
1813
|
+
generate: (line) => {
|
|
1814
|
+
const m = line.match(/^(\s*)(const\s+app\s*=\s*express\s*\(\s*\)\s*;?)/);
|
|
1815
|
+
if (!m)
|
|
1816
|
+
return null;
|
|
1817
|
+
return {
|
|
1818
|
+
oldText: m[0],
|
|
1819
|
+
newText: `${m[1]}const helmet = require("helmet"); /* TODO: npm install helmet */\n${m[1]}${m[2]}\n${m[1]}app.use(helmet());`,
|
|
1820
|
+
};
|
|
1821
|
+
},
|
|
1822
|
+
},
|
|
1823
|
+
// FW: Flask debug mode in production
|
|
1824
|
+
{
|
|
1825
|
+
match: /flask.*debug|debug.*production|app\.run.*debug/i,
|
|
1826
|
+
generate: (line) => {
|
|
1827
|
+
const m = line.match(/app\.run\s*\(\s*([^)]*)debug\s*=\s*True/);
|
|
1828
|
+
if (!m)
|
|
1829
|
+
return null;
|
|
1830
|
+
return { oldText: `debug=True`, newText: `debug=os.environ.get("FLASK_DEBUG", "false") == "true"` };
|
|
1831
|
+
},
|
|
1832
|
+
},
|
|
1833
|
+
// FW: Django SECRET_KEY hardcoded
|
|
1834
|
+
{
|
|
1835
|
+
match: /django.*secret|SECRET_KEY.*hardcoded|hardcoded.*django/i,
|
|
1836
|
+
generate: (line) => {
|
|
1837
|
+
const m = line.match(/^(\s*)SECRET_KEY\s*=\s*(["'])[^"']+\2/);
|
|
1838
|
+
if (!m)
|
|
1839
|
+
return null;
|
|
1840
|
+
return {
|
|
1841
|
+
oldText: m[0],
|
|
1842
|
+
newText: `${m[1]}SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me-in-production")`,
|
|
1843
|
+
};
|
|
1844
|
+
},
|
|
1845
|
+
},
|
|
1846
|
+
// ═══ Supply Chain Patches ═══
|
|
1847
|
+
// DEP: importing from CDN without SRI
|
|
1848
|
+
{
|
|
1849
|
+
match: /sri.*missing|subresource.*integrity|cdn.*integrity/i,
|
|
1850
|
+
generate: (line) => {
|
|
1851
|
+
const m = line.match(/(<script\s+src=["']https?:\/\/cdn[^"']*["'])(\s*>)/i);
|
|
1852
|
+
if (!m)
|
|
1853
|
+
return null;
|
|
1854
|
+
if (/integrity=/i.test(m[1]))
|
|
1855
|
+
return null;
|
|
1856
|
+
return { oldText: m[0], newText: `${m[1]} integrity="TODO:sha384-hash" crossorigin="anonymous"${m[2]}` };
|
|
1857
|
+
},
|
|
1858
|
+
},
|
|
1487
1859
|
];
|
|
1488
1860
|
const MULTI_LINE_PATCH_RULES = [
|
|
1489
1861
|
// ── Multi-line empty catch block → re-throw with error parameter ──
|