@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,550 @@
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
+ /// S023: Use output encoding when building dynamic JavaScript/JSON
10
+ /// Prevent JavaScript and JSON injection by applying proper output encoding
11
+ /// when dynamically building JavaScript content or JSON data.
12
+ ///
13
+ /// Detects:
14
+ /// - Dynamic code execution patterns (Function.apply, mirrors, runZoned)
15
+ /// - JavaScript injection via WebView/InAppWebView
16
+ /// - String interpolation/concatenation to build JavaScript code
17
+ /// - JSON construction without proper encoding
18
+ /// - innerHTML-like patterns in Flutter web
19
+ /// - Inline event handlers with user data
20
+ class S023NoJsonInjectionAnalyzer extends BaseAnalyzer {
21
+ @override
22
+ String get ruleId => 'S023';
23
+
24
+ // Methods that are safe for string interpolation (logging, debugging)
25
+ static const _safeLoggingMethods = [
26
+ 'debugprint', 'print', 'log', 'info', 'warn', 'warning',
27
+ 'error', 'debug', 'trace', 'verbose', 'loginfo', 'logerror',
28
+ 'logwarning', 'logdebug', 'logger', 'console',
29
+ ];
30
+
31
+ // Patterns that indicate actual JSON construction (not just logging)
32
+ static const _jsonConstructionPatterns = [
33
+ 'response.write', 'response.send', 'jsonresponse',
34
+ 'res.json', 'res.send', 'http.post', 'http.put',
35
+ 'dio.post', 'dio.put', 'request.body',
36
+ ];
37
+
38
+ // Dangerous JavaScript execution methods in WebView
39
+ static const _jsExecutionMethods = [
40
+ 'evaluatejavascript',
41
+ 'runjavascript',
42
+ 'runjavascriptreturningresult',
43
+ 'callasyncdart',
44
+ 'injectjavascriptfilefrompublicassetfile',
45
+ ];
46
+
47
+ // User input sources to track
48
+ static const _userInputPatterns = [
49
+ 'textcontroller',
50
+ 'texteditingcontroller',
51
+ 'formfield',
52
+ 'inputdecoration',
53
+ 'sharedpreferences',
54
+ 'securestorage',
55
+ 'getstring',
56
+ 'request.body',
57
+ 'request.query',
58
+ 'uri.queryparameters',
59
+ 'platformmessenger',
60
+ 'methodchannel',
61
+ ];
62
+
63
+ // Safe encoding functions
64
+ static const _safeEncodingFunctions = [
65
+ 'jsonencode',
66
+ 'json.encode',
67
+ 'htmlescape',
68
+ 'uriencode',
69
+ 'uri.encodefull',
70
+ 'uri.encodecomponent',
71
+ 'uri.encodequerycomponent',
72
+ 'sanitize',
73
+ 'escape',
74
+ ];
75
+
76
+ // Variable names that typically indicate safe/encoded data
77
+ static const _safeVariablePatterns = [
78
+ 'safejson',
79
+ 'encodedjson',
80
+ 'jsonstr',
81
+ 'jsonstring',
82
+ 'encoded',
83
+ 'sanitized',
84
+ 'escaped',
85
+ 'safe',
86
+ ];
87
+
88
+ // Patterns that indicate constant/config values (not user input)
89
+ static const _constantPatterns = [
90
+ 'constants.', // Constants.fieldName
91
+ 'config.', // Config.value
92
+ 'appconfig.', // AppConfig.value
93
+ 'env.', // Environment variables
94
+ 'settings.', // Settings values
95
+ ];
96
+
97
+ @override
98
+ List<Violation> analyze({
99
+ required CompilationUnit unit,
100
+ required String filePath,
101
+ required Rule rule,
102
+ required LineInfo lineInfo,
103
+ }) {
104
+ final violations = <Violation>[];
105
+ final visitor = _S023Visitor(
106
+ filePath: filePath,
107
+ lineInfo: lineInfo,
108
+ violations: violations,
109
+ analyzer: this,
110
+ );
111
+ unit.accept(visitor);
112
+ return violations;
113
+ }
114
+
115
+ /// Check if method is a safe logging method
116
+ static bool isSafeLoggingMethod(String methodName) {
117
+ return _safeLoggingMethods.any((m) => methodName.contains(m));
118
+ }
119
+
120
+ /// Check if method is a JavaScript execution method (WebView)
121
+ static bool isJsExecutionMethod(String methodName) {
122
+ return _jsExecutionMethods.any((m) => methodName.contains(m));
123
+ }
124
+
125
+ /// Check if expression contains user input
126
+ static bool containsUserInput(String source) {
127
+ final lowerSource = source.toLowerCase();
128
+ return _userInputPatterns.any((p) => lowerSource.contains(p));
129
+ }
130
+
131
+ /// Check if expression has safe encoding
132
+ static bool hasSafeEncoding(String source) {
133
+ final lowerSource = source.toLowerCase();
134
+ return _safeEncodingFunctions.any((f) => lowerSource.contains(f));
135
+ }
136
+
137
+ /// Check if variable name indicates it's already encoded/safe
138
+ static bool hasSafeVariableName(String source) {
139
+ final lowerSource = source.toLowerCase();
140
+ return _safeVariablePatterns.any((p) => lowerSource.contains(p));
141
+ }
142
+
143
+ /// Check if interpolation contains only constant values (not user input)
144
+ static bool containsOnlyConstants(String source) {
145
+ final lowerSource = source.toLowerCase();
146
+
147
+ // Check for known constant patterns
148
+ if (_constantPatterns.any((p) => lowerSource.contains(p))) {
149
+ return true;
150
+ }
151
+
152
+ // Check for all-uppercase variable names which typically indicate constants
153
+ // Pattern: ${CONSTANT_NAME} or $CONSTANT_NAME
154
+ final constPattern = RegExp(r'\$\{?[A-Z][A-Z0-9_]*\}?');
155
+ if (constPattern.hasMatch(source)) {
156
+ return true;
157
+ }
158
+
159
+ return false;
160
+ }
161
+
162
+ /// Check if the interpolation elements appear to be dynamic user data
163
+ /// Returns true if it looks like user-controlled input, false if it looks like safe data
164
+ static bool looksLikeUserInput(String source) {
165
+ final lowerSource = source.toLowerCase();
166
+
167
+ // Check for user input patterns
168
+ if (containsUserInput(source)) {
169
+ return true;
170
+ }
171
+
172
+ // Check for common patterns that indicate runtime/dynamic data
173
+ final dynamicPatterns = [
174
+ 'widget.', // Widget properties (often from user)
175
+ 'state.', // State values
176
+ '.text', // TextField values
177
+ 'input', // Input variables
178
+ 'param', // Parameters that could be user input
179
+ 'data[', // Array/map access
180
+ 'body[', // Request body access
181
+ 'query[', // Query parameter access
182
+ ];
183
+
184
+ return dynamicPatterns.any((p) => lowerSource.contains(p));
185
+ }
186
+ }
187
+
188
+ class _S023Visitor extends RecursiveAstVisitor<void> {
189
+ final String filePath;
190
+ final LineInfo lineInfo;
191
+ final List<Violation> violations;
192
+ final S023NoJsonInjectionAnalyzer analyzer;
193
+
194
+ _S023Visitor({
195
+ required this.filePath,
196
+ required this.lineInfo,
197
+ required this.violations,
198
+ required this.analyzer,
199
+ });
200
+
201
+ /// Track if we're inside a logging method call or toString method
202
+ bool _insideLoggingMethod = false;
203
+ bool _insideToString = false;
204
+
205
+ @override
206
+ void visitMethodDeclaration(MethodDeclaration node) {
207
+ final methodName = node.name.lexeme.toLowerCase();
208
+
209
+ // Skip toString methods - they're not JSON construction
210
+ if (methodName == 'tostring') {
211
+ _insideToString = true;
212
+ super.visitMethodDeclaration(node);
213
+ _insideToString = false;
214
+ return;
215
+ }
216
+
217
+ super.visitMethodDeclaration(node);
218
+ }
219
+
220
+ @override
221
+ void visitMethodInvocation(MethodInvocation node) {
222
+ final methodName = node.methodName.name.toLowerCase();
223
+
224
+ // Check if this is a logging method
225
+ bool isLoggingMethod = S023NoJsonInjectionAnalyzer.isSafeLoggingMethod(methodName);
226
+
227
+ if (isLoggingMethod) {
228
+ _insideLoggingMethod = true;
229
+ super.visitMethodInvocation(node);
230
+ _insideLoggingMethod = false;
231
+ return;
232
+ }
233
+
234
+ // 1. Check for JavaScript execution in WebView with user data
235
+ if (S023NoJsonInjectionAnalyzer.isJsExecutionMethod(methodName)) {
236
+ _checkJsExecutionMethod(node);
237
+ }
238
+
239
+ // 2. Check for Function.apply with dynamic data (Dart's equivalent to eval)
240
+ if (methodName == 'apply' && _isFunctionApply(node)) {
241
+ _checkFunctionApply(node);
242
+ }
243
+
244
+ // 3. Check for dart:mirrors usage with user data
245
+ if (methodName == 'invoke' || methodName == 'getfield' || methodName == 'setfield') {
246
+ _checkMirrorsUsage(node);
247
+ }
248
+
249
+ // 4. Check for manual JSON construction in non-logging methods
250
+ if (methodName == 'write' || methodName == 'writeln' || methodName == 'add') {
251
+ for (final arg in node.argumentList.arguments) {
252
+ if (arg is StringInterpolation) {
253
+ final source = arg.toSource();
254
+ // Only flag if it looks like structured JSON (key-value pairs)
255
+ if (_looksLikeJsonStructure(source)) {
256
+ violations.add(analyzer.createViolation(
257
+ filePath: filePath,
258
+ line: analyzer.getLine(lineInfo, node.offset),
259
+ column: analyzer.getColumn(lineInfo, node.offset),
260
+ message: 'JSON injection risk - use jsonEncode() for proper JSON encoding',
261
+ ));
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ // 5. Check for setInnerHtml or similar HTML injection methods
268
+ if (methodName == 'setinnerhtml' || methodName == 'appendhtml') {
269
+ _checkHtmlInjection(node);
270
+ }
271
+
272
+ super.visitMethodInvocation(node);
273
+ }
274
+
275
+ /// Check JavaScript execution methods for user data injection
276
+ void _checkJsExecutionMethod(MethodInvocation node) {
277
+ for (final arg in node.argumentList.arguments) {
278
+ final source = arg.toSource();
279
+
280
+ // Skip if source indicates safe encoding or safe variable names
281
+ if (S023NoJsonInjectionAnalyzer.hasSafeEncoding(source) ||
282
+ S023NoJsonInjectionAnalyzer.hasSafeVariableName(source)) {
283
+ continue;
284
+ }
285
+
286
+ // Check if argument contains interpolation or user input
287
+ if (arg is StringInterpolation || source.contains('+')) {
288
+ violations.add(analyzer.createViolation(
289
+ filePath: filePath,
290
+ line: analyzer.getLine(lineInfo, node.offset),
291
+ column: analyzer.getColumn(lineInfo, node.offset),
292
+ message: 'JavaScript injection risk - encode user data before passing to WebView JavaScript execution',
293
+ ));
294
+ return;
295
+ }
296
+
297
+ // Check for user input patterns
298
+ if (S023NoJsonInjectionAnalyzer.containsUserInput(source)) {
299
+ violations.add(analyzer.createViolation(
300
+ filePath: filePath,
301
+ line: analyzer.getLine(lineInfo, node.offset),
302
+ column: analyzer.getColumn(lineInfo, node.offset),
303
+ message: 'JavaScript injection risk - sanitize user input before WebView JavaScript execution',
304
+ ));
305
+ return;
306
+ }
307
+ }
308
+ }
309
+
310
+ /// Check if this is Function.apply call
311
+ bool _isFunctionApply(MethodInvocation node) {
312
+ final target = node.target;
313
+ if (target is Identifier) {
314
+ // Check if target type might be Function
315
+ final name = target.name.toLowerCase();
316
+ return name.contains('function') || name.contains('callback') || name.contains('handler');
317
+ }
318
+ return false;
319
+ }
320
+
321
+ /// Check Function.apply for dynamic code execution risks
322
+ void _checkFunctionApply(MethodInvocation node) {
323
+ final source = node.toSource();
324
+
325
+ if (S023NoJsonInjectionAnalyzer.containsUserInput(source)) {
326
+ violations.add(analyzer.createViolation(
327
+ filePath: filePath,
328
+ line: analyzer.getLine(lineInfo, node.offset),
329
+ column: analyzer.getColumn(lineInfo, node.offset),
330
+ message: 'Dynamic code execution risk - avoid Function.apply with user-controlled data',
331
+ ));
332
+ }
333
+ }
334
+
335
+ /// Check dart:mirrors usage for reflection-based injection
336
+ void _checkMirrorsUsage(MethodInvocation node) {
337
+ final source = node.toSource();
338
+
339
+ if (S023NoJsonInjectionAnalyzer.containsUserInput(source)) {
340
+ violations.add(analyzer.createViolation(
341
+ filePath: filePath,
342
+ line: analyzer.getLine(lineInfo, node.offset),
343
+ column: analyzer.getColumn(lineInfo, node.offset),
344
+ message: 'Reflection injection risk - validate user input before using with dart:mirrors',
345
+ ));
346
+ }
347
+ }
348
+
349
+ /// Check HTML injection methods
350
+ void _checkHtmlInjection(MethodInvocation node) {
351
+ for (final arg in node.argumentList.arguments) {
352
+ final source = arg.toSource();
353
+
354
+ // Check for unescaped user data in HTML
355
+ if ((arg is StringInterpolation || source.contains('+')) &&
356
+ !S023NoJsonInjectionAnalyzer.hasSafeEncoding(source)) {
357
+ violations.add(analyzer.createViolation(
358
+ filePath: filePath,
359
+ line: analyzer.getLine(lineInfo, node.offset),
360
+ column: analyzer.getColumn(lineInfo, node.offset),
361
+ message: 'HTML injection risk - use HtmlEscape or sanitize user data before inserting into HTML',
362
+ ));
363
+ return;
364
+ }
365
+ }
366
+ }
367
+
368
+ @override
369
+ void visitStringInterpolation(StringInterpolation node) {
370
+ // Skip if inside logging method or toString
371
+ if (_insideLoggingMethod || _insideToString) {
372
+ super.visitStringInterpolation(node);
373
+ return;
374
+ }
375
+
376
+ final source = node.toSource();
377
+
378
+ // Check if there are interpolation elements
379
+ bool hasInterpolation = node.elements.any((e) => e is InterpolationExpression);
380
+ if (!hasInterpolation) {
381
+ super.visitStringInterpolation(node);
382
+ return;
383
+ }
384
+
385
+ // Skip if source contains safe encoding or safe variable names
386
+ if (S023NoJsonInjectionAnalyzer.hasSafeEncoding(source) ||
387
+ S023NoJsonInjectionAnalyzer.hasSafeVariableName(source)) {
388
+ super.visitStringInterpolation(node);
389
+ return;
390
+ }
391
+
392
+ // Skip if interpolation contains only constants (not user input)
393
+ if (S023NoJsonInjectionAnalyzer.containsOnlyConstants(source)) {
394
+ super.visitStringInterpolation(node);
395
+ return;
396
+ }
397
+
398
+ // 1. Check for JavaScript code being built
399
+ if (_looksLikeJavaScriptCode(source)) {
400
+ // Only flag if it looks like user input is being used
401
+ // Skip if the interpolated values don't appear to be user-controlled
402
+ if (S023NoJsonInjectionAnalyzer.looksLikeUserInput(source) ||
403
+ S023NoJsonInjectionAnalyzer.containsUserInput(source)) {
404
+ violations.add(analyzer.createViolation(
405
+ filePath: filePath,
406
+ line: analyzer.getLine(lineInfo, node.offset),
407
+ column: analyzer.getColumn(lineInfo, node.offset),
408
+ message: 'JavaScript injection risk - encode data before building JavaScript code strings',
409
+ ));
410
+ super.visitStringInterpolation(node);
411
+ return;
412
+ }
413
+ // For JavaScript code without clear user input, skip to reduce false positives
414
+ super.visitStringInterpolation(node);
415
+ return;
416
+ }
417
+
418
+ // 2. Check for HTML with event handlers
419
+ if (_looksLikeHtmlWithEventHandler(source)) {
420
+ violations.add(analyzer.createViolation(
421
+ filePath: filePath,
422
+ line: analyzer.getLine(lineInfo, node.offset),
423
+ column: analyzer.getColumn(lineInfo, node.offset),
424
+ message: 'Inline event handler injection risk - avoid building HTML with inline event handlers containing user data',
425
+ ));
426
+ super.visitStringInterpolation(node);
427
+ return;
428
+ }
429
+
430
+ // 3. Check for structured JSON being constructed
431
+ if (_looksLikeJsonStructure(source)) {
432
+ violations.add(analyzer.createViolation(
433
+ filePath: filePath,
434
+ line: analyzer.getLine(lineInfo, node.offset),
435
+ column: analyzer.getColumn(lineInfo, node.offset),
436
+ message: 'JSON injection risk - use jsonEncode() instead of string interpolation for JSON',
437
+ ));
438
+ }
439
+
440
+ super.visitStringInterpolation(node);
441
+ }
442
+
443
+ /// Check if string looks like JavaScript code
444
+ bool _looksLikeJavaScriptCode(String source) {
445
+ // Patterns indicating JavaScript code construction
446
+ final jsPatterns = [
447
+ RegExp(r'\bvar\s+\w+\s*='),
448
+ RegExp(r'\blet\s+\w+\s*='),
449
+ RegExp(r'\bconst\s+\w+\s*='),
450
+ RegExp(r'\bfunction\s*\('),
451
+ RegExp(r'=>\s*\{'),
452
+ RegExp(r'return\s+'),
453
+ RegExp(r'\beval\s*\('),
454
+ RegExp(r'\bnew\s+Function\s*\('),
455
+ RegExp(r'document\.'),
456
+ RegExp(r'window\.'),
457
+ RegExp(r'javascript:', caseSensitive: false),
458
+ ];
459
+
460
+ return jsPatterns.any((pattern) => pattern.hasMatch(source));
461
+ }
462
+
463
+ /// Check if string looks like HTML with inline event handlers
464
+ bool _looksLikeHtmlWithEventHandler(String source) {
465
+ // Pattern for inline event handlers: onclick, onload, onerror, etc.
466
+ final eventHandlerPattern = RegExp(r'\bon\w+\s*=\s*["\x27]', caseSensitive: false);
467
+ return eventHandlerPattern.hasMatch(source);
468
+ }
469
+
470
+ /// Check if string looks like actual JSON structure (not just debug log)
471
+ bool _looksLikeJsonStructure(String source) {
472
+ // Must have JSON-like structure: {"key": value} or [...]
473
+ // Not just "message: $var" which is common in logging
474
+
475
+ // Check for actual JSON object pattern: {"key": or {'key':
476
+ // Pattern: { followed by optional whitespace, optional quote, word, optional quote, colon
477
+ final jsonObjectPattern = RegExp(r'\{\s*["\x27]?\w+["\x27]?\s*:');
478
+ if (jsonObjectPattern.hasMatch(source)) {
479
+ return true;
480
+ }
481
+
482
+ // Check for JSON array with objects: [{"
483
+ if (source.contains('[{') || source.contains('[ {')) {
484
+ return true;
485
+ }
486
+
487
+ return false;
488
+ }
489
+
490
+ @override
491
+ void visitBinaryExpression(BinaryExpression node) {
492
+ // Skip if inside logging method or toString
493
+ if (_insideLoggingMethod || _insideToString) {
494
+ super.visitBinaryExpression(node);
495
+ return;
496
+ }
497
+
498
+ // Check for string concatenation
499
+ if (node.operator.type.lexeme == '+') {
500
+ final combined = node.toSource();
501
+
502
+ // Check for JavaScript code building
503
+ if (_looksLikeJavaScriptCode(combined) && !S023NoJsonInjectionAnalyzer.hasSafeEncoding(combined)) {
504
+ violations.add(analyzer.createViolation(
505
+ filePath: filePath,
506
+ line: analyzer.getLine(lineInfo, node.offset),
507
+ column: analyzer.getColumn(lineInfo, node.offset),
508
+ message: 'JavaScript injection risk - avoid string concatenation for JavaScript code',
509
+ ));
510
+ super.visitBinaryExpression(node);
511
+ return;
512
+ }
513
+
514
+ // Check for JSON structure
515
+ if (_looksLikeJsonStructure(combined)) {
516
+ violations.add(analyzer.createViolation(
517
+ filePath: filePath,
518
+ line: analyzer.getLine(lineInfo, node.offset),
519
+ column: analyzer.getColumn(lineInfo, node.offset),
520
+ message: 'JSON injection risk - avoid string concatenation for JSON, use jsonEncode()',
521
+ ));
522
+ }
523
+ }
524
+
525
+ super.visitBinaryExpression(node);
526
+ }
527
+
528
+ @override
529
+ void visitAssignmentExpression(AssignmentExpression node) {
530
+ // Check for innerHTML-like assignments in Flutter web
531
+ final leftSide = node.leftHandSide.toSource().toLowerCase();
532
+
533
+ if (leftSide.contains('innerhtml') || leftSide.contains('outerhtml')) {
534
+ final rightSide = node.rightHandSide;
535
+ final source = rightSide.toSource();
536
+
537
+ if ((rightSide is StringInterpolation || source.contains('+')) &&
538
+ !S023NoJsonInjectionAnalyzer.hasSafeEncoding(source)) {
539
+ violations.add(analyzer.createViolation(
540
+ filePath: filePath,
541
+ line: analyzer.getLine(lineInfo, node.offset),
542
+ column: analyzer.getColumn(lineInfo, node.offset),
543
+ message: 'HTML injection risk - sanitize user data before assigning to innerHTML/outerHTML',
544
+ ));
545
+ }
546
+ }
547
+
548
+ super.visitAssignmentExpression(node);
549
+ }
550
+ }