@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.
- package/CHANGELOG.md +82 -0
- package/dist/api.d.ts +3 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +3 -1
- package/dist/api.js.map +1 -1
- package/dist/ast/structural-parser.d.ts.map +1 -1
- package/dist/ast/structural-parser.js +148 -3
- package/dist/ast/structural-parser.js.map +1 -1
- package/dist/auto-tune.d.ts +147 -0
- package/dist/auto-tune.d.ts.map +1 -0
- package/dist/auto-tune.js +374 -0
- package/dist/auto-tune.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/auto-detect.js.map +1 -1
- package/dist/commands/benchmark-expanded-2.d.ts +13 -0
- package/dist/commands/benchmark-expanded-2.d.ts.map +1 -0
- package/dist/commands/benchmark-expanded-2.js +5531 -0
- package/dist/commands/benchmark-expanded-2.js.map +1 -0
- package/dist/commands/benchmark-expanded.d.ts +13 -0
- package/dist/commands/benchmark-expanded.d.ts.map +1 -0
- package/dist/commands/benchmark-expanded.js +2600 -0
- package/dist/commands/benchmark-expanded.js.map +1 -0
- package/dist/commands/benchmark.d.ts +1 -0
- package/dist/commands/benchmark.d.ts.map +1 -1
- package/dist/commands/benchmark.js +176 -3
- package/dist/commands/benchmark.js.map +1 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +13 -0
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/lsp.d.ts.map +1 -1
- package/dist/commands/lsp.js.map +1 -1
- package/dist/commands/review.d.ts +8 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +175 -13
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/tune.js.map +1 -1
- package/dist/dedup.d.ts.map +1 -1
- package/dist/dedup.js +24 -2
- package/dist/dedup.js.map +1 -1
- package/dist/disk-cache.js.map +1 -1
- package/dist/evaluators/accessibility.d.ts.map +1 -1
- package/dist/evaluators/accessibility.js +18 -4
- package/dist/evaluators/accessibility.js.map +1 -1
- package/dist/evaluators/agent-instructions.d.ts.map +1 -1
- package/dist/evaluators/agent-instructions.js +52 -1
- package/dist/evaluators/agent-instructions.js.map +1 -1
- package/dist/evaluators/authentication.d.ts.map +1 -1
- package/dist/evaluators/authentication.js +51 -2
- package/dist/evaluators/authentication.js.map +1 -1
- package/dist/evaluators/caching.d.ts.map +1 -1
- package/dist/evaluators/caching.js +5 -4
- package/dist/evaluators/caching.js.map +1 -1
- package/dist/evaluators/ci-cd.d.ts.map +1 -1
- package/dist/evaluators/ci-cd.js +23 -0
- package/dist/evaluators/ci-cd.js.map +1 -1
- package/dist/evaluators/compliance.d.ts.map +1 -1
- package/dist/evaluators/compliance.js +5 -1
- package/dist/evaluators/compliance.js.map +1 -1
- package/dist/evaluators/concurrency.d.ts.map +1 -1
- package/dist/evaluators/concurrency.js +34 -0
- package/dist/evaluators/concurrency.js.map +1 -1
- package/dist/evaluators/cybersecurity.d.ts.map +1 -1
- package/dist/evaluators/cybersecurity.js +231 -0
- package/dist/evaluators/cybersecurity.js.map +1 -1
- package/dist/evaluators/false-positive-review.js +25 -20
- package/dist/evaluators/false-positive-review.js.map +1 -1
- package/dist/evaluators/hallucination-detection.d.ts +3 -0
- package/dist/evaluators/hallucination-detection.d.ts.map +1 -0
- package/dist/evaluators/hallucination-detection.js +463 -0
- package/dist/evaluators/hallucination-detection.js.map +1 -0
- package/dist/evaluators/iac-security.d.ts.map +1 -1
- package/dist/evaluators/iac-security.js +18 -1
- package/dist/evaluators/iac-security.js.map +1 -1
- package/dist/evaluators/index.d.ts.map +1 -1
- package/dist/evaluators/index.js +18 -6
- package/dist/evaluators/index.js.map +1 -1
- package/dist/evaluators/maintainability.d.ts.map +1 -1
- package/dist/evaluators/maintainability.js +46 -0
- package/dist/evaluators/maintainability.js.map +1 -1
- package/dist/evaluators/observability.d.ts.map +1 -1
- package/dist/evaluators/observability.js +19 -1
- package/dist/evaluators/observability.js.map +1 -1
- package/dist/evaluators/reliability.d.ts.map +1 -1
- package/dist/evaluators/reliability.js +17 -1
- package/dist/evaluators/reliability.js.map +1 -1
- package/dist/evaluators/scalability.js +1 -1
- package/dist/evaluators/scalability.js.map +1 -1
- package/dist/evaluators/security.d.ts +13 -0
- package/dist/evaluators/security.d.ts.map +1 -0
- package/dist/evaluators/security.js +529 -0
- package/dist/evaluators/security.js.map +1 -0
- package/dist/evaluators/shared.d.ts.map +1 -1
- package/dist/evaluators/shared.js +15 -3
- package/dist/evaluators/shared.js.map +1 -1
- package/dist/evaluators/software-practices.d.ts.map +1 -1
- package/dist/evaluators/software-practices.js +20 -0
- package/dist/evaluators/software-practices.js.map +1 -1
- package/dist/evaluators/testing.d.ts.map +1 -1
- package/dist/evaluators/testing.js +3 -3
- package/dist/evaluators/testing.js.map +1 -1
- package/dist/evaluators/ux.d.ts.map +1 -1
- package/dist/evaluators/ux.js +10 -2
- package/dist/evaluators/ux.js.map +1 -1
- package/dist/github-app.d.ts +96 -0
- package/dist/github-app.d.ts.map +1 -0
- package/dist/github-app.js +541 -0
- package/dist/github-app.js.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/judges/hallucination-detection.d.ts +3 -0
- package/dist/judges/hallucination-detection.d.ts.map +1 -0
- package/dist/judges/hallucination-detection.js +30 -0
- package/dist/judges/hallucination-detection.js.map +1 -0
- package/dist/judges/index.d.ts.map +1 -1
- package/dist/judges/index.js +8 -0
- package/dist/judges/index.js.map +1 -1
- package/dist/judges/security.d.ts +3 -0
- package/dist/judges/security.d.ts.map +1 -0
- package/dist/judges/security.js +28 -0
- package/dist/judges/security.js.map +1 -0
- package/dist/language-patterns.d.ts.map +1 -1
- package/dist/language-patterns.js +12 -4
- package/dist/language-patterns.js.map +1 -1
- package/dist/patches/index.d.ts.map +1 -1
- package/dist/patches/index.js +501 -0
- package/dist/patches/index.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/server.json +3 -3
package/dist/patches/index.js
CHANGED
|
@@ -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");
|