@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,119 @@
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
+ /// S056: Log Injection Protection
10
+ /// Sanitize user input before logging to prevent log injection
11
+ class S056LogInjectionProtectionAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S056';
14
+
15
+ // Logging methods
16
+ static const _logMethods = [
17
+ 'log', 'info', 'debug', 'warn', 'warning', 'error', 'trace',
18
+ 'verbose', 'print', 'writeln',
19
+ ];
20
+
21
+ // User input indicators - be specific to avoid false positives
22
+ static const _userInputPatterns = [
23
+ 'request.body', 'request.query', 'request.params', 'request.input',
24
+ 'req.body', 'req.query', 'req.params', 'req.input',
25
+ 'userinput', 'user_input', 'userdata', 'user_data',
26
+ 'forminput', 'form_input', 'formdata', 'form_data',
27
+ 'rawbody', 'raw_body', 'rawinput', 'raw_input',
28
+ ];
29
+
30
+ // Safe patterns that should be skipped
31
+ static const _safePatterns = [
32
+ 'requestoptions', 'request_options', 'response',
33
+ 'statuscode', 'status_code', 'path', 'url', 'method',
34
+ 'contenttype', 'content_type', 'headers',
35
+ // Controller values - sanitized by framework
36
+ 'controller', '.text', '.value',
37
+ // Class/widget names containing "forminput" but not actual user input
38
+ 'forminputbirthday', 'forminputname', 'forminputemail', 'forminputphone',
39
+ 'forminputaddress', 'forminputcard', 'forminputpassword',
40
+ ];
41
+
42
+ @override
43
+ List<Violation> analyze({
44
+ required CompilationUnit unit,
45
+ required String filePath,
46
+ required Rule rule,
47
+ required LineInfo lineInfo,
48
+ }) {
49
+ final violations = <Violation>[];
50
+ final visitor = _S056Visitor(
51
+ filePath: filePath,
52
+ lineInfo: lineInfo,
53
+ violations: violations,
54
+ analyzer: this,
55
+ );
56
+ unit.accept(visitor);
57
+ return violations;
58
+ }
59
+ }
60
+
61
+ class _S056Visitor extends RecursiveAstVisitor<void> {
62
+ final String filePath;
63
+ final LineInfo lineInfo;
64
+ final List<Violation> violations;
65
+ final S056LogInjectionProtectionAnalyzer analyzer;
66
+
67
+ _S056Visitor({
68
+ required this.filePath,
69
+ required this.lineInfo,
70
+ required this.violations,
71
+ required this.analyzer,
72
+ });
73
+
74
+ @override
75
+ void visitMethodInvocation(MethodInvocation node) {
76
+ final methodName = node.methodName.name.toLowerCase();
77
+
78
+ bool isLogMethod = S056LogInjectionProtectionAnalyzer._logMethods
79
+ .any((m) => methodName == m || methodName.endsWith(m));
80
+
81
+ if (isLogMethod) {
82
+ for (final arg in node.argumentList.arguments) {
83
+ final argSource = arg.toSource().toLowerCase().replaceAll('_', '');
84
+
85
+ // Skip safe patterns (response metadata, etc.)
86
+ bool isSafe = S056LogInjectionProtectionAnalyzer._safePatterns
87
+ .any((p) => argSource.contains(p.replaceAll('_', '')));
88
+
89
+ if (isSafe) {
90
+ continue;
91
+ }
92
+
93
+ // Check if logging user input
94
+ bool hasUserInput = S056LogInjectionProtectionAnalyzer._userInputPatterns
95
+ .any((p) => argSource.contains(p.replaceAll('_', '')));
96
+
97
+ if (hasUserInput) {
98
+ // Check for sanitization
99
+ bool isSanitized = argSource.contains('sanitize') ||
100
+ argSource.contains('escape') ||
101
+ argSource.contains('encode') ||
102
+ argSource.contains('replace');
103
+
104
+ if (!isSanitized && (arg is StringInterpolation || arg is SimpleIdentifier)) {
105
+ violations.add(analyzer.createViolation(
106
+ filePath: filePath,
107
+ line: analyzer.getLine(lineInfo, node.offset),
108
+ column: analyzer.getColumn(lineInfo, node.offset),
109
+ message: 'Log injection risk - sanitize user input before logging',
110
+ ));
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ super.visitMethodInvocation(node);
118
+ }
119
+ }
@@ -0,0 +1,114 @@
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
+ /// S057: UTC Logging
10
+ /// Use UTC timestamps in logs for consistency across time zones
11
+ class S057UtcLoggingAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S057';
14
+
15
+ // Logging methods
16
+ static const _logMethods = [
17
+ 'log', 'info', 'debug', 'warn', 'warning', 'error', 'trace',
18
+ ];
19
+
20
+ @override
21
+ List<Violation> analyze({
22
+ required CompilationUnit unit,
23
+ required String filePath,
24
+ required Rule rule,
25
+ required LineInfo lineInfo,
26
+ }) {
27
+ final violations = <Violation>[];
28
+ final visitor = _S057Visitor(
29
+ filePath: filePath,
30
+ lineInfo: lineInfo,
31
+ violations: violations,
32
+ analyzer: this,
33
+ );
34
+ unit.accept(visitor);
35
+ return violations;
36
+ }
37
+ }
38
+
39
+ class _S057Visitor extends RecursiveAstVisitor<void> {
40
+ final String filePath;
41
+ final LineInfo lineInfo;
42
+ final List<Violation> violations;
43
+ final S057UtcLoggingAnalyzer analyzer;
44
+
45
+ _S057Visitor({
46
+ required this.filePath,
47
+ required this.lineInfo,
48
+ required this.violations,
49
+ required this.analyzer,
50
+ });
51
+
52
+ @override
53
+ void visitMethodInvocation(MethodInvocation node) {
54
+ final methodName = node.methodName.name.toLowerCase();
55
+
56
+ bool isLogMethod = S057UtcLoggingAnalyzer._logMethods
57
+ .any((m) => methodName == m);
58
+
59
+ if (isLogMethod) {
60
+ final source = node.toSource().toLowerCase();
61
+
62
+ // Check for DateTime.now() without .toUtc()
63
+ if (source.contains('datetime.now()') && !source.contains('.toutc()')) {
64
+ violations.add(analyzer.createViolation(
65
+ filePath: filePath,
66
+ line: analyzer.getLine(lineInfo, node.offset),
67
+ column: analyzer.getColumn(lineInfo, node.offset),
68
+ message: 'Use UTC timestamps in logs - call .toUtc() on DateTime',
69
+ ));
70
+ }
71
+
72
+ // Check for local time methods
73
+ if (source.contains('tolocal()') || source.contains('localtime')) {
74
+ violations.add(analyzer.createViolation(
75
+ filePath: filePath,
76
+ line: analyzer.getLine(lineInfo, node.offset),
77
+ column: analyzer.getColumn(lineInfo, node.offset),
78
+ message: 'Avoid local time in logs - use UTC for consistency',
79
+ ));
80
+ }
81
+ }
82
+
83
+ super.visitMethodInvocation(node);
84
+ }
85
+
86
+ @override
87
+ void visitVariableDeclaration(VariableDeclaration node) {
88
+ final varName = node.name.lexeme.toLowerCase();
89
+ final initializer = node.initializer;
90
+
91
+ // Only check for LOG timestamp variables, not general timestamps
92
+ // uploadTimestamp, activityTime, etc. are NOT logging timestamps
93
+ // Must have 'log' in name to be considered a logging timestamp
94
+ bool isLogTimestamp = (varName.contains('log') && varName.contains('time')) ||
95
+ (varName.contains('log') && varName.contains('timestamp')) ||
96
+ varName == 'logtime' || varName == 'log_time' ||
97
+ varName == 'logtimestamp' || varName == 'log_timestamp';
98
+
99
+ if (isLogTimestamp && initializer != null) {
100
+ final initSource = initializer.toSource().toLowerCase();
101
+
102
+ if (initSource.contains('datetime.now()') && !initSource.contains('.toutc()')) {
103
+ violations.add(analyzer.createViolation(
104
+ filePath: filePath,
105
+ line: analyzer.getLine(lineInfo, node.offset),
106
+ column: analyzer.getColumn(lineInfo, node.offset),
107
+ message: 'Log timestamp should use UTC',
108
+ ));
109
+ }
110
+ }
111
+
112
+ super.visitVariableDeclaration(node);
113
+ }
114
+ }
@@ -0,0 +1,175 @@
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
+ /// S058: No SSRF (Server-Side Request Forgery)
10
+ /// Prevent SSRF attacks by validating URLs from user input
11
+ class S058NoSsrfAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S058';
14
+
15
+ // HTTP request methods - must be actual HTTP client methods
16
+ static const _httpMethods = [
17
+ 'fetch', 'httprequest', 'http_request',
18
+ ];
19
+
20
+ // HTTP client names - check target/receiver
21
+ static const _httpClients = [
22
+ 'http', 'dio', 'client', 'httpclient', 'http_client',
23
+ 'request', 'axios', 'fetch',
24
+ ];
25
+
26
+ // User input indicators - be more specific to avoid false positives
27
+ static const _userInputPatterns = [
28
+ 'request.body', 'request.query', 'request.params', 'request.url',
29
+ 'req.body', 'req.query', 'req.params', 'req.url',
30
+ 'userinput', 'user_input', 'userurl', 'user_url',
31
+ 'externalurl', 'external_url', 'remoteurl', 'remote_url',
32
+ 'targeturl', 'target_url',
33
+ ];
34
+
35
+ // Variable names that suggest user input
36
+ static final _userInputVarPatterns = [
37
+ RegExp(r'\buserurl\b', caseSensitive: false),
38
+ RegExp(r'\buser_url\b', caseSensitive: false),
39
+ RegExp(r'\btargeturl\b', caseSensitive: false),
40
+ RegExp(r'\btarget_url\b', caseSensitive: false),
41
+ RegExp(r'\bexternalurl\b', caseSensitive: false),
42
+ RegExp(r'\bexternal_url\b', caseSensitive: false),
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 = _S058Visitor(
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 _S058Visitor extends RecursiveAstVisitor<void> {
65
+ final String filePath;
66
+ final LineInfo lineInfo;
67
+ final List<Violation> violations;
68
+ final S058NoSsrfAnalyzer analyzer;
69
+
70
+ _S058Visitor({
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 if this is an HTTP client method call
83
+ bool isHttpRequest = S058NoSsrfAnalyzer._httpMethods
84
+ .any((m) => methodName == m);
85
+
86
+ // Check target/receiver for HTTP client usage
87
+ final target = node.target?.toSource().toLowerCase() ?? '';
88
+ bool isHttpClient = S058NoSsrfAnalyzer._httpClients
89
+ .any((c) => target.contains(c));
90
+
91
+ // Check for common HTTP patterns: dio.get(), http.post(), client.request()
92
+ bool isHttpOp = (methodName == 'get' || methodName == 'post' ||
93
+ methodName == 'put' || methodName == 'patch' ||
94
+ methodName == 'delete' || methodName == 'head') && isHttpClient;
95
+
96
+ if (isHttpRequest || isHttpOp) {
97
+ // Check if URL comes from user input
98
+ for (final arg in node.argumentList.arguments) {
99
+ final argSource = arg.toSource().toLowerCase();
100
+
101
+ // Check against string patterns
102
+ bool isUserInput = S058NoSsrfAnalyzer._userInputPatterns
103
+ .any((p) => argSource.contains(p));
104
+
105
+ // Check against variable name patterns (e.g., userUrl parameter)
106
+ if (!isUserInput) {
107
+ isUserInput = S058NoSsrfAnalyzer._userInputVarPatterns
108
+ .any((p) => p.hasMatch(argSource));
109
+ }
110
+
111
+ if (isUserInput) {
112
+ // Check for URL validation
113
+ bool hasValidation = argSource.contains('validate') ||
114
+ argSource.contains('whitelist') ||
115
+ argSource.contains('allowlist') ||
116
+ argSource.contains('isallowed') ||
117
+ argSource.contains('is_allowed');
118
+
119
+ if (!hasValidation) {
120
+ violations.add(analyzer.createViolation(
121
+ filePath: filePath,
122
+ line: analyzer.getLine(lineInfo, node.offset),
123
+ column: analyzer.getColumn(lineInfo, node.offset),
124
+ message: 'SSRF risk - validate/whitelist URLs from user input',
125
+ ));
126
+ }
127
+ break;
128
+ }
129
+ }
130
+ }
131
+
132
+ super.visitMethodInvocation(node);
133
+ }
134
+
135
+ @override
136
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
137
+ final typeName = node.constructorName.type.name2.lexeme;
138
+
139
+ // Check for Uri construction with user input
140
+ if (typeName == 'Uri') {
141
+ for (final arg in node.argumentList.arguments) {
142
+ final argSource = arg.toSource().toLowerCase();
143
+
144
+ // Check against string patterns
145
+ bool isUserInput = S058NoSsrfAnalyzer._userInputPatterns
146
+ .any((p) => argSource.contains(p));
147
+
148
+ // Check against variable name patterns
149
+ if (!isUserInput) {
150
+ isUserInput = S058NoSsrfAnalyzer._userInputVarPatterns
151
+ .any((p) => p.hasMatch(argSource));
152
+ }
153
+
154
+ if (isUserInput) {
155
+ // Check for validation
156
+ bool hasValidation = argSource.contains('validate') ||
157
+ argSource.contains('whitelist') ||
158
+ argSource.contains('allowlist');
159
+
160
+ if (!hasValidation) {
161
+ violations.add(analyzer.createViolation(
162
+ filePath: filePath,
163
+ line: analyzer.getLine(lineInfo, node.offset),
164
+ column: analyzer.getColumn(lineInfo, node.offset),
165
+ message: 'SSRF risk - Uri created from user input without validation',
166
+ ));
167
+ }
168
+ break;
169
+ }
170
+ }
171
+ }
172
+
173
+ super.visitInstanceCreationExpression(node);
174
+ }
175
+ }
@@ -0,0 +1,172 @@
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
+ /// S059: Disable debug modes in production environments
10
+ /// Detect hardcoded debug modes and development features
11
+ class S059DisableDebugModeAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S059';
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 = _S059Visitor(
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 _S059Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S059DisableDebugModeAnalyzer analyzer;
39
+
40
+ _S059Visitor({
41
+ required this.filePath,
42
+ required this.lineInfo,
43
+ required this.violations,
44
+ required this.analyzer,
45
+ });
46
+
47
+ @override
48
+ void visitVariableDeclaration(VariableDeclaration node) {
49
+ final name = node.name.lexeme.toLowerCase();
50
+ final initializer = node.initializer;
51
+
52
+ // Check for debug flags hardcoded to true
53
+ if ((name == 'debug' ||
54
+ name == 'isdebug' ||
55
+ name == 'debugmode' ||
56
+ name == 'is_debug') &&
57
+ initializer != null) {
58
+ final value = initializer.toSource().toLowerCase();
59
+ if (value == 'true') {
60
+ violations.add(analyzer.createViolation(
61
+ filePath: filePath,
62
+ line: analyzer.getLine(lineInfo, node.offset),
63
+ column: analyzer.getColumn(lineInfo, node.offset),
64
+ message: 'DEBUG hardcoded to true - use environment variable',
65
+ ));
66
+ }
67
+ }
68
+
69
+ // Check for verbose/logging flags
70
+ if ((name == 'verbose' || name == 'enablelogging') &&
71
+ initializer != null) {
72
+ final value = initializer.toSource().toLowerCase();
73
+ if (value == 'true') {
74
+ violations.add(analyzer.createViolation(
75
+ filePath: filePath,
76
+ line: analyzer.getLine(lineInfo, node.offset),
77
+ column: analyzer.getColumn(lineInfo, node.offset),
78
+ message:
79
+ 'Verbose logging hardcoded to true - disable in production',
80
+ ));
81
+ }
82
+ }
83
+
84
+ super.visitVariableDeclaration(node);
85
+ }
86
+
87
+ @override
88
+ void visitNamedExpression(NamedExpression node) {
89
+ final name = node.name.label.name.toLowerCase();
90
+ final value = node.expression.toSource().toLowerCase();
91
+
92
+ // Check for debugShowCheckedModeBanner (Flutter)
93
+ if (name == 'debugshowcheckedmodebanner' && value == 'true') {
94
+ violations.add(analyzer.createViolation(
95
+ filePath: filePath,
96
+ line: analyzer.getLine(lineInfo, node.offset),
97
+ column: analyzer.getColumn(lineInfo, node.offset),
98
+ message:
99
+ 'debugShowCheckedModeBanner: true - set to false in production',
100
+ ));
101
+ }
102
+
103
+ // Check for showPerformanceOverlay
104
+ if (name == 'showperformanceoverlay' && value == 'true') {
105
+ violations.add(analyzer.createViolation(
106
+ filePath: filePath,
107
+ line: analyzer.getLine(lineInfo, node.offset),
108
+ column: analyzer.getColumn(lineInfo, node.offset),
109
+ message: 'showPerformanceOverlay: true - disable in production',
110
+ ));
111
+ }
112
+
113
+ // Check for debugPaintSizeEnabled
114
+ if (name == 'debugpaintsizeenabled' && value == 'true') {
115
+ violations.add(analyzer.createViolation(
116
+ filePath: filePath,
117
+ line: analyzer.getLine(lineInfo, node.offset),
118
+ column: analyzer.getColumn(lineInfo, node.offset),
119
+ message: 'debugPaintSizeEnabled: true - disable in production',
120
+ ));
121
+ }
122
+
123
+ // Check for checkerboardRasterCacheImages
124
+ if (name == 'checkerboardrastercacheimages' && value == 'true') {
125
+ violations.add(analyzer.createViolation(
126
+ filePath: filePath,
127
+ line: analyzer.getLine(lineInfo, node.offset),
128
+ column: analyzer.getColumn(lineInfo, node.offset),
129
+ message:
130
+ 'checkerboardRasterCacheImages: true - disable in production',
131
+ ));
132
+ }
133
+
134
+ super.visitNamedExpression(node);
135
+ }
136
+
137
+ @override
138
+ void visitAssignmentExpression(AssignmentExpression node) {
139
+ final left = node.leftHandSide.toSource().toLowerCase();
140
+ final right = node.rightHandSide.toSource().toLowerCase();
141
+
142
+ // Check for debug assignments
143
+ if ((left.contains('debug') || left.contains('isdebug')) &&
144
+ right == 'true') {
145
+ violations.add(analyzer.createViolation(
146
+ filePath: filePath,
147
+ line: analyzer.getLine(lineInfo, node.offset),
148
+ column: analyzer.getColumn(lineInfo, node.offset),
149
+ message: 'Debug mode enabled - use environment check for production',
150
+ ));
151
+ }
152
+
153
+ super.visitAssignmentExpression(node);
154
+ }
155
+
156
+ @override
157
+ void visitMethodInvocation(MethodInvocation node) {
158
+ final methodName = node.methodName.name.toLowerCase();
159
+
160
+ // Check for debugPrint in production code
161
+ if (methodName == 'debugprint') {
162
+ violations.add(analyzer.createViolation(
163
+ filePath: filePath,
164
+ line: analyzer.getLine(lineInfo, node.offset),
165
+ column: analyzer.getColumn(lineInfo, node.offset),
166
+ message: 'debugPrint() found - use proper logging or remove in production',
167
+ ));
168
+ }
169
+
170
+ super.visitMethodInvocation(node);
171
+ }
172
+ }