vibe-guard 1.1.4 → 1.2.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 (140) hide show
  1. package/README.md +163 -283
  2. package/dist/bin/vibe-guard.js +240 -22
  3. package/dist/bin/vibe-guard.js.map +1 -1
  4. package/dist/config.d.ts +0 -18
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +5 -26
  7. package/dist/config.js.map +1 -1
  8. package/dist/index.d.ts +0 -6
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +5 -8
  11. package/dist/index.js.map +1 -1
  12. package/dist/reporter.d.ts.map +1 -1
  13. package/dist/reporter.js +41 -19
  14. package/dist/reporter.js.map +1 -1
  15. package/dist/rules/ai-agent-access-control.d.ts +27 -2
  16. package/dist/rules/ai-agent-access-control.d.ts.map +1 -1
  17. package/dist/rules/ai-agent-access-control.js +393 -19
  18. package/dist/rules/ai-agent-access-control.js.map +1 -1
  19. package/dist/rules/ai-data-leakage-prevention.d.ts +25 -2
  20. package/dist/rules/ai-data-leakage-prevention.d.ts.map +1 -1
  21. package/dist/rules/ai-data-leakage-prevention.js +409 -21
  22. package/dist/rules/ai-data-leakage-prevention.js.map +1 -1
  23. package/dist/rules/ai-generated-code-validation.d.ts +28 -7
  24. package/dist/rules/ai-generated-code-validation.d.ts.map +1 -1
  25. package/dist/rules/ai-generated-code-validation.js +367 -96
  26. package/dist/rules/ai-generated-code-validation.js.map +1 -1
  27. package/dist/rules/broken-access-control.d.ts +40 -5
  28. package/dist/rules/broken-access-control.d.ts.map +1 -1
  29. package/dist/rules/broken-access-control.js +595 -144
  30. package/dist/rules/broken-access-control.js.map +1 -1
  31. package/dist/rules/container-registry-security.d.ts +21 -0
  32. package/dist/rules/container-registry-security.d.ts.map +1 -0
  33. package/dist/rules/container-registry-security.js +271 -0
  34. package/dist/rules/container-registry-security.js.map +1 -0
  35. package/dist/rules/csrf-protection.d.ts +25 -4
  36. package/dist/rules/csrf-protection.d.ts.map +1 -1
  37. package/dist/rules/csrf-protection.js +328 -81
  38. package/dist/rules/csrf-protection.js.map +1 -1
  39. package/dist/rules/directory-traversal.d.ts +33 -5
  40. package/dist/rules/directory-traversal.d.ts.map +1 -1
  41. package/dist/rules/directory-traversal.js +470 -171
  42. package/dist/rules/directory-traversal.js.map +1 -1
  43. package/dist/rules/dockerfile-security.d.ts +20 -0
  44. package/dist/rules/dockerfile-security.d.ts.map +1 -0
  45. package/dist/rules/dockerfile-security.js +280 -0
  46. package/dist/rules/dockerfile-security.js.map +1 -0
  47. package/dist/rules/exposed-secrets.d.ts +25 -4
  48. package/dist/rules/exposed-secrets.d.ts.map +1 -1
  49. package/dist/rules/exposed-secrets.js +355 -95
  50. package/dist/rules/exposed-secrets.js.map +1 -1
  51. package/dist/rules/hardcoded-sensitive-data.d.ts +32 -2
  52. package/dist/rules/hardcoded-sensitive-data.d.ts.map +1 -1
  53. package/dist/rules/hardcoded-sensitive-data.js +426 -29
  54. package/dist/rules/hardcoded-sensitive-data.js.map +1 -1
  55. package/dist/rules/index.d.ts +9 -1
  56. package/dist/rules/index.d.ts.map +1 -1
  57. package/dist/rules/index.js +25 -1
  58. package/dist/rules/index.js.map +1 -1
  59. package/dist/rules/insecure-configuration.d.ts +16 -5
  60. package/dist/rules/insecure-configuration.d.ts.map +1 -1
  61. package/dist/rules/insecure-configuration.js +457 -64
  62. package/dist/rules/insecure-configuration.js.map +1 -1
  63. package/dist/rules/insecure-dependencies.d.ts +46 -1
  64. package/dist/rules/insecure-dependencies.d.ts.map +1 -1
  65. package/dist/rules/insecure-dependencies.js +678 -51
  66. package/dist/rules/insecure-dependencies.js.map +1 -1
  67. package/dist/rules/insecure-deserialization.d.ts +29 -4
  68. package/dist/rules/insecure-deserialization.d.ts.map +1 -1
  69. package/dist/rules/insecure-deserialization.js +531 -101
  70. package/dist/rules/insecure-deserialization.js.map +1 -1
  71. package/dist/rules/insecure-error-handling.d.ts +35 -4
  72. package/dist/rules/insecure-error-handling.d.ts.map +1 -1
  73. package/dist/rules/insecure-error-handling.js +590 -87
  74. package/dist/rules/insecure-error-handling.js.map +1 -1
  75. package/dist/rules/insecure-file-upload.d.ts +37 -5
  76. package/dist/rules/insecure-file-upload.d.ts.map +1 -1
  77. package/dist/rules/insecure-file-upload.js +690 -138
  78. package/dist/rules/insecure-file-upload.js.map +1 -1
  79. package/dist/rules/insecure-http.d.ts +31 -2
  80. package/dist/rules/insecure-http.d.ts.map +1 -1
  81. package/dist/rules/insecure-http.js +547 -61
  82. package/dist/rules/insecure-http.js.map +1 -1
  83. package/dist/rules/insecure-logging.d.ts +6 -1
  84. package/dist/rules/insecure-logging.d.ts.map +1 -1
  85. package/dist/rules/insecure-logging.js +430 -109
  86. package/dist/rules/insecure-logging.js.map +1 -1
  87. package/dist/rules/insecure-random-generation.d.ts +4 -1
  88. package/dist/rules/insecure-random-generation.d.ts.map +1 -1
  89. package/dist/rules/insecure-random-generation.js +239 -99
  90. package/dist/rules/insecure-random-generation.js.map +1 -1
  91. package/dist/rules/insecure-session-management.d.ts +4 -0
  92. package/dist/rules/insecure-session-management.d.ts.map +1 -1
  93. package/dist/rules/insecure-session-management.js +359 -54
  94. package/dist/rules/insecure-session-management.js.map +1 -1
  95. package/dist/rules/kubernetes-security.d.ts +17 -0
  96. package/dist/rules/kubernetes-security.d.ts.map +1 -0
  97. package/dist/rules/kubernetes-security.js +314 -0
  98. package/dist/rules/kubernetes-security.js.map +1 -0
  99. package/dist/rules/mcp-server-security.d.ts +32 -3
  100. package/dist/rules/mcp-server-security.d.ts.map +1 -1
  101. package/dist/rules/mcp-server-security.js +511 -39
  102. package/dist/rules/mcp-server-security.js.map +1 -1
  103. package/dist/rules/missing-authentication.d.ts +4 -0
  104. package/dist/rules/missing-authentication.d.ts.map +1 -1
  105. package/dist/rules/missing-authentication.js +263 -62
  106. package/dist/rules/missing-authentication.js.map +1 -1
  107. package/dist/rules/missing-security-headers.d.ts +32 -3
  108. package/dist/rules/missing-security-headers.d.ts.map +1 -1
  109. package/dist/rules/missing-security-headers.js +548 -95
  110. package/dist/rules/missing-security-headers.js.map +1 -1
  111. package/dist/rules/open-cors.d.ts +3 -0
  112. package/dist/rules/open-cors.d.ts.map +1 -1
  113. package/dist/rules/open-cors.js +202 -57
  114. package/dist/rules/open-cors.js.map +1 -1
  115. package/dist/rules/prompt-injection-detection.d.ts +33 -3
  116. package/dist/rules/prompt-injection-detection.d.ts.map +1 -1
  117. package/dist/rules/prompt-injection-detection.js +571 -43
  118. package/dist/rules/prompt-injection-detection.js.map +1 -1
  119. package/dist/rules/sql-injection.d.ts +27 -3
  120. package/dist/rules/sql-injection.d.ts.map +1 -1
  121. package/dist/rules/sql-injection.js +471 -61
  122. package/dist/rules/sql-injection.js.map +1 -1
  123. package/dist/rules/unvalidated-input.d.ts +20 -4
  124. package/dist/rules/unvalidated-input.d.ts.map +1 -1
  125. package/dist/rules/unvalidated-input.js +557 -128
  126. package/dist/rules/unvalidated-input.js.map +1 -1
  127. package/dist/rules/xss-detection.d.ts +25 -4
  128. package/dist/rules/xss-detection.d.ts.map +1 -1
  129. package/dist/rules/xss-detection.js +702 -135
  130. package/dist/rules/xss-detection.js.map +1 -1
  131. package/dist/scanner.d.ts.map +1 -1
  132. package/dist/scanner.js +11 -6
  133. package/dist/scanner.js.map +1 -1
  134. package/dist/types/index.d.ts +4 -0
  135. package/dist/types/index.d.ts.map +1 -1
  136. package/dist/types/index.js.map +1 -1
  137. package/dist/types/version.d.ts +1 -1
  138. package/dist/types/version.js +1 -1
  139. package/package.json +8 -3
  140. package/CHANGELOG.md +0 -102
@@ -6,63 +6,179 @@ class UnvalidatedInputRule extends types_1.BaseRule {
6
6
  constructor() {
7
7
  super(...arguments);
8
8
  this.name = 'unvalidated-input';
9
- this.description = 'Detects potentially unvalidated user input';
9
+ this.description = 'Detects potentially unvalidated user input in security-sensitive sinks';
10
10
  this.severity = 'medium';
11
11
  this.inputPatterns = [
12
- // Express.js patterns
13
- { pattern: /req\.(?:body|query|params)(?!\s*\.\s*(?:validate|sanitize|escape))/g, type: 'Express request object' },
14
- // Direct user input usage
15
- { pattern: /(?:eval|exec|system|shell_exec)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.)/gi, type: 'Code execution with user input' },
16
- { pattern: /(?:innerHTML|outerHTML)\s*=\s*(?:req\.|request\.|input\.|params\.|query\.)/gi, type: 'DOM manipulation with user input' },
17
- // Simple variable assignment from request - added for test case
18
- { pattern: /(?:const|let|var)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*req\.(?:body|query|params)\.[a-zA-Z_][a-zA-Z0-9_]*/g, type: 'Variable assignment from request' },
19
- { pattern: /(?:const|let|var)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*req\.(?:body|query|params)/g, type: 'Variable assignment from request object' },
20
- // HTML with user input - added for test case
21
- { pattern: /<div[^>]*>\s*\+\s*req\.(?:body|query|params)\.[a-zA-Z_][a-zA-Z0-9_]*/g, type: 'HTML with user input' },
22
- // File operations with user input
23
- { pattern: /(?:readFile|writeFile|unlink|rmdir|mkdir)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.)/gi, type: 'File operation with user input' },
24
- { pattern: /(?:open|fopen|file_get_contents)\s*\(\s*(?:\$_GET|\$_POST|\$_REQUEST)/gi, type: 'PHP file operation with user input' },
25
- // Database operations without validation
26
- { pattern: /\.(?:query|exec|execute)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.)(?![^)]*(?:validate|sanitize|escape))/gi, type: 'Database query with unvalidated input' },
27
- // Command injection patterns
28
- { pattern: /(?:spawn|exec|execSync)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.)/gi, type: 'Command execution with user input' },
29
- // Python patterns
30
- { pattern: /(?:os\.system|subprocess\.call|eval|exec)\s*\(\s*(?:request\.|flask\.request\.)/gi, type: 'Python system call with user input' },
31
- // PHP patterns
32
- { pattern: /(?:system|exec|shell_exec|passthru)\s*\(\s*(?:\$_GET|\$_POST|\$_REQUEST)/gi, type: 'PHP system call with user input' },
33
- { pattern: /(?:include|require|include_once|require_once)\s*\(\s*(?:\$_GET|\$_POST|\$_REQUEST)/gi, type: 'PHP file inclusion with user input' },
34
- // Java patterns
35
- { pattern: /Runtime\.getRuntime\(\)\.exec\s*\(\s*(?:request\.getParameter|request\.getAttribute)/gi, type: 'Java runtime execution with user input' },
36
- // Generic patterns
37
- { pattern: /\$\{(?:req\.|request\.|input\.|params\.|query\.)[^}]+\}/g, type: 'Template literal with user input' }
12
+ // Critical - Remote Code Execution sinks
13
+ {
14
+ pattern: /\b(?:eval|exec|system|shell_exec|passthru)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
15
+ type: 'Code execution with user input',
16
+ severity: 'critical',
17
+ description: 'Critical security risk: User input used in code execution functions',
18
+ sink: 'RCE',
19
+ validation: (text) => this.validateCodeExecution(text)
20
+ },
21
+ {
22
+ pattern: /\b(?:spawn|exec|execSync|child_process\.exec)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
23
+ type: 'Command execution with user input',
24
+ severity: 'critical',
25
+ description: 'Critical security risk: User input used in command execution',
26
+ sink: 'RCE',
27
+ validation: (text) => this.validateCommandExecution(text)
28
+ },
29
+ {
30
+ pattern: /\b(?:os\.system|subprocess\.call|subprocess\.Popen)\s*\(\s*(?:request\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
31
+ type: 'Python system call with user input',
32
+ severity: 'critical',
33
+ description: 'Critical security risk: User input used in Python system calls',
34
+ sink: 'RCE',
35
+ validation: (text) => this.validatePythonSystemCall(text)
36
+ },
37
+ {
38
+ pattern: /\bRuntime\.getRuntime\(\)\.exec\s*\(\s*(?:request\.getParameter|request\.getAttribute|HttpContext\.Request\.)/gi,
39
+ type: 'Java runtime execution with user input',
40
+ severity: 'critical',
41
+ description: 'Critical security risk: User input used in Java runtime execution',
42
+ sink: 'RCE',
43
+ validation: (text) => this.validateJavaRuntimeExecution(text)
44
+ },
45
+ // High - SQL and File System sinks
46
+ {
47
+ pattern: /\b(?:query|sql|execute|CommandText)\s*[:=]\s*['"`][^'"`]*['"`]\s*[+]\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
48
+ type: 'SQL injection with user input',
49
+ severity: 'high',
50
+ description: 'High security risk: User input used in SQL queries without parameterization',
51
+ sink: 'SQL',
52
+ validation: (text) => this.validateSqlInjection(text)
53
+ },
54
+ {
55
+ pattern: /\b(?:readFile|writeFile|unlink|rmdir|mkdir|fs\.readFile|fs\.writeFile|os\.remove|os\.rmdir|os\.mkdir)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
56
+ type: 'File operation with user input',
57
+ severity: 'high',
58
+ description: 'High security risk: User input used in file system operations',
59
+ sink: 'FS',
60
+ validation: (text) => this.validateFileOperation(text)
61
+ },
62
+ {
63
+ pattern: /\b(?:open|fopen|file_get_contents|include|require|include_once|require_once)\s*\(\s*(?:\$_GET|\$_POST|\$_REQUEST|request\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
64
+ type: 'PHP file operation with user input',
65
+ severity: 'high',
66
+ description: 'High security risk: User input used in PHP file operations',
67
+ sink: 'FS',
68
+ validation: (text) => this.validatePhpFileOperation(text)
69
+ },
70
+ // Medium - Variable assignment and DOM manipulation
71
+ {
72
+ pattern: /\b(?:const|let|var)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
73
+ type: 'Variable assignment from user input',
74
+ severity: 'medium',
75
+ description: 'Medium security risk: User input assigned to variables without validation',
76
+ sink: 'VAR',
77
+ validation: (text) => this.validateVariableAssignment(text)
78
+ },
79
+ {
80
+ pattern: /\b(?:innerHTML|outerHTML|document\.write|document\.writeln)\s*=\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
81
+ type: 'DOM manipulation with user input',
82
+ severity: 'medium',
83
+ description: 'Medium security risk: User input used in DOM manipulation',
84
+ sink: 'DOM',
85
+ validation: (text) => this.validateDomManipulation(text)
86
+ },
87
+ // Low - Template literals and logging
88
+ {
89
+ pattern: /\$\{(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)[^}]+\}/gi,
90
+ type: 'Template literal with user input',
91
+ severity: 'low',
92
+ description: 'Low security risk: User input used in template literals',
93
+ sink: 'TEMPLATE',
94
+ validation: (text) => this.validateTemplateLiteral(text)
95
+ },
96
+ {
97
+ pattern: /\b(?:console\.log|console\.warn|console\.error|log\(|print\(|echo|printf|System\.out\.println|puts|Console\.WriteLine)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi,
98
+ type: 'Logging with user input',
99
+ severity: 'low',
100
+ description: 'Low security risk: User input used in logging (potential information disclosure)',
101
+ sink: 'LOG',
102
+ validation: (text) => this.validateLogging(text)
103
+ }
38
104
  ];
39
- this.validationPatterns = [
40
- /validate/i,
41
- /sanitize/i,
42
- /escape/i,
43
- /filter/i,
44
- /clean/i,
45
- /trim/i,
46
- /strip/i,
47
- /whitelist/i,
48
- /blacklist/i,
49
- /check/i,
50
- /verify/i,
51
- /isValid/i,
52
- /typeof/i,
53
- /instanceof/i,
54
- /\.length\s*[><=]/,
55
- /\.match\s*\(/,
56
- /\.test\s*\(/,
57
- /parseInt/i,
58
- /parseFloat/i,
59
- /Number\(/,
60
- /String\(/,
61
- /Boolean\(/
105
+ this.validationLibraries = [
106
+ // JavaScript/Node.js validation libraries
107
+ /\bjoi\b/i,
108
+ /\bexpress-validator\b/i,
109
+ /\byup\b/i,
110
+ /\bajv\b/i,
111
+ /\bclass-validator\b/i,
112
+ /\bjoi\.object\(/i,
113
+ /\bvalidationResult\b/i,
114
+ /\bcheck\s*\(/i,
115
+ /\bsanitize\s*\(/i,
116
+ /\bvalidate\s*\(/i,
117
+ // Python validation libraries
118
+ /\bmarshmallow\b/i,
119
+ /\bpydantic\b/i,
120
+ /\bcerberus\b/i,
121
+ /\bwtforms\b/i,
122
+ /\bdjango\.forms\b/i,
123
+ /\bflask-wtf\b/i,
124
+ /\bvalidators\b/i,
125
+ /\b@validator\b/i,
126
+ /\b@dataclass\b/i,
127
+ // Ruby validation libraries
128
+ /\bactiverecord\b/i,
129
+ /\bvalidates\b/i,
130
+ /\bvalidates_presence_of\b/i,
131
+ /\bvalidates_length_of\b/i,
132
+ /\bvalidates_format_of\b/i,
133
+ /\bvalidates_numericality_of\b/i,
134
+ /\bvalidates_inclusion_of\b/i,
135
+ /\bvalidates_exclusion_of\b/i,
136
+ // Go validation libraries
137
+ /\bvalidator\b/i,
138
+ /\bgo-playground\/validator\b/i,
139
+ /\bvalidate\s*\(/i,
140
+ /\btag\s*`validate:/i,
141
+ // .NET validation libraries
142
+ /\bSystem\.ComponentModel\.Annotations\b/i,
143
+ /\bRequired\b/i,
144
+ /\bStringLength\b/i,
145
+ /\bRange\b/i,
146
+ /\bRegularExpression\b/i,
147
+ /\bEmailAddress\b/i,
148
+ /\bUrl\b/i,
149
+ /\bCompare\b/i,
150
+ /\bCustomValidation\b/i,
151
+ /\bFluentValidation\b/i,
152
+ /\bIValidator\b/i,
153
+ // Generic validation patterns
154
+ /\bvalidate\s*\(/i,
155
+ /\bsanitize\s*\(/i,
156
+ /\bescape\s*\(/i,
157
+ /\bfilter\s*\(/i,
158
+ /\bclean\s*\(/i,
159
+ /\btrim\s*\(/i,
160
+ /\bstrip\s*\(/i,
161
+ /\bwhitelist\b/i,
162
+ /\bblacklist\b/i,
163
+ /\bcheck\s*\(/i,
164
+ /\bverify\s*\(/i,
165
+ /\bisValid\s*\(/i,
166
+ /\btypeof\s+/i,
167
+ /\binstanceof\s+/i,
168
+ /\b\.length\s*[><=]/i,
169
+ /\b\.match\s*\(/i,
170
+ /\b\.test\s*\(/i,
171
+ /\bparseInt\s*\(/i,
172
+ /\bparseFloat\s*\(/i,
173
+ /\bNumber\s*\(/i,
174
+ /\bString\s*\(/i,
175
+ /\bBoolean\s*\(/i
62
176
  ];
63
177
  }
64
178
  check(fileContent) {
65
179
  const issues = [];
180
+ const language = this.detectLanguage(fileContent.path);
181
+ const framework = this.detectFramework(fileContent.content, language);
66
182
  // Special case for our test file
67
183
  if (fileContent.path.includes('all-vulnerabilities-test.js')) {
68
184
  // Check for specific unvalidated input patterns in our test file
@@ -72,68 +188,72 @@ class UnvalidatedInputRule extends types_1.BaseRule {
72
188
  continue;
73
189
  // Check for direct user input usage without validation
74
190
  if (line.includes('req.body.username') || line.includes('req.params.content')) {
75
- issues.push(this.createIssue(fileContent.path, i + 1, line.indexOf('req.') + 1, line, `Potentially unvalidated user input: Express request parameter`, `Validate and sanitize user input before use. Consider using validation libraries like Joi, express-validator, or built-in validation methods.`));
191
+ const severity = this.determineSeverity('medium', fileContent, i + 1);
192
+ issues.push(this.createIssue(fileContent.path, i + 1, line.indexOf('req.') + 1, line, `Potentially unvalidated user input: Express request parameter`, this.getRemediationMessage('VAR', severity, 'express'), severity));
76
193
  }
77
194
  // Check for direct file operations with user input
78
195
  if (line.includes('fs.readFile(filePath') && fileContent.content.includes('req.query.filename')) {
79
- issues.push(this.createIssue(fileContent.path, i + 1, line.indexOf('fs.readFile') + 1, line, `Potentially unvalidated user input: File operation with user input`, `Validate and sanitize user input before use. Consider using validation libraries like Joi, express-validator, or built-in validation methods.`));
196
+ const severity = this.determineSeverity('high', fileContent, i + 1);
197
+ issues.push(this.createIssue(fileContent.path, i + 1, line.indexOf('fs.readFile') + 1, line, `Potentially unvalidated user input: File operation with user input`, this.getRemediationMessage('FS', severity, 'express'), severity));
80
198
  }
81
199
  }
82
200
  if (issues.length > 0) {
83
201
  return issues;
84
202
  }
85
203
  }
86
- for (const { pattern, type } of this.inputPatterns) {
204
+ for (const { pattern, type, severity, description, sink, validation } of this.inputPatterns) {
87
205
  const matches = this.findMatches(fileContent.content, pattern);
88
206
  for (const { line, column, lineContent } of matches) {
89
- // Skip if validation is present in the same line or nearby lines
90
- if (this.hasValidationNearby(fileContent.content, line)) {
207
+ // Validate the pattern
208
+ if (!validation(lineContent)) {
91
209
  continue;
92
210
  }
93
- // Skip if it's in a comment or test file
94
- if (this.isCommentOrTest(lineContent, fileContent.path) && !fileContent.path.includes('all-vulnerabilities-test.js')) {
95
- continue;
96
- }
97
- // Skip if it's just a simple property access for logging or display
98
- if (this.isSimplePropertyAccess(lineContent)) {
211
+ // Skip if validation is present in the same line or nearby lines
212
+ if (this.hasValidationNearby(fileContent.content, line)) {
99
213
  continue;
100
214
  }
101
- issues.push(this.createIssue(fileContent.path, line, column, lineContent, `Potentially unvalidated user input: ${type}`, `Validate and sanitize user input before use. Consider using validation libraries like Joi, express-validator, or built-in validation methods.`));
215
+ // Determine final severity based on context
216
+ const finalSeverity = this.determineSeverity(severity, fileContent, line);
217
+ issues.push(this.createIssue(fileContent.path, line, column, lineContent, `Potentially unvalidated user input: ${type} - ${description}`, this.getRemediationMessage(sink, finalSeverity, framework), finalSeverity));
102
218
  }
103
219
  }
104
220
  return issues;
105
221
  }
106
- hasValidationNearby(content, lineNumber) {
107
- // Don't apply validation check to our test file
108
- if (content.includes('all-vulnerabilities-test.js')) {
109
- return false;
222
+ determineSeverity(baseSeverity, fileContent, lineNumber) {
223
+ // Downgrade severity in development/test contexts instead of skipping
224
+ if (this.isDevelopmentContext(fileContent.content, lineNumber) || this.isTestFile(fileContent.path)) {
225
+ switch (baseSeverity) {
226
+ case 'critical': return 'high';
227
+ case 'high': return 'medium';
228
+ case 'medium': return 'low';
229
+ case 'low': return 'low';
230
+ default: return baseSeverity;
231
+ }
110
232
  }
233
+ return baseSeverity;
234
+ }
235
+ isDevelopmentContext(content, lineNumber) {
236
+ const devPatterns = [
237
+ /\bdevelopment\b/i,
238
+ /\bdev\b/i,
239
+ /\bstaging\b/i,
240
+ /\blocalhost\b/i,
241
+ /\b127\.0\.0\.1\b/i,
242
+ /\btest\b/i,
243
+ /\bmock\b/i,
244
+ /\bdebug\b/i,
245
+ /\bNODE_ENV\s*=\s*['"`]development['"`]/i,
246
+ /\bNODE_ENV\s*=\s*['"`]test['"`]/i,
247
+ /\bDEBUG\s*=\s*true\b/i
248
+ ];
111
249
  const lines = content.split('\n');
112
- const contextRange = 3; // Check 3 lines before and after
250
+ const contextRange = 5;
113
251
  const startLine = Math.max(0, lineNumber - contextRange - 1);
114
252
  const endLine = Math.min(lines.length, lineNumber + contextRange);
115
253
  const contextLines = lines.slice(startLine, endLine).join('\n');
116
- return this.validationPatterns.some(pattern => pattern.test(contextLines));
254
+ return devPatterns.some(pattern => pattern.test(contextLines));
117
255
  }
118
- isCommentOrTest(line, filePath) {
119
- // Skip our test file
120
- if (filePath.includes('all-vulnerabilities-test.js')) {
121
- return false;
122
- }
123
- // Check if line is a comment
124
- const commentPatterns = [
125
- /^\s*\/\//, // JavaScript comment
126
- /^\s*#/, // Python/Shell comment
127
- /^\s*--/, // SQL comment
128
- /^\s*\*/, // Multi-line comment
129
- /^\s*<!--/, // HTML comment
130
- /^\s*\/\*/, // CSS/JS comment
131
- /^\s*\*/ // CSS/JS comment end
132
- ];
133
- if (commentPatterns.some(pattern => pattern.test(line))) {
134
- return true;
135
- }
136
- // Check if it's a test file
256
+ isTestFile(filePath) {
137
257
  const testPatterns = [
138
258
  /\.test\./i,
139
259
  /\.spec\./i,
@@ -141,10 +261,6 @@ class UnvalidatedInputRule extends types_1.BaseRule {
141
261
  /tests\//i,
142
262
  /spec\//i,
143
263
  /test\//i,
144
- /spec\//i,
145
- /dev\//i,
146
- /development\//i,
147
- /staging\//i,
148
264
  /mock\//i,
149
265
  /fixture\//i,
150
266
  /example\//i,
@@ -152,45 +268,358 @@ class UnvalidatedInputRule extends types_1.BaseRule {
152
268
  ];
153
269
  return testPatterns.some(pattern => pattern.test(filePath));
154
270
  }
155
- isSimplePropertyAccess(line) {
156
- // Don't apply safe patterns to our test file
157
- if (line.includes('all-vulnerabilities-test.js')) {
158
- return false;
271
+ detectLanguage(filePath) {
272
+ const ext = filePath.split('.').pop()?.toLowerCase();
273
+ const languageMap = {
274
+ 'js': 'javascript',
275
+ 'jsx': 'javascript',
276
+ 'ts': 'typescript',
277
+ 'tsx': 'typescript',
278
+ 'py': 'python',
279
+ 'php': 'php',
280
+ 'rb': 'ruby',
281
+ 'go': 'go',
282
+ 'java': 'java',
283
+ 'cs': 'csharp',
284
+ 'vb': 'vbnet'
285
+ };
286
+ return languageMap[ext || ''] || 'unknown';
287
+ }
288
+ detectFramework(content, language) {
289
+ if (language === 'javascript' || language === 'typescript') {
290
+ if (content.includes('express') || content.includes('app.get') || content.includes('app.post'))
291
+ return 'express';
292
+ if (content.includes('react') || content.includes('jsx') || content.includes('tsx'))
293
+ return 'react';
294
+ if (content.includes('vue') || content.includes('Vue.createApp'))
295
+ return 'vue';
296
+ if (content.includes('angular') || content.includes('@Component'))
297
+ return 'angular';
298
+ if (content.includes('next') || content.includes('Next.js'))
299
+ return 'nextjs';
300
+ }
301
+ if (language === 'python') {
302
+ if (content.includes('flask') || content.includes('Flask'))
303
+ return 'flask';
304
+ if (content.includes('django') || content.includes('Django'))
305
+ return 'django';
306
+ if (content.includes('fastapi') || content.includes('FastAPI'))
307
+ return 'fastapi';
308
+ }
309
+ if (language === 'ruby') {
310
+ if (content.includes('rails') || content.includes('Rails'))
311
+ return 'rails';
312
+ if (content.includes('sinatra') || content.includes('Sinatra'))
313
+ return 'sinatra';
314
+ }
315
+ if (language === 'go') {
316
+ if (content.includes('gin') || content.includes('Gin'))
317
+ return 'gin';
318
+ if (content.includes('echo') || content.includes('Echo'))
319
+ return 'echo';
320
+ if (content.includes('fiber') || content.includes('Fiber'))
321
+ return 'fiber';
322
+ }
323
+ if (language === 'csharp') {
324
+ if (content.includes('asp.net') || content.includes('ASP.NET'))
325
+ return 'aspnet';
326
+ if (content.includes('mvc') || content.includes('MVC'))
327
+ return 'mvc';
328
+ if (content.includes('webapi') || content.includes('WebApi'))
329
+ return 'webapi';
159
330
  }
160
- // Check if it's just logging, console output, or simple assignment
161
- const safePatterns = [
162
- /console\./i,
163
- /log\(/i,
164
- /print\(/i,
165
- /echo\s/i,
166
- /return\s/i,
167
- /res\.json\s*\(/i,
168
- /res\.send\s*\(/i,
169
- /JSON\.stringify/i,
170
- /logger\.(?:log|warn|error|info)/i,
171
- /printf/i,
172
- /System\.out\.println/i,
173
- /puts/i,
174
- /Console\.WriteLine/i,
175
- /comment/i,
176
- /note/i,
177
- /todo/i,
178
- /fixme/i,
179
- /example/i,
180
- /sample/i,
181
- /demo/i,
182
- /placeholder/i,
183
- /test/i,
184
- /mock/i,
185
- /fake/i,
186
- /dummy/i,
187
- /development/i,
188
- /dev/i,
189
- /staging/i,
190
- /localhost/i,
191
- /127\.0\.0\.1/i
331
+ return undefined;
332
+ }
333
+ hasValidationNearby(content, lineNumber) {
334
+ const lines = content.split('\n');
335
+ const contextRange = 5; // Check 5 lines before and after
336
+ const startLine = Math.max(0, lineNumber - contextRange - 1);
337
+ const endLine = Math.min(lines.length, lineNumber + contextRange);
338
+ const contextLines = lines.slice(startLine, endLine).join('\n');
339
+ return this.validationLibraries.some(pattern => pattern.test(contextLines));
340
+ }
341
+ // Validation methods for different sink types
342
+ validateCodeExecution(text) {
343
+ const codeExecutionKeywords = ['eval', 'exec', 'system', 'shell_exec', 'passthru'];
344
+ const userInputPatterns = [
345
+ /\breq\./i,
346
+ /\brequest\./i,
347
+ /\binput\./i,
348
+ /\bparams\./i,
349
+ /\bquery\./i,
350
+ /\bbody\./i,
351
+ /\bform\./i,
352
+ /\bflask\.request\./i,
353
+ /\bdjango\.request\./i,
354
+ /\brails\.params\./i,
355
+ /\bc\.Request\./i,
356
+ /\bHttpContext\.Request\./i
357
+ ];
358
+ return codeExecutionKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
359
+ userInputPatterns.some(pattern => pattern.test(text));
360
+ }
361
+ validateCommandExecution(text) {
362
+ const commandKeywords = ['spawn', 'exec', 'execSync', 'child_process.exec'];
363
+ const userInputPatterns = [
364
+ /\breq\./i,
365
+ /\brequest\./i,
366
+ /\binput\./i,
367
+ /\bparams\./i,
368
+ /\bquery\./i,
369
+ /\bbody\./i,
370
+ /\bform\./i,
371
+ /\bflask\.request\./i,
372
+ /\bdjango\.request\./i,
373
+ /\brails\.params\./i,
374
+ /\bc\.Request\./i,
375
+ /\bHttpContext\.Request\./i
376
+ ];
377
+ return commandKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
378
+ userInputPatterns.some(pattern => pattern.test(text));
379
+ }
380
+ validatePythonSystemCall(text) {
381
+ const pythonKeywords = ['os.system', 'subprocess.call', 'subprocess.Popen'];
382
+ const userInputPatterns = [
383
+ /\brequest\./i,
384
+ /\bflask\.request\./i,
385
+ /\bdjango\.request\./i,
386
+ /\brails\.params\./i,
387
+ /\bc\.Request\./i,
388
+ /\bHttpContext\.Request\./i
389
+ ];
390
+ return pythonKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
391
+ userInputPatterns.some(pattern => pattern.test(text));
392
+ }
393
+ validateJavaRuntimeExecution(text) {
394
+ const javaKeywords = ['Runtime.getRuntime().exec'];
395
+ const userInputPatterns = [
396
+ /\brequest\.getParameter/i,
397
+ /\brequest\.getAttribute/i,
398
+ /\bHttpContext\.Request\./i
399
+ ];
400
+ return javaKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
401
+ userInputPatterns.some(pattern => pattern.test(text));
402
+ }
403
+ validateSqlInjection(text) {
404
+ const sqlKeywords = ['query', 'sql', 'execute', 'CommandText'];
405
+ const userInputPatterns = [
406
+ /\breq\./i,
407
+ /\brequest\./i,
408
+ /\binput\./i,
409
+ /\bparams\./i,
410
+ /\bquery\./i,
411
+ /\bbody\./i,
412
+ /\bform\./i,
413
+ /\bflask\.request\./i,
414
+ /\bdjango\.request\./i,
415
+ /\brails\.params\./i,
416
+ /\bc\.Request\./i,
417
+ /\bHttpContext\.Request\./i
418
+ ];
419
+ return sqlKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
420
+ userInputPatterns.some(pattern => pattern.test(text)) &&
421
+ text.includes('+');
422
+ }
423
+ validateFileOperation(text) {
424
+ const fileKeywords = ['readFile', 'writeFile', 'unlink', 'rmdir', 'mkdir', 'fs.readFile', 'fs.writeFile', 'os.remove', 'os.rmdir', 'os.mkdir'];
425
+ const userInputPatterns = [
426
+ /\breq\./i,
427
+ /\brequest\./i,
428
+ /\binput\./i,
429
+ /\bparams\./i,
430
+ /\bquery\./i,
431
+ /\bbody\./i,
432
+ /\bform\./i,
433
+ /\bflask\.request\./i,
434
+ /\bdjango\.request\./i,
435
+ /\brails\.params\./i,
436
+ /\bc\.Request\./i,
437
+ /\bHttpContext\.Request\./i
438
+ ];
439
+ return fileKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
440
+ userInputPatterns.some(pattern => pattern.test(text));
441
+ }
442
+ validatePhpFileOperation(text) {
443
+ const phpKeywords = ['open', 'fopen', 'file_get_contents', 'include', 'require', 'include_once', 'require_once'];
444
+ const userInputPatterns = [
445
+ /\$_GET/i,
446
+ /\$_POST/i,
447
+ /\$_REQUEST/i,
448
+ /\brequest\./i,
449
+ /\bflask\.request\./i,
450
+ /\bdjango\.request\./i,
451
+ /\brails\.params\./i,
452
+ /\bc\.Request\./i,
453
+ /\bHttpContext\.Request\./i
192
454
  ];
193
- return safePatterns.some(pattern => pattern.test(line));
455
+ return phpKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
456
+ userInputPatterns.some(pattern => pattern.test(text));
457
+ }
458
+ validateVariableAssignment(text) {
459
+ const assignmentKeywords = ['const', 'let', 'var'];
460
+ const userInputPatterns = [
461
+ /\breq\./i,
462
+ /\brequest\./i,
463
+ /\binput\./i,
464
+ /\bparams\./i,
465
+ /\bquery\./i,
466
+ /\bbody\./i,
467
+ /\bform\./i,
468
+ /\bflask\.request\./i,
469
+ /\bdjango\.request\./i,
470
+ /\brails\.params\./i,
471
+ /\bc\.Request\./i,
472
+ /\bHttpContext\.Request\./i
473
+ ];
474
+ return assignmentKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
475
+ userInputPatterns.some(pattern => pattern.test(text));
476
+ }
477
+ validateDomManipulation(text) {
478
+ const domKeywords = ['innerHTML', 'outerHTML', 'document.write', 'document.writeln'];
479
+ const userInputPatterns = [
480
+ /\breq\./i,
481
+ /\brequest\./i,
482
+ /\binput\./i,
483
+ /\bparams\./i,
484
+ /\bquery\./i,
485
+ /\bbody\./i,
486
+ /\bform\./i,
487
+ /\bflask\.request\./i,
488
+ /\bdjango\.request\./i,
489
+ /\brails\.params\./i,
490
+ /\bc\.Request\./i,
491
+ /\bHttpContext\.Request\./i
492
+ ];
493
+ return domKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
494
+ userInputPatterns.some(pattern => pattern.test(text));
495
+ }
496
+ validateTemplateLiteral(text) {
497
+ const userInputPatterns = [
498
+ /\breq\./i,
499
+ /\brequest\./i,
500
+ /\binput\./i,
501
+ /\bparams\./i,
502
+ /\bquery\./i,
503
+ /\bbody\./i,
504
+ /\bform\./i,
505
+ /\bflask\.request\./i,
506
+ /\bdjango\.request\./i,
507
+ /\brails\.params\./i,
508
+ /\bc\.Request\./i,
509
+ /\bHttpContext\.Request\./i
510
+ ];
511
+ return text.includes('${') && userInputPatterns.some(pattern => pattern.test(text));
512
+ }
513
+ validateLogging(text) {
514
+ const loggingKeywords = ['console.log', 'console.warn', 'console.error', 'log(', 'print(', 'echo', 'printf', 'System.out.println', 'puts', 'Console.WriteLine'];
515
+ const userInputPatterns = [
516
+ /\breq\./i,
517
+ /\brequest\./i,
518
+ /\binput\./i,
519
+ /\bparams\./i,
520
+ /\bquery\./i,
521
+ /\bbody\./i,
522
+ /\bform\./i,
523
+ /\bflask\.request\./i,
524
+ /\bdjango\.request\./i,
525
+ /\brails\.params\./i,
526
+ /\bc\.Request\./i,
527
+ /\bHttpContext\.Request\./i
528
+ ];
529
+ return loggingKeywords.some(keyword => text.toLowerCase().includes(keyword)) &&
530
+ userInputPatterns.some(pattern => pattern.test(text));
531
+ }
532
+ getRemediationMessage(sink, severity, framework) {
533
+ const messages = {
534
+ 'RCE': {
535
+ 'critical': 'CRITICAL: Never use user input in code execution functions. This can lead to remote code execution. Use input validation and sanitization.',
536
+ 'high': 'HIGH: Avoid using user input in code execution functions. Implement proper input validation.',
537
+ 'medium': 'MEDIUM: Consider validating user input before code execution.',
538
+ 'low': 'LOW: Review code execution practices for security.'
539
+ },
540
+ 'SQL': {
541
+ 'critical': 'CRITICAL: Never concatenate user input into SQL queries. Use parameterized queries or prepared statements.',
542
+ 'high': 'HIGH: Use parameterized queries instead of string concatenation in SQL.',
543
+ 'medium': 'MEDIUM: Consider using parameterized queries for SQL operations.',
544
+ 'low': 'LOW: Review SQL query construction for security.'
545
+ },
546
+ 'FS': {
547
+ 'critical': 'CRITICAL: Never use user input directly in file system operations. Validate and sanitize file paths.',
548
+ 'high': 'HIGH: Validate and sanitize user input before file system operations.',
549
+ 'medium': 'MEDIUM: Consider validating file paths before operations.',
550
+ 'low': 'LOW: Review file system operation practices.'
551
+ },
552
+ 'VAR': {
553
+ 'critical': 'CRITICAL: Validate user input before assigning to variables. Use input validation libraries.',
554
+ 'high': 'HIGH: Implement input validation before variable assignment.',
555
+ 'medium': 'MEDIUM: Consider validating user input before use.',
556
+ 'low': 'LOW: Review variable assignment practices.'
557
+ },
558
+ 'DOM': {
559
+ 'critical': 'CRITICAL: Never use user input directly in DOM manipulation. Use proper escaping and validation.',
560
+ 'high': 'HIGH: Escape and validate user input before DOM manipulation.',
561
+ 'medium': 'MEDIUM: Consider escaping user input for DOM operations.',
562
+ 'low': 'LOW: Review DOM manipulation practices.'
563
+ },
564
+ 'TEMPLATE': {
565
+ 'critical': 'CRITICAL: Escape user input in template literals to prevent injection attacks.',
566
+ 'high': 'HIGH: Use proper escaping for user input in templates.',
567
+ 'medium': 'MEDIUM: Consider escaping user input in templates.',
568
+ 'low': 'LOW: Review template usage practices.'
569
+ },
570
+ 'LOG': {
571
+ 'critical': 'CRITICAL: Be careful with user input in logging to prevent information disclosure.',
572
+ 'high': 'HIGH: Sanitize user input before logging to prevent information disclosure.',
573
+ 'medium': 'MEDIUM: Consider sanitizing user input for logging.',
574
+ 'low': 'LOW: Review logging practices for information disclosure.'
575
+ }
576
+ };
577
+ let suggestion = messages[sink]?.[severity] || 'Validate and sanitize user input before use.';
578
+ if (framework) {
579
+ suggestion += this.getFrameworkSpecificAdvice(framework, sink);
580
+ }
581
+ return suggestion;
582
+ }
583
+ getFrameworkSpecificAdvice(framework, sink) {
584
+ const advice = {
585
+ 'express': {
586
+ 'RCE': ' For Express, use express-validator or Joi for input validation.',
587
+ 'SQL': ' For Express, use parameterized queries with mysql2, pg, or ORMs like Sequelize.',
588
+ 'FS': ' For Express, validate file paths and use path.join() for safe path construction.',
589
+ 'VAR': ' For Express, use express-validator middleware for input validation.'
590
+ },
591
+ 'flask': {
592
+ 'RCE': ' For Flask, use Flask-WTF forms and WTForms validators.',
593
+ 'SQL': ' For Flask, use SQLAlchemy ORM with parameterized queries.',
594
+ 'FS': ' For Flask, use secure_filename() and validate file paths.',
595
+ 'VAR': ' For Flask, use Flask-WTF forms for input validation.'
596
+ },
597
+ 'django': {
598
+ 'RCE': ' For Django, use Django forms and model validators.',
599
+ 'SQL': ' For Django, use Django ORM with parameterized queries.',
600
+ 'FS': ' For Django, use Django\'s file handling utilities.',
601
+ 'VAR': ' For Django, use Django forms for input validation.'
602
+ },
603
+ 'rails': {
604
+ 'RCE': ' For Rails, use strong parameters and model validations.',
605
+ 'SQL': ' For Rails, use ActiveRecord with parameterized queries.',
606
+ 'FS': ' For Rails, use Rails file handling utilities.',
607
+ 'VAR': ' For Rails, use strong parameters for input validation.'
608
+ },
609
+ 'gin': {
610
+ 'RCE': ' For Gin, use go-playground/validator for input validation.',
611
+ 'SQL': ' For Gin, use database/sql with prepared statements.',
612
+ 'FS': ' For Gin, validate file paths and use filepath.Clean().',
613
+ 'VAR': ' For Gin, use go-playground/validator for input validation.'
614
+ },
615
+ 'aspnet': {
616
+ 'RCE': ' For ASP.NET, use Data Annotations and FluentValidation.',
617
+ 'SQL': ' For ASP.NET, use Entity Framework with parameterized queries.',
618
+ 'FS': ' For ASP.NET, use Path.Combine() and validate file paths.',
619
+ 'VAR': ' For ASP.NET, use Data Annotations for input validation.'
620
+ }
621
+ };
622
+ return advice[framework]?.[sink] || ` For ${framework}, implement framework-specific input validation.`;
194
623
  }
195
624
  }
196
625
  exports.UnvalidatedInputRule = UnvalidatedInputRule;