@kevinrabun/judges 3.20.14 → 3.22.0

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 (93) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/api.d.ts +42 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +49 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/ast/taint-tracker.d.ts +3 -1
  7. package/dist/ast/taint-tracker.d.ts.map +1 -1
  8. package/dist/ast/taint-tracker.js +523 -12
  9. package/dist/ast/taint-tracker.js.map +1 -1
  10. package/dist/cli.d.ts +13 -0
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +124 -19
  13. package/dist/cli.js.map +1 -1
  14. package/dist/commands/benchmark.d.ts +28 -0
  15. package/dist/commands/benchmark.d.ts.map +1 -1
  16. package/dist/commands/benchmark.js +1058 -1
  17. package/dist/commands/benchmark.js.map +1 -1
  18. package/dist/config.d.ts +17 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +88 -0
  21. package/dist/config.js.map +1 -1
  22. package/dist/dedup.d.ts +23 -0
  23. package/dist/dedup.d.ts.map +1 -1
  24. package/dist/dedup.js +123 -0
  25. package/dist/dedup.js.map +1 -1
  26. package/dist/evaluators/authentication.d.ts +2 -2
  27. package/dist/evaluators/authentication.d.ts.map +1 -1
  28. package/dist/evaluators/authentication.js +26 -2
  29. package/dist/evaluators/authentication.js.map +1 -1
  30. package/dist/evaluators/cybersecurity.d.ts +2 -2
  31. package/dist/evaluators/cybersecurity.d.ts.map +1 -1
  32. package/dist/evaluators/cybersecurity.js +58 -5
  33. package/dist/evaluators/cybersecurity.js.map +1 -1
  34. package/dist/evaluators/framework-safety.d.ts.map +1 -1
  35. package/dist/evaluators/framework-safety.js +855 -365
  36. package/dist/evaluators/framework-safety.js.map +1 -1
  37. package/dist/evaluators/index.d.ts +1 -1
  38. package/dist/evaluators/index.d.ts.map +1 -1
  39. package/dist/evaluators/index.js +6 -2
  40. package/dist/evaluators/index.js.map +1 -1
  41. package/dist/evaluators/performance.d.ts +2 -2
  42. package/dist/evaluators/performance.d.ts.map +1 -1
  43. package/dist/evaluators/performance.js +33 -4
  44. package/dist/evaluators/performance.js.map +1 -1
  45. package/dist/evaluators/project.d.ts.map +1 -1
  46. package/dist/evaluators/project.js +223 -13
  47. package/dist/evaluators/project.js.map +1 -1
  48. package/dist/evaluators/shared.d.ts +31 -3
  49. package/dist/evaluators/shared.d.ts.map +1 -1
  50. package/dist/evaluators/shared.js +145 -11
  51. package/dist/evaluators/shared.js.map +1 -1
  52. package/dist/evaluators/v2.d.ts.map +1 -1
  53. package/dist/evaluators/v2.js +8 -0
  54. package/dist/evaluators/v2.js.map +1 -1
  55. package/dist/formatters/csv.d.ts +17 -0
  56. package/dist/formatters/csv.d.ts.map +1 -0
  57. package/dist/formatters/csv.js +54 -0
  58. package/dist/formatters/csv.js.map +1 -0
  59. package/dist/language-patterns.d.ts +136 -0
  60. package/dist/language-patterns.d.ts.map +1 -1
  61. package/dist/language-patterns.js +155 -1
  62. package/dist/language-patterns.js.map +1 -1
  63. package/dist/patches/index.d.ts.map +1 -1
  64. package/dist/patches/index.js +210 -0
  65. package/dist/patches/index.js.map +1 -1
  66. package/dist/presets.d.ts +14 -0
  67. package/dist/presets.d.ts.map +1 -1
  68. package/dist/presets.js +72 -0
  69. package/dist/presets.js.map +1 -1
  70. package/dist/scoring.d.ts.map +1 -1
  71. package/dist/scoring.js +43 -4
  72. package/dist/scoring.js.map +1 -1
  73. package/dist/tools/register-fix.d.ts +6 -0
  74. package/dist/tools/register-fix.d.ts.map +1 -0
  75. package/dist/tools/register-fix.js +153 -0
  76. package/dist/tools/register-fix.js.map +1 -0
  77. package/dist/tools/register-workflow.d.ts.map +1 -1
  78. package/dist/tools/register-workflow.js +79 -0
  79. package/dist/tools/register-workflow.js.map +1 -1
  80. package/dist/tools/register-workspace.d.ts +3 -0
  81. package/dist/tools/register-workspace.d.ts.map +1 -0
  82. package/dist/tools/register-workspace.js +215 -0
  83. package/dist/tools/register-workspace.js.map +1 -0
  84. package/dist/tools/register.d.ts +1 -1
  85. package/dist/tools/register.d.ts.map +1 -1
  86. package/dist/tools/register.js +5 -1
  87. package/dist/tools/register.js.map +1 -1
  88. package/dist/tools/schemas.d.ts +2 -2
  89. package/dist/types.d.ts +24 -2
  90. package/dist/types.d.ts.map +1 -1
  91. package/judgesrc.schema.json +17 -2
  92. package/package.json +1 -1
  93. package/server.json +30 -2
@@ -208,13 +208,480 @@ function getFnName(node) {
208
208
  }
209
209
  return undefined;
210
210
  }
211
+ const PYTHON_PATTERNS = {
212
+ sources: [
213
+ {
214
+ pattern: /\brequest\.(?:form|args|json|data|values|files|cookies|headers)\b(?:\[|\.get\s*\()/i,
215
+ kind: "http-param",
216
+ },
217
+ { pattern: /\brequest\.GET\b(?:\[|\.get\s*\()/i, kind: "http-param" },
218
+ { pattern: /\brequest\.POST\b(?:\[|\.get\s*\()/i, kind: "http-param" },
219
+ { pattern: /\brequest\.(?:query_params|query_string)\b/i, kind: "http-param" },
220
+ { pattern: /\bflask\.request\.\w+/i, kind: "http-param" },
221
+ { pattern: /\binput\s*\(/i, kind: "user-input" },
222
+ { pattern: /\bsys\.stdin\b/i, kind: "user-input" },
223
+ { pattern: /\bos\.environ\b(?:\[|\.get\s*\()/i, kind: "environment" },
224
+ { pattern: /\burlparse\s*\(|parse_qs\s*\(/i, kind: "url-param" },
225
+ { pattern: /\bopen\s*\(.*\)\.read/i, kind: "external-data" },
226
+ { pattern: /\brequests\.(?:get|post|put|delete)\s*\(/i, kind: "external-data" },
227
+ { pattern: /\bjson\.loads?\s*\(/i, kind: "external-data" },
228
+ ],
229
+ sinks: [
230
+ { pattern: /\bexec\s*\(/i, kind: "code-execution" },
231
+ { pattern: /\beval\s*\(/i, kind: "code-execution" },
232
+ { pattern: /\bcompile\s*\(.*\).*\bexec\b/i, kind: "code-execution" },
233
+ { pattern: /\bos\.system\s*\(/i, kind: "command-exec" },
234
+ { pattern: /\bos\.popen\s*\(/i, kind: "command-exec" },
235
+ {
236
+ pattern: /\bsubprocess\.(?:Popen|run|call|check_output|check_call|getoutput|getstatusoutput)\s*\(/i,
237
+ kind: "command-exec",
238
+ },
239
+ { pattern: /\bcursor\.execute\s*\(/i, kind: "sql-query" },
240
+ { pattern: /\b(?:connection|conn|db)\.execute\s*\(/i, kind: "sql-query" },
241
+ { pattern: /\braw\s*\(\s*["'`]?\s*(?:SELECT|INSERT|UPDATE|DELETE)\b/i, kind: "sql-query" },
242
+ { pattern: /\.(?:extra|raw)\s*\(/i, kind: "sql-query" },
243
+ { pattern: /\brender_template_string\s*\(/i, kind: "template" },
244
+ { pattern: /\bTemplate\s*\(.*\)\.render\s*\(/i, kind: "template" },
245
+ { pattern: /\bJinja2\.\w*\.from_string\s*\(/i, kind: "template" },
246
+ { pattern: /\bmarkup\s*\(.*\+/i, kind: "xss" },
247
+ { pattern: /\bopen\s*\(.*user|path|file|name/i, kind: "path-traversal" },
248
+ { pattern: /\bredirect\s*\(/i, kind: "redirect" },
249
+ { pattern: /\bpickle\.loads?\s*\(/i, kind: "deserialization" },
250
+ { pattern: /\byaml\.(?:load|unsafe_load)\s*\(/i, kind: "deserialization" },
251
+ { pattern: /\bmarshal\.loads?\s*\(/i, kind: "deserialization" },
252
+ ],
253
+ sanitizers: [
254
+ /\bbleach\.clean\s*\(/i,
255
+ /\bmarkup_safe\b/i,
256
+ /\bescape\s*\(/i,
257
+ /\bMarkup\b/i,
258
+ /\bquote\s*\(/i,
259
+ /\bshlex\.quote\s*\(/i,
260
+ /\bshellescape\s*\(/i,
261
+ /\bsanitize\w*\s*\(/i,
262
+ /\bvalidator\.\w+\s*\(/i,
263
+ /\bpydantic\b/i,
264
+ /\b%s\b.*\bexecute\s*\(/i, // parameterized query
265
+ /\bparamstyle\b/i,
266
+ /\bsqlalchemy\.text\s*\(/i,
267
+ ],
268
+ assignPattern: /^\s*(\w+)\s*(?::\s*\w[\w[\], |]*\s*)?=\s*(.+)/,
269
+ guards: [
270
+ /if[ \t]+(?:not[ \t]+)?isinstance\s*\(/i,
271
+ /if[ \t]+(?:not[ \t]+)?\w+\s*(?:is|==|!=)\s*None/i,
272
+ /raise[ \t]+(?:ValueError|TypeError|ValidationError)/i,
273
+ /assert[ \t]+isinstance\s*\(/i,
274
+ /\.validate\s*\(|\.is_valid\s*\(/i,
275
+ ],
276
+ };
277
+ const JAVA_PATTERNS = {
278
+ sources: [
279
+ { pattern: /\b(?:request|req|httpRequest)\.getParameter\s*\(/i, kind: "http-param" },
280
+ { pattern: /\brequest\.getAttribute\s*\(/i, kind: "http-param" },
281
+ { pattern: /\brequest\.getHeader\s*\(/i, kind: "http-param" },
282
+ { pattern: /\brequest\.getQueryString\s*\(/i, kind: "http-param" },
283
+ { pattern: /\brequest\.getInputStream\s*\(/i, kind: "http-param" },
284
+ { pattern: /\brequest\.getReader\s*\(/i, kind: "http-param" },
285
+ { pattern: /\brequest\.getCookies\s*\(/i, kind: "http-param" },
286
+ { pattern: /\b@RequestParam\b/i, kind: "http-param" },
287
+ { pattern: /\b@PathVariable\b/i, kind: "url-param" },
288
+ { pattern: /\b@RequestBody\b/i, kind: "http-param" },
289
+ { pattern: /\b@RequestHeader\b/i, kind: "http-param" },
290
+ { pattern: /\bSystem\.getenv\s*\(/i, kind: "environment" },
291
+ { pattern: /\bScanner\s*\(\s*System\.in\b/i, kind: "user-input" },
292
+ { pattern: /\bBufferedReader\b.*\bInputStreamReader\b.*\bSystem\.in\b/i, kind: "user-input" },
293
+ { pattern: /\bargs\[/i, kind: "user-input" },
294
+ { pattern: /\bnew\s+ObjectMapper\b.*\.read/i, kind: "external-data" },
295
+ { pattern: /\bURL\s*\(.*\)\.openStream\s*\(/i, kind: "external-data" },
296
+ ],
297
+ sinks: [
298
+ { pattern: /\bRuntime\.getRuntime\s*\(\)\.exec\s*\(/i, kind: "command-exec" },
299
+ { pattern: /\bProcessBuilder\b/i, kind: "command-exec" },
300
+ { pattern: /\bStatement\b.*\.(?:execute|executeQuery|executeUpdate)\s*\(/i, kind: "sql-query" },
301
+ { pattern: /\.(?:createQuery|createNativeQuery)\s*\(/i, kind: "sql-query" },
302
+ { pattern: /\bString\.format\s*\(.*(?:SELECT|INSERT|UPDATE|DELETE)\b/i, kind: "sql-query" },
303
+ { pattern: /\bScriptEngine\b.*\.eval\s*\(/i, kind: "code-execution" },
304
+ { pattern: /\bClass\.forName\s*\(/i, kind: "code-execution" },
305
+ { pattern: /\.newInstance\s*\(/i, kind: "code-execution" },
306
+ { pattern: /\bXStream\b.*\.fromXML\s*\(/i, kind: "deserialization" },
307
+ { pattern: /\bObjectInputStream\b.*\.readObject\s*\(/i, kind: "deserialization" },
308
+ { pattern: /\bnew\s+File\s*\(/i, kind: "path-traversal" },
309
+ { pattern: /\bFiles\.(?:read|write|copy|move|newInputStream)\s*\(/i, kind: "path-traversal" },
310
+ { pattern: /\bresponse\.sendRedirect\s*\(/i, kind: "redirect" },
311
+ { pattern: /\.(?:forward|include)\s*\(/i, kind: "redirect" },
312
+ { pattern: /\bVelocity\b.*\.evaluate\s*\(/i, kind: "template" },
313
+ { pattern: /\bFreemarkerConfiguration\b/i, kind: "template" },
314
+ ],
315
+ sanitizers: [
316
+ /\bPreparedStatement\b/i,
317
+ /\bEncoder\.encode\s*\(/i,
318
+ /\bOWASP\.\w+\.encode\s*\(/i,
319
+ /\bHtmlUtils\.htmlEscape\s*\(/i,
320
+ /\bStringEscapeUtils\.escape\w+\s*\(/i,
321
+ /\bPattern\.matches\s*\(/i,
322
+ /\b@Valid\b/i,
323
+ /\b@Validated\b/i,
324
+ /\bBindingResult\b/i,
325
+ /\bInputValidator\b/i,
326
+ /\bwhitelist\s*\(/i,
327
+ /\bSanitizers\.\w+\s*\(/i,
328
+ ],
329
+ assignPattern: /^\s*(?:(?:final|var|String|int|long|double|boolean|byte|short|float|char|Object|List|Map|Set|Integer|Long|Double|Boolean|Optional|HttpServletRequest)\s+)*(\w+)\s*=\s*(.+);/,
330
+ guards: [
331
+ /if[ \t]*\([ \t]*\w+[ \t]*==[ \t]*null/i,
332
+ /\bObjects\.requireNonNull\s*\(/i,
333
+ /\bOptional\.ofNullable\s*\(/i,
334
+ /\bif[ \t]*\([ \t]*!?\w+\.(?:isEmpty|isBlank|matches|startsWith)\s*\(/i,
335
+ /throw[ \t]+new[ \t]+(?:IllegalArgumentException|ValidationException)/i,
336
+ ],
337
+ };
338
+ const GO_PATTERNS = {
339
+ sources: [
340
+ { pattern: /\br\.(?:FormValue|PostFormValue)\s*\(/i, kind: "http-param" },
341
+ { pattern: /\br\.URL\.Query\s*\(\)/i, kind: "http-param" },
342
+ { pattern: /\br\.Header\.Get\s*\(/i, kind: "http-param" },
343
+ { pattern: /\br\.Body\b/i, kind: "http-param" },
344
+ { pattern: /\bc\.(?:Query|Param|PostForm|FormValue|GetHeader)\s*\(/i, kind: "http-param" },
345
+ { pattern: /\bc\.(?:BindJSON|ShouldBindJSON|Bind)\s*\(/i, kind: "http-param" },
346
+ { pattern: /\bos\.Getenv\s*\(/i, kind: "environment" },
347
+ { pattern: /\bos\.Args\b/i, kind: "user-input" },
348
+ { pattern: /\bflag\.(?:String|Int|Bool|Arg)\s*\(/i, kind: "user-input" },
349
+ { pattern: /\bbufio\.NewReader\s*\(\s*os\.Stdin\b/i, kind: "user-input" },
350
+ { pattern: /\bjson\.(?:Unmarshal|NewDecoder)\s*\(/i, kind: "external-data" },
351
+ { pattern: /\bhttp\.Get\s*\(/i, kind: "external-data" },
352
+ { pattern: /\bioutil\.ReadAll\s*\(/i, kind: "external-data" },
353
+ { pattern: /\bio\.ReadAll\s*\(/i, kind: "external-data" },
354
+ ],
355
+ sinks: [
356
+ { pattern: /\bexec\.Command\s*\(/i, kind: "command-exec" },
357
+ { pattern: /\bexec\.CommandContext\s*\(/i, kind: "command-exec" },
358
+ { pattern: /\bos\.(?:StartProcess|Exec)\s*\(/i, kind: "command-exec" },
359
+ { pattern: /\bdb\.(?:Query|Exec|QueryRow|QueryContext|ExecContext)\s*\(/i, kind: "sql-query" },
360
+ { pattern: /\bsql\.(?:Open|Query)\s*\(/i, kind: "sql-query" },
361
+ { pattern: /\bfmt\.Sprintf\s*\(.*(?:SELECT|INSERT|UPDATE|DELETE)\b/i, kind: "sql-query" },
362
+ { pattern: /\btemplate\.(?:New|Must)\s*\(.*\.Parse\s*\(/i, kind: "template" },
363
+ { pattern: /\bhtml\/template\b.*\.Execute\s*\(/i, kind: "template" },
364
+ { pattern: /\btext\/template\b.*\.Execute\s*\(/i, kind: "template" },
365
+ { pattern: /\bos\.(?:Open|Create|OpenFile|ReadFile|WriteFile)\s*\(/i, kind: "path-traversal" },
366
+ { pattern: /\bfilepath\.Join\s*\(.*\+/i, kind: "path-traversal" },
367
+ { pattern: /\bhttp\.Redirect\s*\(/i, kind: "redirect" },
368
+ { pattern: /\bgob\.NewDecoder\b.*\.Decode\s*\(/i, kind: "deserialization" },
369
+ { pattern: /\bencoding\/gob\b/i, kind: "deserialization" },
370
+ { pattern: /\byaml\.Unmarshal\s*\(/i, kind: "deserialization" },
371
+ ],
372
+ sanitizers: [
373
+ /\bhtml\.EscapeString\s*\(/i,
374
+ /\burl\.QueryEscape\s*\(/i,
375
+ /\burl\.PathEscape\s*\(/i,
376
+ /\btemplate\.HTMLEscapeString\s*\(/i,
377
+ /\bstrconv\.(?:Atoi|ParseInt|ParseFloat|ParseBool)\s*\(/i,
378
+ /\bregexp\.MustCompile\b.*\.(?:MatchString|FindString)\s*\(/i,
379
+ /\bfilepath\.Clean\s*\(/i,
380
+ /\bpath\.Clean\s*\(/i,
381
+ /\bsqlx?\.\w*Prepared\b/i,
382
+ /\bValidate\.\w+\s*\(/i,
383
+ ],
384
+ assignPattern: /^\s*(?:var\s+)?(\w+)\s*(?::=|=)\s*(.+)/,
385
+ guards: [
386
+ /if[ \t]+\w+[ \t]*(?:==|!=)[ \t]*nil/i,
387
+ /if[ \t]+err[ \t]*!=[ \t]*nil/i,
388
+ /if[ \t]+!?(?:strings\.Contains|strings\.HasPrefix|regexp)\b/i,
389
+ /if[ \t]+len\s*\(\w+\)[ \t]*(?:==|!=|<|>|<=|>=)/i,
390
+ ],
391
+ };
392
+ const CSHARP_PATTERNS = {
393
+ sources: [
394
+ { pattern: /\bRequest\.(?:Form|QueryString|Query|Params|Headers|Cookies)\b/i, kind: "http-param" },
395
+ { pattern: /\bRequest\.(?:Body|InputStream)\b/i, kind: "http-param" },
396
+ { pattern: /\b\[FromQuery\]/i, kind: "http-param" },
397
+ { pattern: /\b\[FromBody\]/i, kind: "http-param" },
398
+ { pattern: /\b\[FromForm\]/i, kind: "http-param" },
399
+ { pattern: /\b\[FromHeader\]/i, kind: "http-param" },
400
+ { pattern: /\b\[FromRoute\]/i, kind: "url-param" },
401
+ { pattern: /\bHttpContext\.Request\b/i, kind: "http-param" },
402
+ { pattern: /\bEnvironment\.GetEnvironmentVariable\s*\(/i, kind: "environment" },
403
+ { pattern: /\bConsole\.ReadLine\s*\(/i, kind: "user-input" },
404
+ { pattern: /\bargs\[/i, kind: "user-input" },
405
+ { pattern: /\bHttpClient\b.*\.(?:GetAsync|PostAsync|GetStringAsync)\s*\(/i, kind: "external-data" },
406
+ { pattern: /\bJsonSerializer\.Deserialize\s*\(/i, kind: "external-data" },
407
+ { pattern: /\bJsonConvert\.DeserializeObject\s*\(/i, kind: "external-data" },
408
+ ],
409
+ sinks: [
410
+ { pattern: /\bProcess\.Start\s*\(/i, kind: "command-exec" },
411
+ { pattern: /\bProcessStartInfo\b/i, kind: "command-exec" },
412
+ { pattern: /\bSqlCommand\b.*\.(?:ExecuteReader|ExecuteNonQuery|ExecuteScalar)\s*\(/i, kind: "sql-query" },
413
+ { pattern: /\bnew\s+SqlCommand\s*\(\s*(?:\$"|".*\+)/i, kind: "sql-query" },
414
+ { pattern: /\.(?:FromSqlRaw|ExecuteSqlRaw|SqlQuery)\s*\(/i, kind: "sql-query" },
415
+ { pattern: /\bstring\.Format\s*\(.*(?:SELECT|INSERT|UPDATE|DELETE)\b/i, kind: "sql-query" },
416
+ { pattern: /\bCSharpScript\.EvaluateAsync\s*\(/i, kind: "code-execution" },
417
+ { pattern: /\bAssembly\.Load\s*\(/i, kind: "code-execution" },
418
+ { pattern: /\bActivator\.CreateInstance\s*\(/i, kind: "code-execution" },
419
+ { pattern: /\bBinaryFormatter\b.*\.Deserialize\s*\(/i, kind: "deserialization" },
420
+ { pattern: /\bXmlSerializer\b.*\.Deserialize\s*\(/i, kind: "deserialization" },
421
+ { pattern: /\bFile\.(?:ReadAllText|ReadAllBytes|ReadAllLines|Open|OpenRead)\s*\(/i, kind: "path-traversal" },
422
+ { pattern: /\bPath\.Combine\s*\(.*\+/i, kind: "path-traversal" },
423
+ { pattern: /\bResponse\.Redirect\s*\(/i, kind: "redirect" },
424
+ { pattern: /\bRedirectToAction\s*\(/i, kind: "redirect" },
425
+ { pattern: /\b@Html\.Raw\s*\(/i, kind: "xss" },
426
+ { pattern: /\bHtmlHelper\b.*\.Raw\s*\(/i, kind: "xss" },
427
+ ],
428
+ sanitizers: [
429
+ /\bHtmlEncoder\.Default\.Encode\s*\(/i,
430
+ /\bWebUtility\.HtmlEncode\s*\(/i,
431
+ /\bUrlEncoder\.Default\.Encode\s*\(/i,
432
+ /\bAntiXssEncoder\.\w+\s*\(/i,
433
+ /\b\[ValidateAntiForgeryToken\]/i,
434
+ /\bModelState\.IsValid\b/i,
435
+ /\b\[Required\]/i,
436
+ /\b\[StringLength\b/i,
437
+ /\b\[RegularExpression\b/i,
438
+ /\bSqlParameter\b/i,
439
+ /\bParameterized\b/i,
440
+ /\bAddWithValue\s*\(/i,
441
+ /\bInputValidator\b/i,
442
+ ],
443
+ assignPattern: /^\s*(?:(?:var|string|int|long|double|bool|float|decimal|object|dynamic|char|byte|List|Dictionary|IEnumerable|Task)\s*(?:<[^>]+>\s*)?)?(\w+)\s*=\s*(.+);/,
444
+ guards: [
445
+ /if[ \t]*\([ \t]*\w+[ \t]*(?:==|!=)[ \t]*null/i,
446
+ /\bif[ \t]*\([ \t]*!?string\.IsNullOrEmpty\s*\(/i,
447
+ /\bif[ \t]*\([ \t]*!?string\.IsNullOrWhiteSpace\s*\(/i,
448
+ /\?\?[ \t]+throw\b/i,
449
+ /\bargument\w*Exception\b/i,
450
+ /\bModelState\.IsValid\b/i,
451
+ ],
452
+ };
453
+ const RUST_PATTERNS = {
454
+ sources: [
455
+ { pattern: /\b(?:web|actix_web)::(?:Query|Form|Json|Path)\b/i, kind: "http-param" },
456
+ { pattern: /\breq\.(?:body|param|query|header)\s*\(/i, kind: "http-param" },
457
+ { pattern: /\baxum::extract::(?:Query|Form|Json|Path)\b/i, kind: "http-param" },
458
+ { pattern: /\bstd::env::(?:var|args)\b/i, kind: "environment" },
459
+ { pattern: /\bstd::io::stdin\b/i, kind: "user-input" },
460
+ { pattern: /\bserde_json::from_str\s*\(/i, kind: "external-data" },
461
+ { pattern: /\breqwest::(?:get|Client)\b/i, kind: "external-data" },
462
+ ],
463
+ sinks: [
464
+ { pattern: /\bCommand::new\s*\(/i, kind: "command-exec" },
465
+ { pattern: /\bstd::process::Command\b/i, kind: "command-exec" },
466
+ { pattern: /\.(?:query|execute|query_as|query_scalar)\s*\(/i, kind: "sql-query" },
467
+ { pattern: /\bformat!\s*\(.*(?:SELECT|INSERT|UPDATE|DELETE)\b/i, kind: "sql-query" },
468
+ { pattern: /\bstd::fs::(?:read_to_string|read|write|File::open)\s*\(/i, kind: "path-traversal" },
469
+ { pattern: /\bFile::open\s*\(/i, kind: "path-traversal" },
470
+ { pattern: /\bserde_json::from_value\s*\(/i, kind: "deserialization" },
471
+ { pattern: /\bbincode::deserialize\s*\(/i, kind: "deserialization" },
472
+ { pattern: /\bRedirect::to\s*\(/i, kind: "redirect" },
473
+ ],
474
+ sanitizers: [
475
+ /\bhtml_escape\s*\(/i,
476
+ /\bammonia::clean\s*\(/i,
477
+ /\bencode_safe\s*\(/i,
478
+ /\bsqlx::query!\s*\(/i,
479
+ /\b\.bind\s*\(/i,
480
+ /\.parse::<(?:i32|i64|u32|u64|f64|usize|bool)>/i,
481
+ /\bvalidate\s*\(\)/i,
482
+ /\bPath::new\s*\(.*\)\.canonicalize\s*\(/i,
483
+ ],
484
+ assignPattern: /^\s*(?:let\s+(?:mut\s+)?)?(\w+)\s*(?::\s*[\w<>&, [\]]+\s*)?=\s*(.+);/,
485
+ guards: [
486
+ /\bmatch\s+\w+\s*\{/i,
487
+ /if[ \t]+let[ \t]+Some\b/i,
488
+ /\.(?:unwrap_or|unwrap_or_else|unwrap_or_default)\s*\(/i,
489
+ /\.is_(?:some|none|ok|err)\s*\(\)/i,
490
+ /\bensure!\s*\(/i,
491
+ /\banyhow::ensure!\s*\(/i,
492
+ ],
493
+ };
494
+ const PHP_PATTERNS = {
495
+ sources: [
496
+ { pattern: /\$_(?:GET|POST|REQUEST|COOKIE|SERVER|FILES)\[/i, kind: "http-param" },
497
+ { pattern: /\$request->(?:input|get|post|query|all)\s*\(/i, kind: "http-param" },
498
+ { pattern: /\$_ENV\[|getenv\s*\(/i, kind: "environment" },
499
+ { pattern: /\$argv\b|fgets\s*\(\s*STDIN\b/i, kind: "user-input" },
500
+ { pattern: /file_get_contents\s*\(\s*['"]php:\/\/input/i, kind: "http-param" },
501
+ { pattern: /json_decode\s*\(\s*file_get_contents/i, kind: "external-data" },
502
+ { pattern: /\$_SESSION\[/i, kind: "external-data" },
503
+ ],
504
+ sinks: [
505
+ { pattern: /\b(?:exec|system|passthru|shell_exec|popen|proc_open)\s*\(/i, kind: "command-exec" },
506
+ { pattern: /\beval\s*\(|preg_replace\b.*\/e/i, kind: "code-execution" },
507
+ { pattern: /\bmysqli?_query\s*\(/i, kind: "sql-query" },
508
+ { pattern: /\$(?:pdo|db|conn)->(?:query|exec)\s*\(/i, kind: "sql-query" },
509
+ { pattern: /->(?:where|whereRaw|selectRaw|orderByRaw)\s*\(/i, kind: "sql-query" },
510
+ { pattern: /\binclude\s*\(|\brequire\s*\(|include_once\s*\(|require_once\s*\(/i, kind: "path-traversal" },
511
+ { pattern: /\bfile_(?:get_contents|put_contents)\s*\(/i, kind: "path-traversal" },
512
+ { pattern: /\bfopen\s*\(/i, kind: "path-traversal" },
513
+ { pattern: /\bheader\s*\(\s*['"]Location:/i, kind: "redirect" },
514
+ { pattern: /\bunserialize\s*\(/i, kind: "deserialization" },
515
+ { pattern: /\becho\b|\bprint\b/i, kind: "xss" },
516
+ ],
517
+ sanitizers: [
518
+ /\bhtmlspecialchars\s*\(/i,
519
+ /\bhtmlentities\s*\(/i,
520
+ /\bstrip_tags\s*\(/i,
521
+ /\baddslashes\s*\(/i,
522
+ /\bmysqli?_real_escape_string\s*\(/i,
523
+ /\bPDO::quote\s*\(/i,
524
+ /->(?:prepare|bindParam|bindValue)\s*\(/i,
525
+ /\bintval\s*\(|\bfloatval\s*\(|\b\(int\)|\b\(float\)/i,
526
+ /\bfilter_(?:var|input)\s*\(/i,
527
+ /\bpreg_match\s*\(/i,
528
+ /\brealpath\s*\(|basename\s*\(/i,
529
+ ],
530
+ assignPattern: /^\s*\$(\w+)\s*=\s*(.+);/,
531
+ guards: [
532
+ /if[ \t]*\([ \t]*!?(?:isset|empty|is_null|is_numeric|is_string|is_array)\s*\(/i,
533
+ /if[ \t]*\([ \t]*!?\$\w+\s*(?:===?|!==?)\s*(?:null|false|''|"")\b/i,
534
+ /\bvalidate\s*\(/i,
535
+ /\bpreg_match\s*\(/i,
536
+ /\bfilter_(?:var|input)\s*\(/i,
537
+ ],
538
+ };
539
+ const RUBY_PATTERNS = {
540
+ sources: [
541
+ { pattern: /\bparams\[/i, kind: "http-param" },
542
+ { pattern: /\bparams\.(?:require|permit|fetch)\s*\(/i, kind: "http-param" },
543
+ { pattern: /\brequest\.(?:body|env|headers|params)\b/i, kind: "http-param" },
544
+ { pattern: /\bENV\[|ENV\.fetch\s*\(/i, kind: "environment" },
545
+ { pattern: /\bARGV\b|\bgets\b|\breadline\b/i, kind: "user-input" },
546
+ { pattern: /\bJSON\.parse\s*\(/i, kind: "external-data" },
547
+ { pattern: /\bNet::HTTP\b.*\.(?:get|post)\s*\(/i, kind: "external-data" },
548
+ { pattern: /\bsession\[/i, kind: "external-data" },
549
+ { pattern: /\bcookies\[/i, kind: "http-param" },
550
+ ],
551
+ sinks: [
552
+ { pattern: /\bsystem\s*\(|\bexec\s*\(|\b`[^`]*#\{/i, kind: "command-exec" },
553
+ { pattern: /\b%x\{|Kernel\.system\s*\(/i, kind: "command-exec" },
554
+ { pattern: /\beval\s*\(|instance_eval\s*\(|class_eval\s*\(/i, kind: "code-execution" },
555
+ { pattern: /\bsend\s*\(|public_send\s*\(/i, kind: "code-execution" },
556
+ { pattern: /\.(?:where|find_by_sql|execute|select)\s*\(\s*(?:"|'|%|#)/i, kind: "sql-query" },
557
+ { pattern: /\.connection\.execute\s*\(/i, kind: "sql-query" },
558
+ { pattern: /\bFile\.(?:open|read|write|delete)\s*\(/i, kind: "path-traversal" },
559
+ { pattern: /\bredirect_to\s*\(/i, kind: "redirect" },
560
+ { pattern: /\bMarshal\.load\s*\(|YAML\.load\s*\(/i, kind: "deserialization" },
561
+ { pattern: /\b\.html_safe\b/i, kind: "xss" },
562
+ { pattern: /\braw\s*\(/i, kind: "xss" },
563
+ ],
564
+ sanitizers: [
565
+ /\bERB::Util\.html_escape\s*\(/i,
566
+ /\bCGI\.escapeHTML\s*\(/i,
567
+ /\bsanitize\s*\(/i,
568
+ /\bparams\.(?:require|permit)\s*\(/i,
569
+ /\.to_i\b|\.to_f\b/i,
570
+ /\bActiveRecord::Base\.connection\.quote\s*\(/i,
571
+ /\.(?:where|find_by)\s*\(\s*\w+\s*:\s/i,
572
+ /\bMarshal\.safe_load\b|YAML\.safe_load\s*\(/i,
573
+ /\bRegexp\.match\s*\(/i,
574
+ /\bFile\.expand_path\b.*\.start_with\?\s*\(/i,
575
+ ],
576
+ assignPattern: /^\s*(\w+)\s*=\s*(.+)/,
577
+ guards: [
578
+ /\bunless\s+\w+\.(?:nil\?|blank\?|empty\?)\b/i,
579
+ /if[ \t]+\w+\.(?:present\?|valid\?)\b/i,
580
+ /\braise\s+\w+Error\b/i,
581
+ /\.(?:validates?|validate!)\s/i,
582
+ ],
583
+ };
584
+ const KOTLIN_PATTERNS = {
585
+ sources: [
586
+ { pattern: /\brequest\.(?:getParameter|getAttribute|getHeader)\s*\(/i, kind: "http-param" },
587
+ { pattern: /\b@RequestParam\b|\b@PathVariable\b|\b@RequestBody\b/i, kind: "http-param" },
588
+ { pattern: /\bcall\.receive\b/i, kind: "http-param" },
589
+ { pattern: /\bcall\.parameters\[/i, kind: "http-param" },
590
+ { pattern: /\bSystem\.getenv\s*\(/i, kind: "environment" },
591
+ { pattern: /\breadLine\s*\(\)|Scanner\s*\(\s*System\.`in`\)/i, kind: "user-input" },
592
+ { pattern: /\bargs\[/i, kind: "user-input" },
593
+ { pattern: /\bGson\(\)\.fromJson\s*\(/i, kind: "external-data" },
594
+ { pattern: /\bJson\.decodeFromString\s*\(/i, kind: "external-data" },
595
+ ],
596
+ sinks: [
597
+ { pattern: /\bRuntime\.getRuntime\(\)\.exec\s*\(/i, kind: "command-exec" },
598
+ { pattern: /\bProcessBuilder\s*\(/i, kind: "command-exec" },
599
+ { pattern: /\.(?:executeQuery|executeUpdate|createQuery|nativeQuery)\s*\(/i, kind: "sql-query" },
600
+ { pattern: /\bString\.format\s*\(.*(?:SELECT|INSERT|UPDATE|DELETE)\b/i, kind: "sql-query" },
601
+ { pattern: /\"\$\{?\w+\}?.*(?:SELECT|INSERT|UPDATE|DELETE)\b/i, kind: "sql-query" },
602
+ { pattern: /\bFile\s*\(\s*(?:\$|[^")]+\+)/i, kind: "path-traversal" },
603
+ { pattern: /\bScriptEngine\b.*\.eval\s*\(/i, kind: "code-execution" },
604
+ { pattern: /\bObjectInputStream\b.*\.readObject\s*\(/i, kind: "deserialization" },
605
+ ],
606
+ sanitizers: [
607
+ /\bPreparedStatement\b/i,
608
+ /\bEncoder\.encode\s*\(/i,
609
+ /\bHtmlUtils\.htmlEscape\s*\(/i,
610
+ /\bStringEscapeUtils\.escape\w+\s*\(/i,
611
+ /\b@Valid\b|\b@Validated\b/i,
612
+ /\brequire\s*\{|check\s*\{/i,
613
+ /\.(?:toIntOrNull|toLongOrNull|toDoubleOrNull)\s*\(/i,
614
+ /\bRegex\s*\(.*\)\.matches\s*\(/i,
615
+ ],
616
+ assignPattern: /^\s*(?:(?:val|var|private|internal)\s+)?(\w+)\s*(?::\s*[\w<>?, [\]]+\s*)?=\s*(.+)/,
617
+ guards: [
618
+ /if[ \t]*\([ \t]*\w+[ \t]*(?:==|!=)[ \t]*null\b/i,
619
+ /\?\.\s*let\s*\{/i,
620
+ /\brequire\s*\(/i,
621
+ /\bcheck\s*\(/i,
622
+ /if[ \t]*\([ \t]*!?\w+\.(?:isBlank|isEmpty|isNullOrBlank|isNullOrEmpty)\s*\(/i,
623
+ ],
624
+ };
625
+ const SWIFT_PATTERNS = {
626
+ sources: [
627
+ { pattern: /\breq\.(?:content|query|parameters)\b/i, kind: "http-param" },
628
+ { pattern: /\brequest\.(?:content|query|body)\b/i, kind: "http-param" },
629
+ { pattern: /\bURLComponents\b.*\.queryItems\b/i, kind: "url-param" },
630
+ { pattern: /\bProcessInfo\.processInfo\.environment\[/i, kind: "environment" },
631
+ { pattern: /\bCommandLine\.arguments\b/i, kind: "user-input" },
632
+ { pattern: /\breadLine\s*\(/i, kind: "user-input" },
633
+ { pattern: /\bJSONDecoder\(\)\.decode\s*\(/i, kind: "external-data" },
634
+ { pattern: /\bURLSession\b.*\.data\s*\(/i, kind: "external-data" },
635
+ ],
636
+ sinks: [
637
+ { pattern: /\bProcess\(\)\s*.*arguments/i, kind: "command-exec" },
638
+ { pattern: /\bNSTask\b/i, kind: "command-exec" },
639
+ { pattern: /\.(?:execute|prepare)\s*\(\s*(?:".*\\|".*\+)/i, kind: "sql-query" },
640
+ { pattern: /\bFileManager\b.*\.(?:contentsOfFile|createFile)\s*\(/i, kind: "path-traversal" },
641
+ { pattern: /\bURL\s*\(\s*fileURLWithPath:\s*(?:\w+\s*\+|"\\)/i, kind: "path-traversal" },
642
+ { pattern: /\bJSContext\b.*\.evaluateScript\s*\(/i, kind: "code-execution" },
643
+ { pattern: /\bNSExpression\s*\(/i, kind: "code-execution" },
644
+ { pattern: /\bNSKeyedUnarchiver\b.*\.unarchiveObject\s*\(/i, kind: "deserialization" },
645
+ { pattern: /\bResponse\.redirect\s*\(/i, kind: "redirect" },
646
+ ],
647
+ sanitizers: [
648
+ /\baddingPercentEncoding\s*\(/i,
649
+ /\.replacingOccurrences\s*\(of:.*with:/i,
650
+ /\bInt\s*\(|Double\s*\(|Float\s*\(/i,
651
+ /\bNSRegularExpression\b/i,
652
+ /\bguard\s+let\b/i,
653
+ /\b\.standardizedFileURL\b|\.resolvingSymlinksInPath\b/i,
654
+ ],
655
+ assignPattern: /^\s*(?:(?:let|var)\s+)?(\w+)\s*(?::\s*[\w<>?, [\]?!]+\s*)?=\s*(.+)/,
656
+ guards: [
657
+ /guard[ \t]+let\b/i,
658
+ /if[ \t]+let\b/i,
659
+ /guard[ \t]+!?\w+\.(?:isEmpty|isNil)\b/i,
660
+ /\bprecondition\s*\(/i,
661
+ /\bassert\s*\(/i,
662
+ ],
663
+ };
664
+ // Map normalized languages to their pattern sets
665
+ const LANGUAGE_PATTERN_MAP = {
666
+ python: PYTHON_PATTERNS,
667
+ java: JAVA_PATTERNS,
668
+ go: GO_PATTERNS,
669
+ csharp: CSHARP_PATTERNS,
670
+ rust: RUST_PATTERNS,
671
+ php: PHP_PATTERNS,
672
+ ruby: RUBY_PATTERNS,
673
+ kotlin: KOTLIN_PATTERNS,
674
+ swift: SWIFT_PATTERNS,
675
+ };
211
676
  // ─── Public API ──────────────────────────────────────────────────────────────
212
677
  /**
213
678
  * Analyze a source file for taint flows: paths from untrusted input to
214
679
  * dangerous sinks through variable assignments and string concatenation.
215
680
  *
216
681
  * For JS/TS, uses the TypeScript compiler AST for precise variable tracking.
217
- * For other languages, falls back to regex-based lightweight analysis.
682
+ * For Python, Java, Go, C#, and Rust: uses language-specific source/sink/
683
+ * sanitizer patterns for deeper analysis.
684
+ * For other languages, falls back to generic regex-based analysis.
218
685
  */
219
686
  export function analyzeTaintFlows(code, language) {
220
687
  const lang = normalizeLanguage(language);
@@ -222,8 +689,10 @@ export function analyzeTaintFlows(code, language) {
222
689
  case "javascript":
223
690
  case "typescript":
224
691
  return analyzeTypeScriptTaint(code, lang);
225
- default:
226
- return analyzeRegexTaint(code);
692
+ default: {
693
+ const langPatterns = LANGUAGE_PATTERN_MAP[lang];
694
+ return analyzeRegexTaint(code, langPatterns);
695
+ }
227
696
  }
228
697
  }
229
698
  // ─── TypeScript / JavaScript Taint Analysis ──────────────────────────────────
@@ -423,13 +892,55 @@ function analyzeTypeScriptTaint(code, language) {
423
892
  return deduplicateFlows(flows);
424
893
  }
425
894
  // ─── Regex-based Taint Analysis (non-JS/TS languages) ────────────────────────
426
- function analyzeRegexTaint(code) {
895
+ /**
896
+ * Language-aware sanitizer check: combines global sanitizers with
897
+ * language-specific ones when available.
898
+ */
899
+ function isLangSanitized(expression, langPatterns) {
900
+ if (isSanitized(expression))
901
+ return true;
902
+ if (langPatterns) {
903
+ for (const p of langPatterns.sanitizers) {
904
+ if (p.test(expression))
905
+ return true;
906
+ }
907
+ }
908
+ return false;
909
+ }
910
+ /**
911
+ * Language-aware guard clause detection: combines global guards with
912
+ * language-specific guard patterns.
913
+ */
914
+ function detectLangGuardClauses(varName, sourceLine, sinkLine, codeLines, langPatterns) {
915
+ const baseReduction = detectGuardClauses(varName, sourceLine, sinkLine, codeLines);
916
+ if (!langPatterns)
917
+ return baseReduction;
918
+ const start = Math.min(sourceLine, sinkLine) - 1;
919
+ const end = Math.max(sourceLine, sinkLine);
920
+ let extraGuards = 0;
921
+ for (let i = start; i < end && i < codeLines.length; i++) {
922
+ const line = codeLines[i];
923
+ if (!containsWordBoundary(line, varName))
924
+ continue;
925
+ for (const guard of langPatterns.guards) {
926
+ if (guard.test(line)) {
927
+ extraGuards++;
928
+ break;
929
+ }
930
+ }
931
+ }
932
+ return Math.min(baseReduction + extraGuards * 0.1, 0.35);
933
+ }
934
+ function analyzeRegexTaint(code, langPatterns) {
427
935
  const codeLines = code.split("\n");
428
936
  const flows = [];
429
937
  // Track tainted variable names
430
938
  const tainted = new Map();
431
- // Assignment pattern: variable = source_expression
432
- const assignPattern = /^\s*(?:(?:let|const|var|val|auto)\s+)?(\w+)\s*[:=]\s*(.+)/;
939
+ // Merge source and sink patterns: language-specific + global
940
+ const allSources = langPatterns ? [...langPatterns.sources, ...SOURCE_PATTERNS] : SOURCE_PATTERNS;
941
+ const allSinks = langPatterns ? [...langPatterns.sinks, ...SINK_PATTERNS] : SINK_PATTERNS;
942
+ // Use language-specific assignment pattern if available
943
+ const assignPattern = langPatterns?.assignPattern ?? /^\s*(?:(?:let|const|var|val|auto)\s+)?(\w+)\s*[:=]\s*(.+)/;
433
944
  for (let i = 0; i < codeLines.length; i++) {
434
945
  const line = codeLines[i];
435
946
  const lineNum = i + 1;
@@ -438,10 +949,10 @@ function analyzeRegexTaint(code) {
438
949
  if (assignMatch) {
439
950
  const [, varName, rhs] = assignMatch;
440
951
  // Skip sanitized assignments
441
- if (isSanitized(rhs))
952
+ if (isLangSanitized(rhs, langPatterns))
442
953
  continue;
443
954
  // Direct source
444
- for (const src of SOURCE_PATTERNS) {
955
+ for (const src of allSources) {
445
956
  if (src.pattern.test(rhs)) {
446
957
  tainted.set(varName, {
447
958
  sourceExpr: rhs.trim(),
@@ -462,16 +973,16 @@ function analyzeRegexTaint(code) {
462
973
  }
463
974
  }
464
975
  // Skip lines with sanitizers for sink checking
465
- if (isSanitized(line))
976
+ if (isLangSanitized(line, langPatterns))
466
977
  continue;
467
978
  // Check for sinks using tainted data
468
- for (const sink of SINK_PATTERNS) {
979
+ for (const sink of allSinks) {
469
980
  if (!sink.pattern.test(line))
470
981
  continue;
471
982
  // Check tainted variables (word-boundary aware)
472
983
  for (const [varName, info] of tainted) {
473
984
  if (containsWordBoundary(line, varName) && lineNum !== info.sourceLine) {
474
- const guardReduction = detectGuardClauses(varName, info.sourceLine, lineNum, codeLines);
985
+ const guardReduction = detectLangGuardClauses(varName, info.sourceLine, lineNum, codeLines, langPatterns);
475
986
  flows.push({
476
987
  source: {
477
988
  line: info.sourceLine,
@@ -486,7 +997,7 @@ function analyzeRegexTaint(code) {
486
997
  }
487
998
  }
488
999
  // Inline source→sink
489
- for (const src of SOURCE_PATTERNS) {
1000
+ for (const src of allSources) {
490
1001
  if (src.pattern.test(line)) {
491
1002
  const alreadyCaptured = flows.some((f) => f.sink.line === lineNum);
492
1003
  if (!alreadyCaptured) {