@sun-asterisk/sunlint 1.3.43 → 1.3.44

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 (137) hide show
  1. package/dart_analyzer/README.md +226 -0
  2. package/dart_analyzer/analysis_options.yaml +66 -0
  3. package/dart_analyzer/bin/sunlint-dart-macos +0 -0
  4. package/dart_analyzer/bin/sunlint_dart_analyzer.dart +124 -0
  5. package/dart_analyzer/lib/analyzer_service.dart +625 -0
  6. package/dart_analyzer/lib/json_rpc_server.dart +275 -0
  7. package/dart_analyzer/lib/models/rule.dart +67 -0
  8. package/dart_analyzer/lib/models/symbol_table.dart +607 -0
  9. package/dart_analyzer/lib/models/violation.dart +69 -0
  10. package/dart_analyzer/lib/rules/base_analyzer.dart +52 -0
  11. package/dart_analyzer/lib/rules/common/C002_no_duplicate_code.dart +344 -0
  12. package/dart_analyzer/lib/rules/common/C003_no_vague_abbreviations.dart +318 -0
  13. package/dart_analyzer/lib/rules/common/C006_function_naming.dart +219 -0
  14. package/dart_analyzer/lib/rules/common/C008_variable_declaration_locality.dart +205 -0
  15. package/dart_analyzer/lib/rules/common/C010_limit_block_nesting.dart +162 -0
  16. package/dart_analyzer/lib/rules/common/C012_command_query_separation.dart +214 -0
  17. package/dart_analyzer/lib/rules/common/C013_no_dead_code.dart +225 -0
  18. package/dart_analyzer/lib/rules/common/C014_dependency_injection.dart +249 -0
  19. package/dart_analyzer/lib/rules/common/C017_constructor_logic.dart +158 -0
  20. package/dart_analyzer/lib/rules/common/C018_no_throw_generic_error.dart +141 -0
  21. package/dart_analyzer/lib/rules/common/C019_log_level_usage.dart +165 -0
  22. package/dart_analyzer/lib/rules/common/C020_unused_imports.dart +128 -0
  23. package/dart_analyzer/lib/rules/common/C021_import_organization.dart +86 -0
  24. package/dart_analyzer/lib/rules/common/C023_no_duplicate_variable.dart +112 -0
  25. package/dart_analyzer/lib/rules/common/C024_no_scatter_hardcoded_constants.dart +79 -0
  26. package/dart_analyzer/lib/rules/common/C029_catch_block_logging.dart +81 -0
  27. package/dart_analyzer/lib/rules/common/C030_use_custom_error_classes.dart +77 -0
  28. package/dart_analyzer/lib/rules/common/C031_validation_separation.dart +90 -0
  29. package/dart_analyzer/lib/rules/common/C033_separate_service_repository.dart +80 -0
  30. package/dart_analyzer/lib/rules/common/C035_error_logging_context.dart +148 -0
  31. package/dart_analyzer/lib/rules/common/C040_centralized_validation.dart +84 -0
  32. package/dart_analyzer/lib/rules/common/C041_no_sensitive_hardcode.dart +103 -0
  33. package/dart_analyzer/lib/rules/common/C042_boolean_name_prefix.dart +105 -0
  34. package/dart_analyzer/lib/rules/common/C043_no_console_or_print.dart +101 -0
  35. package/dart_analyzer/lib/rules/common/C047_no_duplicate_retry_logic.dart +94 -0
  36. package/dart_analyzer/lib/rules/common/C048_no_bypass_architectural_layers.dart +132 -0
  37. package/dart_analyzer/lib/rules/common/C052_parsing_or_data_transformation.dart +95 -0
  38. package/dart_analyzer/lib/rules/common/C060_no_override_superclass.dart +81 -0
  39. package/dart_analyzer/lib/rules/common/C065_one_behavior_per_test.dart +83 -0
  40. package/dart_analyzer/lib/rules/common/C067_no_hardcoded_config.dart +89 -0
  41. package/dart_analyzer/lib/rules/common/C070_no_real_time_tests.dart +99 -0
  42. package/dart_analyzer/lib/rules/common/C072_single_test_behavior.dart +78 -0
  43. package/dart_analyzer/lib/rules/common/C073_validate_required_config_on_startup.dart +82 -0
  44. package/dart_analyzer/lib/rules/common/C075_explicit_return_types.dart +85 -0
  45. package/dart_analyzer/lib/rules/common/C076_explicit_function_types.dart +104 -0
  46. package/dart_analyzer/lib/rules/dart/D001_recommended_lint_rules.dart +309 -0
  47. package/dart_analyzer/lib/rules/dart/D002_dispose_resources.dart +338 -0
  48. package/dart_analyzer/lib/rules/dart/D003_prefer_widgets_over_methods.dart +273 -0
  49. package/dart_analyzer/lib/rules/dart/D004_avoid_shrinkwrap_listview.dart +154 -0
  50. package/dart_analyzer/lib/rules/dart/D005_limit_widget_nesting.dart +265 -0
  51. package/dart_analyzer/lib/rules/dart/D006_prefer_extracting_large_callbacks.dart +135 -0
  52. package/dart_analyzer/lib/rules/dart/D007_prefer_init_first_dispose_last.dart +150 -0
  53. package/dart_analyzer/lib/rules/dart/D008_avoid_long_functions.dart +394 -0
  54. package/dart_analyzer/lib/rules/dart/D009_limit_function_parameters.dart +179 -0
  55. package/dart_analyzer/lib/rules/dart/D010_limit_cyclomatic_complexity.dart +257 -0
  56. package/dart_analyzer/lib/rules/dart/D011_prefer_named_parameters.dart +152 -0
  57. package/dart_analyzer/lib/rules/dart/D012_prefer_named_boolean_parameters.dart +156 -0
  58. package/dart_analyzer/lib/rules/dart/D013_single_public_class.dart +246 -0
  59. package/dart_analyzer/lib/rules/dart/D014_unsafe_collection_access.dart +202 -0
  60. package/dart_analyzer/lib/rules/dart/D015_copywith_all_parameters.dart +125 -0
  61. package/dart_analyzer/lib/rules/dart/D016_project_should_have_tests.dart +134 -0
  62. package/dart_analyzer/lib/rules/dart/D017_pubspec_dependencies_review.dart +187 -0
  63. package/dart_analyzer/lib/rules/dart/D018_remove_commented_code.dart +196 -0
  64. package/dart_analyzer/lib/rules/dart/D019_avoid_single_child_multi_child_widget.dart +161 -0
  65. package/dart_analyzer/lib/rules/dart/D020_limit_if_else_branches.dart +125 -0
  66. package/dart_analyzer/lib/rules/dart/D021_avoid_negated_boolean_checks.dart +227 -0
  67. package/dart_analyzer/lib/rules/dart/D022_use_setstate_correctly.dart +269 -0
  68. package/dart_analyzer/lib/rules/dart/D023_avoid_unnecessary_method_overrides.dart +191 -0
  69. package/dart_analyzer/lib/rules/dart/D024_avoid_unnecessary_stateful_widget.dart +194 -0
  70. package/dart_analyzer/lib/rules/dart/D025_avoid_nested_conditional_expressions.dart +90 -0
  71. package/dart_analyzer/lib/rules/security/S001_backend_auth_communications.dart +155 -0
  72. package/dart_analyzer/lib/rules/security/S002_os_command_injection.dart +159 -0
  73. package/dart_analyzer/lib/rules/security/S003_open_redirect_protection.dart +208 -0
  74. package/dart_analyzer/lib/rules/security/S004_sensitive_data_logging.dart +391 -0
  75. package/dart_analyzer/lib/rules/security/S005_trusted_service_authorization.dart +182 -0
  76. package/dart_analyzer/lib/rules/security/S006_no_default_credentials.dart +208 -0
  77. package/dart_analyzer/lib/rules/security/S007_output_encoding.dart +224 -0
  78. package/dart_analyzer/lib/rules/security/S008_svg_content_sanitization.dart +211 -0
  79. package/dart_analyzer/lib/rules/security/S009_no_insecure_encryption.dart +160 -0
  80. package/dart_analyzer/lib/rules/security/S010_use_csprng.dart +184 -0
  81. package/dart_analyzer/lib/rules/security/S011_ech_tls_config.dart +175 -0
  82. package/dart_analyzer/lib/rules/security/S012_hardcoded_secrets.dart +255 -0
  83. package/dart_analyzer/lib/rules/security/S013_tls_enforcement.dart +148 -0
  84. package/dart_analyzer/lib/rules/security/S014_tls_version_enforcement.dart +117 -0
  85. package/dart_analyzer/lib/rules/security/S015_insecure_tls_certificate.dart +315 -0
  86. package/dart_analyzer/lib/rules/security/S016_no_sensitive_querystring.dart +244 -0
  87. package/dart_analyzer/lib/rules/security/S017_use_parameterized_queries.dart +191 -0
  88. package/dart_analyzer/lib/rules/security/S018_no_sensitive_browser_storage.dart +175 -0
  89. package/dart_analyzer/lib/rules/security/S019_smtp_injection_protection.dart +166 -0
  90. package/dart_analyzer/lib/rules/security/S020_no_eval_dynamic_code.dart +149 -0
  91. package/dart_analyzer/lib/rules/security/S021_referrer_policy.dart +146 -0
  92. package/dart_analyzer/lib/rules/security/S022_escape_output_context.dart +111 -0
  93. package/dart_analyzer/lib/rules/security/S023_no_json_injection.dart +550 -0
  94. package/dart_analyzer/lib/rules/security/S024_xpath_xxe_protection.dart +299 -0
  95. package/dart_analyzer/lib/rules/security/S025_server_side_validation.dart +140 -0
  96. package/dart_analyzer/lib/rules/security/S026_tls_all_connections.dart +196 -0
  97. package/dart_analyzer/lib/rules/security/S027_mtls_certificate_validation.dart +195 -0
  98. package/dart_analyzer/lib/rules/security/S028_file_upload_size_limits.dart +186 -0
  99. package/dart_analyzer/lib/rules/security/S029_csrf_protection.dart +171 -0
  100. package/dart_analyzer/lib/rules/security/S030_directory_browsing_protection.dart +144 -0
  101. package/dart_analyzer/lib/rules/security/S031_secure_session_cookies.dart +118 -0
  102. package/dart_analyzer/lib/rules/security/S032_httponly_session_cookies.dart +114 -0
  103. package/dart_analyzer/lib/rules/security/S033_samesite_session_cookies.dart +120 -0
  104. package/dart_analyzer/lib/rules/security/S034_host_prefix_session_cookies.dart +160 -0
  105. package/dart_analyzer/lib/rules/security/S035_separate_app_hostnames.dart +117 -0
  106. package/dart_analyzer/lib/rules/security/S036_lfi_rfi_protection.dart +188 -0
  107. package/dart_analyzer/lib/rules/security/S037_cache_headers.dart +113 -0
  108. package/dart_analyzer/lib/rules/security/S038_no_version_headers.dart +114 -0
  109. package/dart_analyzer/lib/rules/security/S039_tls_certificate_validation.dart +131 -0
  110. package/dart_analyzer/lib/rules/security/S040_session_fixation_protection.dart +155 -0
  111. package/dart_analyzer/lib/rules/security/S041_session_token_invalidation.dart +201 -0
  112. package/dart_analyzer/lib/rules/security/S042_require_re_authentication_for_long_lived.dart +158 -0
  113. package/dart_analyzer/lib/rules/security/S043_password_changes_invalidate_all_sessions.dart +88 -0
  114. package/dart_analyzer/lib/rules/security/S044_re_authentication_required.dart +119 -0
  115. package/dart_analyzer/lib/rules/security/S045_brute_force_protection.dart +253 -0
  116. package/dart_analyzer/lib/rules/security/S046_jwt_algorithm_allowlist.dart +113 -0
  117. package/dart_analyzer/lib/rules/security/S047_oauth_pkce_protection.dart +124 -0
  118. package/dart_analyzer/lib/rules/security/S048_oauth_redirect_uri_validation.dart +134 -0
  119. package/dart_analyzer/lib/rules/security/S049_short_validity_tokens.dart +145 -0
  120. package/dart_analyzer/lib/rules/security/S050_reference_tokens_entropy.dart +234 -0
  121. package/dart_analyzer/lib/rules/security/S051_password_length_policy.dart +171 -0
  122. package/dart_analyzer/lib/rules/security/S052_weak_otp_entropy.dart +107 -0
  123. package/dart_analyzer/lib/rules/security/S053_generic_error_messages.dart +159 -0
  124. package/dart_analyzer/lib/rules/security/S054_no_default_accounts.dart +141 -0
  125. package/dart_analyzer/lib/rules/security/S055_content_type_validation.dart +324 -0
  126. package/dart_analyzer/lib/rules/security/S056_log_injection_protection.dart +119 -0
  127. package/dart_analyzer/lib/rules/security/S057_utc_logging.dart +114 -0
  128. package/dart_analyzer/lib/rules/security/S058_no_ssrf.dart +175 -0
  129. package/dart_analyzer/lib/rules/security/S059_disable_debug_mode.dart +172 -0
  130. package/dart_analyzer/lib/rules/security/S060_password_minimum_length.dart +170 -0
  131. package/dart_analyzer/lib/symbol_table_extractor.dart +510 -0
  132. package/dart_analyzer/lib/utils/common_utils.dart +26 -0
  133. package/dart_analyzer/pubspec.lock +557 -0
  134. package/dart_analyzer/pubspec.yaml +39 -0
  135. package/dart_analyzer/test/fixtures/complex_code.dart +95 -0
  136. package/docs/GENERATED_FILE_HANDLING_SUMMARY.md +2 -2
  137. package/package.json +3 -2
@@ -0,0 +1,299 @@
1
+ import 'package:analyzer/dart/ast/ast.dart';
2
+ import 'package:analyzer/dart/ast/visitor.dart';
3
+ import 'package:analyzer/source/line_info.dart';
4
+
5
+ import '../../models/rule.dart';
6
+ import '../../models/violation.dart';
7
+ import '../base_analyzer.dart';
8
+
9
+ /// S024: XPath/XXE Protection
10
+ /// Prevent XPath injection and XML External Entity attacks
11
+ class S024XpathXxeProtectionAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S024';
14
+
15
+ // XML parsing methods - must be in XML context
16
+ static const _xmlParsingMethods = [
17
+ 'parsestring', 'parsefragment', 'xmldocument',
18
+ 'parsexml', 'loadxml', 'fromxml', 'readxml',
19
+ ];
20
+
21
+ // XPath query methods - must be exact match or in XML context
22
+ // Exclude JavaScript evaluation methods like evaluateJavascript
23
+ static const _xpathMethods = [
24
+ 'xpath', 'xquery', 'selectnodes', 'selectsinglenode',
25
+ ];
26
+
27
+ // Methods that contain 'evaluate' but are NOT XPath
28
+ static const _excludeEvaluateMethods = [
29
+ 'evaluatejavascript', 'evaluatejs', 'runjavascript', 'executejs',
30
+ ];
31
+
32
+ // XML context indicators
33
+ static const _xmlContextPatterns = [
34
+ 'xmldocument', 'xmlparser', 'xdocument',
35
+ 'saxparser', 'domparser', 'xmlreader',
36
+ 'xmlelement', 'xmlnode', 'xpathdocument',
37
+ ];
38
+
39
+ // Non-XML parse methods to exclude (Dart standard library)
40
+ static const _nonXmlParseMethods = [
41
+ 'uri', 'int', 'double', 'num', 'datetime', 'bigint',
42
+ 'json', 'duration', 'bool', 'string', 'regexp',
43
+ ];
44
+
45
+ @override
46
+ List<Violation> analyze({
47
+ required CompilationUnit unit,
48
+ required String filePath,
49
+ required Rule rule,
50
+ required LineInfo lineInfo,
51
+ }) {
52
+ final violations = <Violation>[];
53
+ final visitor = _S024Visitor(
54
+ filePath: filePath,
55
+ lineInfo: lineInfo,
56
+ violations: violations,
57
+ analyzer: this,
58
+ );
59
+ unit.accept(visitor);
60
+ return violations;
61
+ }
62
+ }
63
+
64
+ class _S024Visitor extends RecursiveAstVisitor<void> {
65
+ final String filePath;
66
+ final LineInfo lineInfo;
67
+ final List<Violation> violations;
68
+ final S024XpathXxeProtectionAnalyzer analyzer;
69
+
70
+ _S024Visitor({
71
+ required this.filePath,
72
+ required this.lineInfo,
73
+ required this.violations,
74
+ required this.analyzer,
75
+ });
76
+
77
+ @override
78
+ void visitMethodInvocation(MethodInvocation node) {
79
+ final methodName = node.methodName.name.toLowerCase();
80
+ final source = node.toSource().toLowerCase();
81
+
82
+ // Check for XPath query methods with user input
83
+ // Skip JavaScript evaluation methods
84
+ bool isExcludedEvaluate = S024XpathXxeProtectionAnalyzer._excludeEvaluateMethods
85
+ .any((m) => methodName.contains(m));
86
+
87
+ bool isXpathMethod = !isExcludedEvaluate &&
88
+ (S024XpathXxeProtectionAnalyzer._xpathMethods.any((m) => methodName.contains(m)) ||
89
+ (methodName == 'evaluate' && _isInXmlContext(node)));
90
+
91
+ if (isXpathMethod) {
92
+ // Check for user input in arguments
93
+ for (final arg in node.argumentList.arguments) {
94
+ if (arg is StringInterpolation ||
95
+ (arg is SimpleIdentifier && _looksLikeUserInput(arg.name))) {
96
+ violations.add(analyzer.createViolation(
97
+ filePath: filePath,
98
+ line: analyzer.getLine(lineInfo, node.offset),
99
+ column: analyzer.getColumn(lineInfo, node.offset),
100
+ message: 'XPath injection risk - use parameterized XPath queries',
101
+ ));
102
+ break;
103
+ }
104
+ }
105
+ }
106
+
107
+ // Check for explicit XML parsing methods
108
+ // Method name must EXACTLY match or be prefixed with xml/Xml
109
+ bool isXmlParsingMethod =
110
+ S024XpathXxeProtectionAnalyzer._xmlParsingMethods.any((m) => methodName == m) ||
111
+ (methodName.startsWith('xml') && methodName.contains('parse')) ||
112
+ (methodName.startsWith('parse') && methodName.contains('xml'));
113
+
114
+ // Also check if method is 'parse' or 'tryparse' but ONLY when target is explicitly XML related
115
+ // e.g., xmlParser.parse(), XmlDocument.parse()
116
+ // Exclude common Dart standard library parse methods: Uri.parse, int.parse, etc.
117
+ final targetStr = node.target?.toSource().toLowerCase() ?? '';
118
+
119
+ // Skip if target is a known non-XML type
120
+ bool isNonXmlParseMethod = S024XpathXxeProtectionAnalyzer._nonXmlParseMethods
121
+ .any((t) => targetStr == t || targetStr.endsWith('.$t'));
122
+
123
+ bool isParseInXmlContext = (methodName == 'parse' || methodName == 'tryparse') &&
124
+ !isNonXmlParseMethod &&
125
+ S024XpathXxeProtectionAnalyzer._xmlContextPatterns.any((p) => targetStr.contains(p));
126
+
127
+ if (isXmlParsingMethod || isParseInXmlContext) {
128
+ // Check for user input in arguments
129
+ for (final arg in node.argumentList.arguments) {
130
+ if (arg is StringInterpolation ||
131
+ (arg is SimpleIdentifier && _looksLikeUserInput(arg.name))) {
132
+ violations.add(analyzer.createViolation(
133
+ filePath: filePath,
134
+ line: analyzer.getLine(lineInfo, node.offset),
135
+ column: analyzer.getColumn(lineInfo, node.offset),
136
+ message: 'XXE risk - disable external entity processing when parsing XML',
137
+ ));
138
+ break;
139
+ }
140
+ }
141
+ }
142
+
143
+ // Check for DOCTYPE with ENTITY in XML strings (XXE risk)
144
+ // Skip HTML doctypes (<!DOCTYPE html>) as they are not XXE risks
145
+ bool hasXmlDoctype = source.contains('<!doctype') && !source.contains('<!doctype html');
146
+ bool hasEntity = source.contains('<!entity');
147
+
148
+ if ((hasXmlDoctype || hasEntity) && _isInXmlContext(node)) {
149
+ violations.add(analyzer.createViolation(
150
+ filePath: filePath,
151
+ line: analyzer.getLine(lineInfo, node.offset),
152
+ column: analyzer.getColumn(lineInfo, node.offset),
153
+ message: 'XXE risk - DOCTYPE declarations can enable external entity attacks',
154
+ ));
155
+ }
156
+
157
+ super.visitMethodInvocation(node);
158
+ }
159
+
160
+ bool _looksLikeUserInput(String name) {
161
+ final lower = name.toLowerCase();
162
+ return lower.contains('input') ||
163
+ lower.contains('request') ||
164
+ lower.contains('param') ||
165
+ lower.contains('query') ||
166
+ lower.contains('body') ||
167
+ lower.contains('data');
168
+ }
169
+
170
+ bool _isInXmlContext(MethodInvocation node) {
171
+ // Check if target or parent context contains XML indicators
172
+ final targetStr = node.target?.toSource().toLowerCase() ?? '';
173
+ if (S024XpathXxeProtectionAnalyzer._xmlContextPatterns.any((p) => targetStr.contains(p))) {
174
+ return true;
175
+ }
176
+
177
+ // Check parent context
178
+ AstNode? current = node.parent;
179
+ int depth = 0;
180
+ while (current != null && depth < 5) {
181
+ final source = current.toSource().toLowerCase();
182
+ if (S024XpathXxeProtectionAnalyzer._xmlContextPatterns.any((p) => source.contains(p))) {
183
+ return true;
184
+ }
185
+ current = current.parent;
186
+ depth++;
187
+ }
188
+ return false;
189
+ }
190
+
191
+ @override
192
+ void visitStringInterpolation(StringInterpolation node) {
193
+ final source = node.toSource().toLowerCase();
194
+
195
+ // Check for XPath expressions with user input
196
+ // XPath patterns: //node[@attr='value'], //node[condition], /root/child
197
+ // Must be in XML context OR have clear XPath indicators
198
+ // Exclude URL patterns like 'https://example.com/path'
199
+ bool looksLikeXpath = _looksLikeXpathExpression(source);
200
+
201
+ if (looksLikeXpath && _isStringInterpolationInXmlContext(node)) {
202
+ bool hasInterpolation = node.elements.any((e) => e is InterpolationExpression);
203
+
204
+ if (hasInterpolation) {
205
+ violations.add(analyzer.createViolation(
206
+ filePath: filePath,
207
+ line: analyzer.getLine(lineInfo, node.offset),
208
+ column: analyzer.getColumn(lineInfo, node.offset),
209
+ message: 'XPath injection risk - avoid string interpolation in XPath queries',
210
+ ));
211
+ }
212
+ }
213
+
214
+ super.visitStringInterpolation(node);
215
+ }
216
+
217
+ /// Check if string looks like an XPath expression (not a URL or other pattern)
218
+ bool _looksLikeXpathExpression(String source) {
219
+ // XPath starts with / or // followed by node name (not :// which is URL protocol)
220
+ // Valid XPath: /root/child, //node[@attr], ./relative
221
+ // Invalid (URL): https://example.com, http://api.test
222
+
223
+ // Skip if it looks like a URL (contains ://)
224
+ if (source.contains('://')) {
225
+ return false;
226
+ }
227
+
228
+ // Skip if it starts with http, https, ftp, file protocols
229
+ if (RegExp(r'(https?|ftp|file|mailto|tel|ws|wss):').hasMatch(source)) {
230
+ return false;
231
+ }
232
+
233
+ // Must have XPath-like patterns
234
+ // - //node (descendant axis)
235
+ // - /root/child (absolute path)
236
+ // - @attribute (attribute selector)
237
+ // - [predicate] (predicate)
238
+ bool hasXpathAxis = source.contains('//') && !source.contains('://');
239
+ bool hasAttributeSelector = RegExp(r'\[@[\w-]+').hasMatch(source); // [@attr
240
+ bool hasXpathPredicate = RegExp(r'/\w+\[').hasMatch(source); // /node[
241
+
242
+ return hasXpathAxis || hasAttributeSelector || hasXpathPredicate;
243
+ }
244
+
245
+ /// Check if string interpolation is within XML context
246
+ bool _isStringInterpolationInXmlContext(StringInterpolation node) {
247
+ // Check parent context for XML-related operations
248
+ AstNode? current = node.parent;
249
+ int depth = 0;
250
+ while (current != null && depth < 8) {
251
+ final source = current.toSource().toLowerCase();
252
+
253
+ // Check for XML method invocations
254
+ if (source.contains('xpath') ||
255
+ source.contains('xquery') ||
256
+ source.contains('selectnodes') ||
257
+ source.contains('selectsinglenode') ||
258
+ source.contains('xmldocument') ||
259
+ source.contains('xmlparser')) {
260
+ return true;
261
+ }
262
+
263
+ current = current.parent;
264
+ depth++;
265
+ }
266
+ return false;
267
+ }
268
+
269
+ @override
270
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
271
+ final typeName = node.constructorName.type.name2.lexeme.toLowerCase();
272
+ final source = node.toSource().toLowerCase();
273
+
274
+ // Check for XML parser instantiation with potentially unsafe config
275
+ bool isXmlParser = typeName.contains('xmlparser') ||
276
+ typeName.contains('xmldocument') ||
277
+ typeName.contains('saxparser') ||
278
+ typeName.contains('domparser');
279
+
280
+ if (isXmlParser) {
281
+ // Check for unsafe configurations
282
+ bool hasExternalEntityConfig = source.contains('external') ||
283
+ source.contains('entity') ||
284
+ source.contains('dtd');
285
+
286
+ // Only flag if it looks like external entities might be enabled
287
+ if (hasExternalEntityConfig && !source.contains('false')) {
288
+ violations.add(analyzer.createViolation(
289
+ filePath: filePath,
290
+ line: analyzer.getLine(lineInfo, node.offset),
291
+ column: analyzer.getColumn(lineInfo, node.offset),
292
+ message: 'XXE protection - ensure external entity processing is disabled',
293
+ ));
294
+ }
295
+ }
296
+
297
+ super.visitInstanceCreationExpression(node);
298
+ }
299
+ }
@@ -0,0 +1,140 @@
1
+ import 'package:analyzer/dart/ast/ast.dart';
2
+ import 'package:analyzer/dart/ast/visitor.dart';
3
+ import 'package:analyzer/source/line_info.dart';
4
+
5
+ import '../../models/rule.dart';
6
+ import '../../models/violation.dart';
7
+ import '../base_analyzer.dart';
8
+
9
+ /// S025: Server-Side Validation
10
+ /// Ensure proper server-side input validation
11
+ class S025ServerSideValidationAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S025';
14
+
15
+ // Request/input sources
16
+ static const _inputSources = [
17
+ 'request.body', 'request.query', 'request.params', 'req.body',
18
+ 'req.query', 'req.params', 'context.request', 'httpcontext',
19
+ ];
20
+
21
+ // Dangerous operations without validation
22
+ static const _dangerousOperations = [
23
+ 'database', 'db.', 'repository', 'execute', 'query',
24
+ 'file', 'process', 'system', 'shell', 'eval',
25
+ ];
26
+
27
+ @override
28
+ List<Violation> analyze({
29
+ required CompilationUnit unit,
30
+ required String filePath,
31
+ required Rule rule,
32
+ required LineInfo lineInfo,
33
+ }) {
34
+ final violations = <Violation>[];
35
+ final visitor = _S025Visitor(
36
+ filePath: filePath,
37
+ lineInfo: lineInfo,
38
+ violations: violations,
39
+ analyzer: this,
40
+ );
41
+ unit.accept(visitor);
42
+ return violations;
43
+ }
44
+ }
45
+
46
+ class _S025Visitor extends RecursiveAstVisitor<void> {
47
+ final String filePath;
48
+ final LineInfo lineInfo;
49
+ final List<Violation> violations;
50
+ final S025ServerSideValidationAnalyzer analyzer;
51
+
52
+ // Track variables from request input
53
+ final Set<String> _inputVariables = {};
54
+
55
+ _S025Visitor({
56
+ required this.filePath,
57
+ required this.lineInfo,
58
+ required this.violations,
59
+ required this.analyzer,
60
+ });
61
+
62
+ @override
63
+ void visitVariableDeclaration(VariableDeclaration node) {
64
+ final initializer = node.initializer;
65
+ if (initializer != null) {
66
+ final source = initializer.toSource().toLowerCase();
67
+
68
+ // Track if variable comes from request input
69
+ bool isFromInput = S025ServerSideValidationAnalyzer._inputSources
70
+ .any((s) => source.contains(s));
71
+
72
+ if (isFromInput) {
73
+ _inputVariables.add(node.name.lexeme);
74
+ }
75
+ }
76
+
77
+ super.visitVariableDeclaration(node);
78
+ }
79
+
80
+ @override
81
+ void visitMethodInvocation(MethodInvocation node) {
82
+ final source = node.toSource().toLowerCase();
83
+ final methodName = node.methodName.name.toLowerCase();
84
+
85
+ // Check for dangerous operations with input data
86
+ bool isDangerousOp = S025ServerSideValidationAnalyzer._dangerousOperations
87
+ .any((op) => source.contains(op) || methodName.contains(op));
88
+
89
+ if (isDangerousOp) {
90
+ // Check if any argument is from unvalidated input
91
+ for (final arg in node.argumentList.arguments) {
92
+ final argSource = arg.toSource();
93
+
94
+ // Check if argument is a known input variable
95
+ bool isInputVar = _inputVariables.any((v) => argSource.contains(v));
96
+
97
+ // Check if directly accessing request properties
98
+ bool isDirectInput = S025ServerSideValidationAnalyzer._inputSources
99
+ .any((s) => argSource.toLowerCase().contains(s));
100
+
101
+ if (isInputVar || isDirectInput) {
102
+ // Check if validation is present - look at the entire containing method/function
103
+ bool hasValidation = false;
104
+
105
+ // Traverse up to find containing method/function body
106
+ AstNode? current = node.parent;
107
+ while (current != null) {
108
+ if (current is MethodDeclaration || current is FunctionDeclaration) {
109
+ final bodySource = current.toSource().toLowerCase();
110
+ hasValidation = bodySource.contains('validate') ||
111
+ bodySource.contains('sanitize') ||
112
+ bodySource.contains('isvalid') ||
113
+ bodySource.contains('is_valid') ||
114
+ bodySource.contains('checkinput') ||
115
+ bodySource.contains('check_input') ||
116
+ bodySource.contains('verifyinput') ||
117
+ bodySource.contains('verify_input') ||
118
+ // Check for conditional with throw/return before the dangerous op
119
+ (bodySource.contains('if') && bodySource.contains('throw'));
120
+ break;
121
+ }
122
+ current = current.parent;
123
+ }
124
+
125
+ if (!hasValidation) {
126
+ violations.add(analyzer.createViolation(
127
+ filePath: filePath,
128
+ line: analyzer.getLine(lineInfo, node.offset),
129
+ column: analyzer.getColumn(lineInfo, node.offset),
130
+ message: 'Server-side validation required before using request input in sensitive operations',
131
+ ));
132
+ }
133
+ break;
134
+ }
135
+ }
136
+ }
137
+
138
+ super.visitMethodInvocation(node);
139
+ }
140
+ }
@@ -0,0 +1,196 @@
1
+ import 'package:analyzer/dart/ast/ast.dart';
2
+ import 'package:analyzer/dart/ast/visitor.dart';
3
+ import 'package:analyzer/source/line_info.dart';
4
+
5
+ import '../../models/rule.dart';
6
+ import '../../models/violation.dart';
7
+ import '../base_analyzer.dart';
8
+
9
+ /// S026: Use TLS encryption for all inbound and outbound connections
10
+ /// Ensure all application connections use encrypted TLS protocol
11
+ class S026TlsAllConnectionsAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S026';
14
+
15
+ @override
16
+ List<Violation> analyze({
17
+ required CompilationUnit unit,
18
+ required String filePath,
19
+ required Rule rule,
20
+ required LineInfo lineInfo,
21
+ }) {
22
+ final violations = <Violation>[];
23
+ final visitor = _S026Visitor(
24
+ filePath: filePath,
25
+ lineInfo: lineInfo,
26
+ violations: violations,
27
+ analyzer: this,
28
+ );
29
+ unit.accept(visitor);
30
+ return violations;
31
+ }
32
+ }
33
+
34
+ class _S026Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S026TlsAllConnectionsAnalyzer analyzer;
39
+
40
+ // Patterns for excluded URLs (not actual connections)
41
+ static final _excludedUrlPatterns = [
42
+ RegExp(r'w3\.org/\d{4}/'), // XML/SVG namespaces
43
+ RegExp(r'schema\.org', caseSensitive: false), // Schema.org metadata
44
+ RegExp(r'example\.(com|org|net)', caseSensitive: false), // Example URLs
45
+ RegExp(r'localhost', caseSensitive: false), // Localhost
46
+ RegExp(r'127\.0\.0\.1'), // Loopback IPv4
47
+ RegExp(r'\[::1\]'), // Loopback IPv6
48
+ RegExp(r'0\.0\.0\.0'), // Any address
49
+ ];
50
+
51
+ _S026Visitor({
52
+ required this.filePath,
53
+ required this.lineInfo,
54
+ required this.violations,
55
+ required this.analyzer,
56
+ });
57
+
58
+ @override
59
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
60
+ final value = node.value;
61
+ _checkInsecureUrl(value, node);
62
+ super.visitSimpleStringLiteral(node);
63
+ }
64
+
65
+ @override
66
+ void visitStringInterpolation(StringInterpolation node) {
67
+ // Check the static parts of interpolated strings
68
+ for (final element in node.elements) {
69
+ if (element is InterpolationString) {
70
+ _checkInsecureUrl(element.value, node);
71
+ }
72
+ }
73
+ super.visitStringInterpolation(node);
74
+ }
75
+
76
+ @override
77
+ void visitAssignmentExpression(AssignmentExpression node) {
78
+ final source = node.toSource().toLowerCase();
79
+
80
+ // Check for ssl: false, tls: false patterns
81
+ if (_isInsecureSslSetting(source)) {
82
+ violations.add(analyzer.createViolation(
83
+ filePath: filePath,
84
+ line: analyzer.getLine(lineInfo, node.offset),
85
+ column: analyzer.getColumn(lineInfo, node.offset),
86
+ message: 'SSL/TLS explicitly disabled. Enable encryption for secure connections',
87
+ ));
88
+ }
89
+
90
+ // Check for badCertificateCallback = (args) => true
91
+ if (source.contains('badcertificatecallback') && source.contains('=> true')) {
92
+ violations.add(analyzer.createViolation(
93
+ filePath: filePath,
94
+ line: analyzer.getLine(lineInfo, node.offset),
95
+ column: analyzer.getColumn(lineInfo, node.offset),
96
+ message: 'TLS certificate validation disabled. This allows MITM attacks',
97
+ ));
98
+ }
99
+
100
+ super.visitAssignmentExpression(node);
101
+ }
102
+
103
+ @override
104
+ void visitNamedExpression(NamedExpression node) {
105
+ final name = node.name.label.name.toLowerCase();
106
+ final expression = node.expression;
107
+
108
+ // Check for badCertificateCallback returning true (disabling validation)
109
+ if (name == 'badcertificatecallback' && expression is FunctionExpression) {
110
+ final body = expression.body;
111
+ if (body is ExpressionFunctionBody) {
112
+ final bodySource = body.expression.toSource().toLowerCase();
113
+ if (bodySource == 'true') {
114
+ violations.add(analyzer.createViolation(
115
+ filePath: filePath,
116
+ line: analyzer.getLine(lineInfo, node.offset),
117
+ column: analyzer.getColumn(lineInfo, node.offset),
118
+ message: 'TLS certificate validation disabled. This allows MITM attacks',
119
+ ));
120
+ }
121
+ }
122
+ }
123
+
124
+ // Check for ssl: false, tls: false in named parameters
125
+ if ((name == 'ssl' || name == 'tls' || name == 'secure') &&
126
+ expression is BooleanLiteral &&
127
+ !expression.value) {
128
+ violations.add(analyzer.createViolation(
129
+ filePath: filePath,
130
+ line: analyzer.getLine(lineInfo, node.offset),
131
+ column: analyzer.getColumn(lineInfo, node.offset),
132
+ message: 'SSL/TLS disabled in connection settings. Enable encryption',
133
+ ));
134
+ }
135
+
136
+ super.visitNamedExpression(node);
137
+ }
138
+
139
+ void _checkInsecureUrl(String value, AstNode node) {
140
+ // Skip if URL is excluded (namespace, localhost, example)
141
+ if (_isExcludedUrl(value)) {
142
+ return;
143
+ }
144
+
145
+ // Check for insecure HTTP URLs
146
+ if (RegExp(r'^http://[^/]+').hasMatch(value)) {
147
+ violations.add(analyzer.createViolation(
148
+ filePath: filePath,
149
+ line: analyzer.getLine(lineInfo, node.offset),
150
+ column: analyzer.getColumn(lineInfo, node.offset),
151
+ message: 'Insecure HTTP connection detected. Use HTTPS instead: "$value"',
152
+ ));
153
+ return;
154
+ }
155
+
156
+ // Check for insecure WebSocket URLs
157
+ if (RegExp(r'^ws://[^/]+').hasMatch(value)) {
158
+ violations.add(analyzer.createViolation(
159
+ filePath: filePath,
160
+ line: analyzer.getLine(lineInfo, node.offset),
161
+ column: analyzer.getColumn(lineInfo, node.offset),
162
+ message: 'Insecure WebSocket connection detected. Use WSS instead: "$value"',
163
+ ));
164
+ return;
165
+ }
166
+
167
+ // Check for insecure FTP URLs
168
+ if (RegExp(r'^ftp://').hasMatch(value)) {
169
+ violations.add(analyzer.createViolation(
170
+ filePath: filePath,
171
+ line: analyzer.getLine(lineInfo, node.offset),
172
+ column: analyzer.getColumn(lineInfo, node.offset),
173
+ message: 'Insecure FTP connection detected. Use SFTP instead: "$value"',
174
+ ));
175
+ return;
176
+ }
177
+ }
178
+
179
+ bool _isExcludedUrl(String url) {
180
+ for (final pattern in _excludedUrlPatterns) {
181
+ if (pattern.hasMatch(url)) {
182
+ return true;
183
+ }
184
+ }
185
+ return false;
186
+ }
187
+
188
+ bool _isInsecureSslSetting(String source) {
189
+ return source.contains('ssl = false') ||
190
+ source.contains('ssl: false') ||
191
+ source.contains('tls = false') ||
192
+ source.contains('tls: false') ||
193
+ source.contains('secure = false') ||
194
+ source.contains('secure: false');
195
+ }
196
+ }