@kevinrabun/judges 3.23.10 → 3.23.13

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 (133) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/api.d.ts +3 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +3 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/ast/structural-parser.d.ts.map +1 -1
  7. package/dist/ast/structural-parser.js +148 -3
  8. package/dist/ast/structural-parser.js.map +1 -1
  9. package/dist/auto-tune.d.ts +147 -0
  10. package/dist/auto-tune.d.ts.map +1 -0
  11. package/dist/auto-tune.js +374 -0
  12. package/dist/auto-tune.js.map +1 -0
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +7 -0
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/auto-detect.js.map +1 -1
  17. package/dist/commands/benchmark-expanded-2.d.ts +13 -0
  18. package/dist/commands/benchmark-expanded-2.d.ts.map +1 -0
  19. package/dist/commands/benchmark-expanded-2.js +5531 -0
  20. package/dist/commands/benchmark-expanded-2.js.map +1 -0
  21. package/dist/commands/benchmark-expanded.d.ts +13 -0
  22. package/dist/commands/benchmark-expanded.d.ts.map +1 -0
  23. package/dist/commands/benchmark-expanded.js +2600 -0
  24. package/dist/commands/benchmark-expanded.js.map +1 -0
  25. package/dist/commands/benchmark.d.ts +1 -0
  26. package/dist/commands/benchmark.d.ts.map +1 -1
  27. package/dist/commands/benchmark.js +176 -3
  28. package/dist/commands/benchmark.js.map +1 -1
  29. package/dist/commands/doctor.js.map +1 -1
  30. package/dist/commands/feedback.d.ts.map +1 -1
  31. package/dist/commands/feedback.js +13 -0
  32. package/dist/commands/feedback.js.map +1 -1
  33. package/dist/commands/lsp.d.ts.map +1 -1
  34. package/dist/commands/lsp.js.map +1 -1
  35. package/dist/commands/review.d.ts +8 -0
  36. package/dist/commands/review.d.ts.map +1 -1
  37. package/dist/commands/review.js +175 -13
  38. package/dist/commands/review.js.map +1 -1
  39. package/dist/commands/tune.js.map +1 -1
  40. package/dist/dedup.d.ts.map +1 -1
  41. package/dist/dedup.js +24 -2
  42. package/dist/dedup.js.map +1 -1
  43. package/dist/disk-cache.js.map +1 -1
  44. package/dist/evaluators/accessibility.d.ts.map +1 -1
  45. package/dist/evaluators/accessibility.js +18 -4
  46. package/dist/evaluators/accessibility.js.map +1 -1
  47. package/dist/evaluators/agent-instructions.d.ts.map +1 -1
  48. package/dist/evaluators/agent-instructions.js +52 -1
  49. package/dist/evaluators/agent-instructions.js.map +1 -1
  50. package/dist/evaluators/authentication.d.ts.map +1 -1
  51. package/dist/evaluators/authentication.js +51 -2
  52. package/dist/evaluators/authentication.js.map +1 -1
  53. package/dist/evaluators/caching.d.ts.map +1 -1
  54. package/dist/evaluators/caching.js +5 -4
  55. package/dist/evaluators/caching.js.map +1 -1
  56. package/dist/evaluators/ci-cd.d.ts.map +1 -1
  57. package/dist/evaluators/ci-cd.js +23 -0
  58. package/dist/evaluators/ci-cd.js.map +1 -1
  59. package/dist/evaluators/compliance.d.ts.map +1 -1
  60. package/dist/evaluators/compliance.js +5 -1
  61. package/dist/evaluators/compliance.js.map +1 -1
  62. package/dist/evaluators/concurrency.d.ts.map +1 -1
  63. package/dist/evaluators/concurrency.js +34 -0
  64. package/dist/evaluators/concurrency.js.map +1 -1
  65. package/dist/evaluators/cybersecurity.d.ts.map +1 -1
  66. package/dist/evaluators/cybersecurity.js +231 -0
  67. package/dist/evaluators/cybersecurity.js.map +1 -1
  68. package/dist/evaluators/false-positive-review.js +25 -20
  69. package/dist/evaluators/false-positive-review.js.map +1 -1
  70. package/dist/evaluators/hallucination-detection.d.ts +3 -0
  71. package/dist/evaluators/hallucination-detection.d.ts.map +1 -0
  72. package/dist/evaluators/hallucination-detection.js +463 -0
  73. package/dist/evaluators/hallucination-detection.js.map +1 -0
  74. package/dist/evaluators/iac-security.d.ts.map +1 -1
  75. package/dist/evaluators/iac-security.js +18 -1
  76. package/dist/evaluators/iac-security.js.map +1 -1
  77. package/dist/evaluators/index.d.ts.map +1 -1
  78. package/dist/evaluators/index.js +18 -6
  79. package/dist/evaluators/index.js.map +1 -1
  80. package/dist/evaluators/maintainability.d.ts.map +1 -1
  81. package/dist/evaluators/maintainability.js +46 -0
  82. package/dist/evaluators/maintainability.js.map +1 -1
  83. package/dist/evaluators/observability.d.ts.map +1 -1
  84. package/dist/evaluators/observability.js +19 -1
  85. package/dist/evaluators/observability.js.map +1 -1
  86. package/dist/evaluators/reliability.d.ts.map +1 -1
  87. package/dist/evaluators/reliability.js +17 -1
  88. package/dist/evaluators/reliability.js.map +1 -1
  89. package/dist/evaluators/scalability.js +1 -1
  90. package/dist/evaluators/scalability.js.map +1 -1
  91. package/dist/evaluators/security.d.ts +13 -0
  92. package/dist/evaluators/security.d.ts.map +1 -0
  93. package/dist/evaluators/security.js +529 -0
  94. package/dist/evaluators/security.js.map +1 -0
  95. package/dist/evaluators/shared.d.ts.map +1 -1
  96. package/dist/evaluators/shared.js +15 -3
  97. package/dist/evaluators/shared.js.map +1 -1
  98. package/dist/evaluators/software-practices.d.ts.map +1 -1
  99. package/dist/evaluators/software-practices.js +20 -0
  100. package/dist/evaluators/software-practices.js.map +1 -1
  101. package/dist/evaluators/testing.d.ts.map +1 -1
  102. package/dist/evaluators/testing.js +3 -3
  103. package/dist/evaluators/testing.js.map +1 -1
  104. package/dist/evaluators/ux.d.ts.map +1 -1
  105. package/dist/evaluators/ux.js +10 -2
  106. package/dist/evaluators/ux.js.map +1 -1
  107. package/dist/github-app.d.ts +96 -0
  108. package/dist/github-app.d.ts.map +1 -0
  109. package/dist/github-app.js +541 -0
  110. package/dist/github-app.js.map +1 -0
  111. package/dist/index.js +8 -0
  112. package/dist/index.js.map +1 -1
  113. package/dist/judges/hallucination-detection.d.ts +3 -0
  114. package/dist/judges/hallucination-detection.d.ts.map +1 -0
  115. package/dist/judges/hallucination-detection.js +30 -0
  116. package/dist/judges/hallucination-detection.js.map +1 -0
  117. package/dist/judges/index.d.ts.map +1 -1
  118. package/dist/judges/index.js +8 -0
  119. package/dist/judges/index.js.map +1 -1
  120. package/dist/judges/security.d.ts +3 -0
  121. package/dist/judges/security.d.ts.map +1 -0
  122. package/dist/judges/security.js +28 -0
  123. package/dist/judges/security.js.map +1 -0
  124. package/dist/language-patterns.d.ts.map +1 -1
  125. package/dist/language-patterns.js +12 -4
  126. package/dist/language-patterns.js.map +1 -1
  127. package/dist/patches/index.d.ts.map +1 -1
  128. package/dist/patches/index.js +501 -0
  129. package/dist/patches/index.js.map +1 -1
  130. package/dist/types.d.ts +1 -1
  131. package/dist/types.d.ts.map +1 -1
  132. package/package.json +1 -1
  133. package/server.json +3 -3
@@ -1085,6 +1085,405 @@ const PATCH_RULES = [
1085
1085
  return { oldText: m[0], newText: `${m[0]} /* TODO: add app.use(helmet()) for security headers */` };
1086
1086
  },
1087
1087
  },
1088
+ // ── Ruby Patches ──
1089
+ // Ruby: system/exec → Shellwords.shellescape
1090
+ {
1091
+ match: /command.*inject|shell.*inject|os.*command|dangerous.*system/i,
1092
+ generate: (line) => {
1093
+ const m = line.match(/\bsystem\s*\(\s*(["'])(.+?)\1\s*\+\s*(\w+)\s*\)/);
1094
+ if (!m)
1095
+ return null;
1096
+ return { oldText: m[0], newText: `system(${m[1]}${m[2]}#{Shellwords.shellescape(${m[3]})}${m[1]})` };
1097
+ },
1098
+ },
1099
+ // Ruby: exec with string interpolation → shellescape
1100
+ {
1101
+ match: /command.*inject|shell.*inject|os.*command/i,
1102
+ generate: (line) => {
1103
+ const m = line.match(/`([^`]*#\{(\w+)\}[^`]*)`/);
1104
+ if (!m)
1105
+ return null;
1106
+ return { oldText: m[0], newText: `Shellwords.shelljoin(["${m[1].replace(/#\{\w+\}/, '", ' + m[2] + ', "')}"])` };
1107
+ },
1108
+ },
1109
+ // Ruby: eval → safer alternative
1110
+ {
1111
+ match: /dangerous.*eval|eval.*usage|code.*inject/i,
1112
+ generate: (line) => {
1113
+ const m = line.match(/\beval\s*\(\s*(\w+)\s*\)/);
1114
+ if (!m)
1115
+ return null;
1116
+ // Only match Ruby-style (no 'new Function' or JS context)
1117
+ if (line.includes("new Function") || line.includes("JSON.parse"))
1118
+ return null;
1119
+ return { oldText: m[0], newText: `JSON.parse(${m[1]}) # TODO: eliminate eval — use safe deserialization` };
1120
+ },
1121
+ },
1122
+ // Ruby: send with user input → allowlist
1123
+ {
1124
+ match: /dynamic.*method|unsafe.*send|method.*inject/i,
1125
+ generate: (line) => {
1126
+ const m = line.match(/(\.send\s*\(\s*)(\w+)\s*\)/);
1127
+ if (!m)
1128
+ return null;
1129
+ return { oldText: m[0], newText: `${m[1]}${m[2]}) # TODO: validate against allowlist before .send` };
1130
+ },
1131
+ },
1132
+ // Ruby: open-uri with user URL → validate
1133
+ {
1134
+ match: /ssrf|open-uri.*untrusted|server.*side.*request/i,
1135
+ generate: (line) => {
1136
+ const m = line.match(/(URI\.open\s*\(\s*)(\w+)\s*\)/);
1137
+ if (!m)
1138
+ return null;
1139
+ return { oldText: m[0], newText: `${m[1]}${m[2]}) # TODO: validate URL against allowlist to prevent SSRF` };
1140
+ },
1141
+ },
1142
+ // Ruby: yaml.load → YAML.safe_load
1143
+ {
1144
+ match: /unsafe.*yaml|yaml.*load|insecure.*yaml|deserialization/i,
1145
+ generate: (line) => {
1146
+ const m = line.match(/YAML\.load\s*\(/);
1147
+ if (!m)
1148
+ return null;
1149
+ return { oldText: m[0], newText: "YAML.safe_load(" };
1150
+ },
1151
+ },
1152
+ // Ruby: Marshal.load → JSON.parse
1153
+ {
1154
+ match: /unsafe.*deserialization|marshal.*untrusted|insecure.*deserialization/i,
1155
+ generate: (line) => {
1156
+ const m = line.match(/Marshal\.load\s*\(/);
1157
+ if (!m)
1158
+ return null;
1159
+ return { oldText: m[0], newText: "JSON.parse( # TODO: replace Marshal with safe serialization" };
1160
+ },
1161
+ },
1162
+ // Ruby: String interpolation in SQL → parameterized
1163
+ {
1164
+ match: /sql.*inject|string.*interpol.*sql|sql.*concat/i,
1165
+ generate: (line) => {
1166
+ const m = line.match(/(\.(?:where|find_by_sql|execute)\s*\(\s*)"([^"]*?)#\{(\w+)\}([^"]*)"/);
1167
+ if (!m)
1168
+ return null;
1169
+ return { oldText: m[0], newText: `${m[1]}"${m[2]}?${m[4]}", ${m[3]}` };
1170
+ },
1171
+ },
1172
+ // Ruby: Digest::MD5 → Digest::SHA256
1173
+ {
1174
+ match: /weak.*hash|insecure.*hash|md5|sha1/i,
1175
+ generate: (line) => {
1176
+ const m = line.match(/Digest::(MD5|SHA1)/);
1177
+ if (!m)
1178
+ return null;
1179
+ return { oldText: m[0], newText: "Digest::SHA256" };
1180
+ },
1181
+ },
1182
+ // Ruby: render inline with user input → sanitize
1183
+ {
1184
+ match: /xss|cross.*site.*script|render.*untrusted/i,
1185
+ generate: (line) => {
1186
+ const m = line.match(/(render\s+inline:\s*)(\w+)/);
1187
+ if (!m)
1188
+ return null;
1189
+ return { oldText: m[0], newText: `${m[1]}ERB::Util.html_escape(${m[2]})` };
1190
+ },
1191
+ },
1192
+ // ── PHP Patches ──
1193
+ // PHP: mysql_query → PDO prepared statement marker
1194
+ {
1195
+ match: /deprecated.*mysql|mysql_query|sql.*inject/i,
1196
+ generate: (line) => {
1197
+ const m = line.match(/mysql_query\s*\(\s*(".*?"|\$\w+)\s*\)/);
1198
+ if (!m)
1199
+ return null;
1200
+ return { oldText: m[0], newText: `$pdo->prepare(${m[1]})->execute() /* TODO: use PDO with bound parameters */` };
1201
+ },
1202
+ },
1203
+ // PHP: eval → safer alternative
1204
+ {
1205
+ match: /dangerous.*eval|eval.*usage|code.*inject/i,
1206
+ generate: (line) => {
1207
+ const m = line.match(/\beval\s*\(\s*(\$\w+)\s*\)/);
1208
+ if (!m)
1209
+ return null;
1210
+ return { oldText: m[0], newText: `json_decode(${m[1]}, true) /* TODO: eliminate eval — use safe parsing */` };
1211
+ },
1212
+ },
1213
+ // PHP: shell_exec/exec → escapeshellarg
1214
+ {
1215
+ match: /command.*inject|shell.*inject|os.*command/i,
1216
+ generate: (line) => {
1217
+ const m = line.match(/((?:shell_exec|exec|system|passthru)\s*\(\s*(?:["'].*?["']\s*\.\s*))(\$\w+)/);
1218
+ if (!m)
1219
+ return null;
1220
+ return { oldText: m[2], newText: `escapeshellarg(${m[2]})` };
1221
+ },
1222
+ },
1223
+ // PHP: md5/sha1 → password_hash for passwords
1224
+ {
1225
+ match: /weak.*hash|password.*hash|insecure.*hash/i,
1226
+ generate: (line) => {
1227
+ const m = line.match(/\bmd5\s*\(\s*(\$\w+)\s*\)/);
1228
+ if (!m)
1229
+ return null;
1230
+ return { oldText: m[0], newText: `password_hash(${m[1]}, PASSWORD_BCRYPT)` };
1231
+ },
1232
+ },
1233
+ // PHP: extract($_POST/GET/REQUEST) → manual assignment
1234
+ {
1235
+ match: /variable.*inject|mass.*assign|extract.*superglobal/i,
1236
+ generate: (line) => {
1237
+ const m = line.match(/extract\s*\(\s*\$_(POST|GET|REQUEST)\s*\)/);
1238
+ if (!m)
1239
+ return null;
1240
+ return { oldText: m[0], newText: `/* TODO: assign specific variables from \$_${m[1]} instead of extract() */` };
1241
+ },
1242
+ },
1243
+ // PHP: htmlspecialchars missing → add
1244
+ {
1245
+ match: /xss|cross.*site.*script|output.*encod|unescaped.*output/i,
1246
+ generate: (line) => {
1247
+ const m = line.match(/(echo\s+)(\$\w+)\s*;/);
1248
+ if (!m)
1249
+ return null;
1250
+ return { oldText: m[0], newText: `${m[1]}htmlspecialchars(${m[2]}, ENT_QUOTES, 'UTF-8');` };
1251
+ },
1252
+ },
1253
+ // PHP: unserialize → json_decode
1254
+ {
1255
+ match: /unsafe.*deserialization|unserialize.*untrusted|insecure.*deserialization/i,
1256
+ generate: (line) => {
1257
+ const m = line.match(/unserialize\s*\(\s*(\$\w+)\s*\)/);
1258
+ if (!m)
1259
+ return null;
1260
+ return { oldText: m[0], newText: `json_decode(${m[1]}, true) /* TODO: replace unserialize with safe format */` };
1261
+ },
1262
+ },
1263
+ // PHP: file_get_contents with user URL → validate
1264
+ {
1265
+ match: /ssrf|server.*side.*request|unvalidated.*url/i,
1266
+ generate: (line) => {
1267
+ const m = line.match(/(file_get_contents\s*\(\s*)(\$\w+)\s*\)/);
1268
+ if (!m)
1269
+ return null;
1270
+ return { oldText: m[0], newText: `${m[1]}${m[2]}) /* TODO: validate URL against allowlist to prevent SSRF */` };
1271
+ },
1272
+ },
1273
+ // PHP: rand() → random_int() (cryptographic)
1274
+ {
1275
+ match: /insecure.*random|weak.*random|predictable.*random/i,
1276
+ generate: (line) => {
1277
+ const m = line.match(/\brand\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)/);
1278
+ if (!m)
1279
+ return null;
1280
+ return { oldText: m[0], newText: `random_int(${m[1]}, ${m[2]})` };
1281
+ },
1282
+ },
1283
+ // ── Kotlin Patches ──
1284
+ // Kotlin: !! (force unwrap) → safe call + default
1285
+ {
1286
+ match: /force.*unwrap|non-null.*assert|!!.*operator|null.*safety/i,
1287
+ generate: (line) => {
1288
+ const m = line.match(/(\w+)!!(\.\w+)/);
1289
+ if (!m)
1290
+ return null;
1291
+ return { oldText: m[0], newText: `${m[1]}?${m[2]} ?: throw IllegalStateException("${m[1]} was null")` };
1292
+ },
1293
+ },
1294
+ // Kotlin: Thread.sleep → delay (coroutines)
1295
+ {
1296
+ match: /thread.*sleep|blocking.*call|blocking.*thread/i,
1297
+ generate: (line) => {
1298
+ const m = line.match(/Thread\.sleep\s*\(\s*(\d+)\s*\)/);
1299
+ if (!m)
1300
+ return null;
1301
+ return { oldText: m[0], newText: `delay(${m[1]}) // TODO: ensure calling function is suspend` };
1302
+ },
1303
+ },
1304
+ // Kotlin: var → val (immutability)
1305
+ {
1306
+ match: /mutable.*variable|var.*instead.*val|prefer.*immutable/i,
1307
+ generate: (line) => {
1308
+ const m = line.match(/\bvar\s+(\w+)\s*[:=]/);
1309
+ if (!m)
1310
+ return null;
1311
+ return { oldText: `var ${m[1]}`, newText: `val ${m[1]}` };
1312
+ },
1313
+ },
1314
+ // Kotlin: catching generic Exception → specific
1315
+ {
1316
+ match: /catch.*generic|broad.*exception|catching.*exception/i,
1317
+ generate: (line) => {
1318
+ const m = line.match(/catch\s*\(\s*(e|ex|err)\s*:\s*Exception\s*\)/);
1319
+ if (!m)
1320
+ return null;
1321
+ return { oldText: m[0], newText: `catch (${m[1]}: Exception) /* TODO: use specific exception type */` };
1322
+ },
1323
+ },
1324
+ // Kotlin: hardcoded URL → BuildConfig
1325
+ {
1326
+ match: /hardcoded.*url|url.*hardcoded|base.*url.*literal/i,
1327
+ generate: (line) => {
1328
+ const m = line.match(/(["'])(https?:\/\/[^"']+)\1/);
1329
+ if (!m)
1330
+ return null;
1331
+ return { oldText: m[0], newText: `BuildConfig.BASE_URL /* TODO: move URL to build config */` };
1332
+ },
1333
+ },
1334
+ // Kotlin: String SQL concatenation → parameterized
1335
+ {
1336
+ match: /sql.*inject|sql.*concat|string.*template.*sql/i,
1337
+ generate: (line) => {
1338
+ const m = line.match(/(rawQuery\s*\(\s*)"([^"]*)\$(\w+)/);
1339
+ if (!m)
1340
+ return null;
1341
+ return { oldText: m[0], newText: `${m[1]}"${m[2]}?", arrayOf(${m[3]}` };
1342
+ },
1343
+ },
1344
+ // ── Swift Patches ──
1345
+ // Swift: force unwrap (!) → optional binding
1346
+ {
1347
+ match: /force.*unwrap|implicit.*unwrap|!.*operator.*crash/i,
1348
+ generate: (line) => {
1349
+ const m = line.match(/(\w+)!\s*\./);
1350
+ if (!m)
1351
+ return null;
1352
+ // Don't match Kotlin !! or negation
1353
+ if (line.includes("!!"))
1354
+ return null;
1355
+ return { oldText: m[0], newText: `${m[1]}?. // TODO: use if-let or guard-let for safe unwrapping` };
1356
+ },
1357
+ },
1358
+ // Swift: try! → do-catch reminder
1359
+ {
1360
+ match: /force.*try|try!.*crash|unhandled.*throw/i,
1361
+ generate: (line) => {
1362
+ const m = line.match(/\btry!\s+/);
1363
+ if (!m)
1364
+ return null;
1365
+ return { oldText: m[0], newText: "try /* TODO: wrap in do-catch */ " };
1366
+ },
1367
+ },
1368
+ // Swift: implicitly unwrapped optional → regular optional
1369
+ {
1370
+ match: /implicit.*unwrap.*optional|iuo.*declaration/i,
1371
+ generate: (line) => {
1372
+ const m = line.match(/(:\s*\w+)!/);
1373
+ if (!m)
1374
+ return null;
1375
+ return { oldText: m[0], newText: `${m[1]}?` };
1376
+ },
1377
+ },
1378
+ // Swift: NSLog → os_log (structured logging)
1379
+ {
1380
+ match: /nslog.*os_log|nslog.*instead|structured.*log/i,
1381
+ generate: (line) => {
1382
+ const m = line.match(/NSLog\s*\(([^)]*)\)/);
1383
+ if (!m)
1384
+ return null;
1385
+ return { oldText: m[0], newText: `os_log(.info, ${m[1]})` };
1386
+ },
1387
+ },
1388
+ // Swift: UserDefaults for sensitive data → Keychain
1389
+ {
1390
+ match: /userdefaults.*sensitive|insecure.*storage|keychain.*instead/i,
1391
+ generate: (line) => {
1392
+ const m = line.match(/(UserDefaults\.standard\.set\s*\([^,]+,\s*forKey:\s*)(["'][^"']*(?:password|token|secret|key)[^"']*["'])\s*\)/i);
1393
+ if (!m)
1394
+ return null;
1395
+ return {
1396
+ oldText: m[0],
1397
+ newText: `KeychainWrapper.standard.set(/* value */, forKey: ${m[2]}) /* TODO: use Keychain for sensitive data */`,
1398
+ };
1399
+ },
1400
+ },
1401
+ // Swift: print() → Logger
1402
+ {
1403
+ match: /print.*instead.*log|print.*production|remove.*print/i,
1404
+ generate: (line) => {
1405
+ const m = line.match(/\bprint\s*\(([^)]*)\)/);
1406
+ if (!m)
1407
+ return null;
1408
+ return { oldText: m[0], newText: `Logger().info(${m[1]})` };
1409
+ },
1410
+ },
1411
+ // ── Additional Cross-Language Patches ──
1412
+ // Terraform: overly broad CIDR → restrict
1413
+ {
1414
+ match: /overly.*broad.*cidr|0\.0\.0\.0\/0|unrestricted.*ingress|open.*to.*world/i,
1415
+ generate: (line) => {
1416
+ const m = line.match(/(["'])0\.0\.0\.0\/0\1/);
1417
+ if (!m)
1418
+ return null;
1419
+ return { oldText: m[0], newText: `${m[1]}10.0.0.0/8${m[1]} /* TODO: restrict to your CIDR range */` };
1420
+ },
1421
+ },
1422
+ // Terraform: public access enabled → private
1423
+ {
1424
+ match: /public.*access.*enabled|publicly.*accessible|public.*bucket/i,
1425
+ generate: (line) => {
1426
+ const m = line.match(/((?:publicly_accessible|public_access|public)\s*=\s*)true/i);
1427
+ if (!m)
1428
+ return null;
1429
+ return { oldText: m[0], newText: `${m[1]}false` };
1430
+ },
1431
+ },
1432
+ // Terraform: encryption disabled → enabled
1433
+ {
1434
+ match: /encryption.*disabled|unencrypted.*storage|encrypt.*at.*rest/i,
1435
+ generate: (line) => {
1436
+ const m = line.match(/((?:encrypted|encryption_enabled|encrypt)\s*=\s*)false/i);
1437
+ if (!m)
1438
+ return null;
1439
+ return { oldText: m[0], newText: `${m[1]}true` };
1440
+ },
1441
+ },
1442
+ // Bicep/ARM: HTTP allowed → HTTPS only
1443
+ {
1444
+ match: /http.*allowed|https.*only|transport.*security/i,
1445
+ generate: (line) => {
1446
+ const m = line.match(/(httpsOnly\s*:\s*)false/);
1447
+ if (!m)
1448
+ return null;
1449
+ return { oldText: m[0], newText: `${m[1]}true` };
1450
+ },
1451
+ },
1452
+ // ── Dockerfile additional patches ──
1453
+ // ADD → COPY (Docker best practice)
1454
+ {
1455
+ match: /add.*instead.*copy|docker.*add|prefer.*copy/i,
1456
+ generate: (line) => {
1457
+ const m = line.match(/^(\s*)ADD\s+(?!https?:)/);
1458
+ if (!m)
1459
+ return null;
1460
+ return { oldText: `${m[1]}ADD `, newText: `${m[1]}COPY ` };
1461
+ },
1462
+ },
1463
+ // Missing HEALTHCHECK
1464
+ {
1465
+ match: /missing.*healthcheck|no.*healthcheck|docker.*health/i,
1466
+ generate: (line) => {
1467
+ const m = line.match(/^(\s*)(CMD\s+.+)$/);
1468
+ if (!m)
1469
+ return null;
1470
+ return {
1471
+ oldText: m[0],
1472
+ newText: `HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/ || exit 1\n${m[1]}${m[2]}`,
1473
+ };
1474
+ },
1475
+ },
1476
+ // ── GitHub Actions / CI Patches ──
1477
+ // Unpinned action version → pin to SHA
1478
+ {
1479
+ match: /unpinned.*action|action.*version.*pin|uses.*latest/i,
1480
+ generate: (line) => {
1481
+ const m = line.match(/(uses:\s*)(\S+)@(master|main|latest)\b/);
1482
+ if (!m)
1483
+ return null;
1484
+ return { oldText: m[0], newText: `${m[1]}${m[2]}@v4 /* TODO: pin to specific SHA */` };
1485
+ },
1486
+ },
1088
1487
  ];
1089
1488
  const MULTI_LINE_PATCH_RULES = [
1090
1489
  // ── Multi-line empty catch block → re-throw with error parameter ──
@@ -1398,6 +1797,108 @@ const MULTI_LINE_PATCH_RULES = [
1398
1797
  return { oldText, newText, startLine: findingLine, endLine: findingLine };
1399
1798
  },
1400
1799
  },
1800
+ // ── Ruby: begin/rescue without specific exception → add specific ──
1801
+ {
1802
+ match: /bare.*rescue|rescue.*generic|rescue.*exception/i,
1803
+ contextLines: 4,
1804
+ generate: (windowLines, windowStart, findingLine) => {
1805
+ const idx = findingLine - windowStart;
1806
+ if (idx < 0 || idx >= windowLines.length)
1807
+ return null;
1808
+ const line = windowLines[idx];
1809
+ const m = line.match(/^(\s*)rescue\s*$/);
1810
+ if (!m)
1811
+ return null;
1812
+ return {
1813
+ oldText: line,
1814
+ newText: `${m[1]}rescue StandardError => e # TODO: use specific exception class`,
1815
+ startLine: findingLine,
1816
+ endLine: findingLine,
1817
+ };
1818
+ },
1819
+ },
1820
+ // ── PHP: missing CSRF token in form → add hidden field ──
1821
+ {
1822
+ match: /csrf.*missing|cross.*site.*request|no.*csrf/i,
1823
+ contextLines: 5,
1824
+ generate: (windowLines, windowStart, findingLine) => {
1825
+ const idx = findingLine - windowStart;
1826
+ if (idx < 0 || idx >= windowLines.length)
1827
+ return null;
1828
+ const line = windowLines[idx];
1829
+ const m = line.match(/^(\s*)(<form\s[^>]*method=["']post["'][^>]*>)/i);
1830
+ if (!m)
1831
+ return null;
1832
+ const [, indent, formTag] = m;
1833
+ return {
1834
+ oldText: line,
1835
+ newText: `${indent}${formTag}\n${indent} <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>" />`,
1836
+ startLine: findingLine,
1837
+ endLine: findingLine,
1838
+ };
1839
+ },
1840
+ },
1841
+ // ── Kotlin: runOnUiThread with long operation → coroutine ──
1842
+ {
1843
+ match: /blocking.*main.*thread|ui.*thread.*block|network.*main/i,
1844
+ contextLines: 5,
1845
+ generate: (windowLines, windowStart, findingLine) => {
1846
+ const idx = findingLine - windowStart;
1847
+ if (idx < 0 || idx >= windowLines.length)
1848
+ return null;
1849
+ const line = windowLines[idx];
1850
+ const m = line.match(/^(\s*)runOnUiThread\s*\{/);
1851
+ if (!m)
1852
+ return null;
1853
+ return {
1854
+ oldText: line,
1855
+ newText: `${m[1]}lifecycleScope.launch(Dispatchers.IO) { // TODO: move I/O-bound work off main thread`,
1856
+ startLine: findingLine,
1857
+ endLine: findingLine,
1858
+ };
1859
+ },
1860
+ },
1861
+ // ── Swift: DispatchQueue.main.sync → async ──
1862
+ {
1863
+ match: /deadlock|main.*thread.*sync|dispatch.*main.*sync/i,
1864
+ contextLines: 3,
1865
+ generate: (windowLines, windowStart, findingLine) => {
1866
+ const idx = findingLine - windowStart;
1867
+ if (idx < 0 || idx >= windowLines.length)
1868
+ return null;
1869
+ const line = windowLines[idx];
1870
+ const m = line.match(/^(\s*)DispatchQueue\.main\.sync\b/);
1871
+ if (!m)
1872
+ return null;
1873
+ return {
1874
+ oldText: "DispatchQueue.main.sync",
1875
+ newText: "DispatchQueue.main.async",
1876
+ startLine: findingLine,
1877
+ endLine: findingLine,
1878
+ };
1879
+ },
1880
+ },
1881
+ // ── Terraform: missing logging/monitoring block ──
1882
+ {
1883
+ match: /missing.*logging|no.*monitoring|audit.*log.*disabled/i,
1884
+ contextLines: 5,
1885
+ generate: (windowLines, windowStart, findingLine) => {
1886
+ const idx = findingLine - windowStart;
1887
+ if (idx < 0 || idx >= windowLines.length)
1888
+ return null;
1889
+ const line = windowLines[idx];
1890
+ const m = line.match(/^(\s*)(resource\s+"[^"]+"\s+"[^"]+"\s*\{)/);
1891
+ if (!m)
1892
+ return null;
1893
+ const [, indent, resourceBlock] = m;
1894
+ return {
1895
+ oldText: line,
1896
+ newText: `${indent}${resourceBlock}\n${indent} # TODO: add logging/monitoring configuration\n${indent} # logging { enabled = true }`,
1897
+ startLine: findingLine,
1898
+ endLine: findingLine,
1899
+ };
1900
+ },
1901
+ },
1401
1902
  ];
1402
1903
  export function enrichWithPatches(findings, code) {
1403
1904
  const lines = code.split("\n");