@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,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
+ /// S011: Enable Encrypted Client Hello (ECH) for TLS
10
+ /// Detect TLS configurations that should enable ECH for privacy
11
+ class S011EchTlsConfigAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S011';
14
+
15
+ // TLS configuration patterns
16
+ static const _tlsConfigPatterns = [
17
+ 'securitycontext',
18
+ 'sslcontext',
19
+ 'tlsconfig',
20
+ 'tls_config',
21
+ 'httpclient',
22
+ 'sslsocket',
23
+ ];
24
+
25
+ // ECH-related patterns (enabled)
26
+ static const _echEnabledPatterns = [
27
+ 'ech',
28
+ 'encryptedclienthello',
29
+ 'encrypted_client_hello',
30
+ 'echconfig',
31
+ 'ech_config',
32
+ ];
33
+
34
+ // TLS version patterns
35
+ static const _tls13Patterns = [
36
+ 'tls1.3',
37
+ 'tls_1_3',
38
+ 'tlsv1_3',
39
+ 'version: 1.3',
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 = _S011Visitor(
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 _S011Visitor extends RecursiveAstVisitor<void> {
62
+ final String filePath;
63
+ final LineInfo lineInfo;
64
+ final List<Violation> violations;
65
+ final S011EchTlsConfigAnalyzer analyzer;
66
+
67
+ bool _hasTlsConfig = false;
68
+ bool _hasTls13 = false;
69
+ bool _hasEch = false;
70
+
71
+ _S011Visitor({
72
+ required this.filePath,
73
+ required this.lineInfo,
74
+ required this.violations,
75
+ required this.analyzer,
76
+ });
77
+
78
+ @override
79
+ void visitClassDeclaration(ClassDeclaration node) {
80
+ final source = node.toSource().toLowerCase();
81
+
82
+ // Check if this is a TLS configuration class
83
+ bool isTlsConfig = S011EchTlsConfigAnalyzer._tlsConfigPatterns
84
+ .any((p) => source.contains(p));
85
+
86
+ if (isTlsConfig) {
87
+ _hasTlsConfig = true;
88
+
89
+ // Check for TLS 1.3
90
+ _hasTls13 = S011EchTlsConfigAnalyzer._tls13Patterns
91
+ .any((p) => source.contains(p));
92
+
93
+ // Check for ECH configuration
94
+ _hasEch = S011EchTlsConfigAnalyzer._echEnabledPatterns
95
+ .any((p) => source.contains(p));
96
+
97
+ // If TLS 1.3 is used but ECH is not configured, suggest enabling it
98
+ if (_hasTls13 && !_hasEch) {
99
+ violations.add(analyzer.createViolation(
100
+ filePath: filePath,
101
+ line: analyzer.getLine(lineInfo, node.offset),
102
+ column: analyzer.getColumn(lineInfo, node.offset),
103
+ message:
104
+ 'TLS 1.3 configuration should enable ECH (Encrypted Client Hello) for enhanced privacy',
105
+ ));
106
+ }
107
+ }
108
+
109
+ super.visitClassDeclaration(node);
110
+ }
111
+
112
+ @override
113
+ void visitMethodInvocation(MethodInvocation node) {
114
+ final source = node.toSource().toLowerCase();
115
+ final methodName = node.methodName.name.toLowerCase();
116
+
117
+ // Check for TLS/SSL context creation
118
+ if (methodName.contains('security') ||
119
+ methodName.contains('ssl') ||
120
+ methodName.contains('tls')) {
121
+ bool isTlsConfig = S011EchTlsConfigAnalyzer._tlsConfigPatterns
122
+ .any((p) => source.contains(p));
123
+
124
+ if (isTlsConfig) {
125
+ bool hasTls13 = S011EchTlsConfigAnalyzer._tls13Patterns
126
+ .any((p) => source.contains(p));
127
+
128
+ bool hasEch = S011EchTlsConfigAnalyzer._echEnabledPatterns
129
+ .any((p) => source.contains(p));
130
+
131
+ // Recommend ECH for TLS 1.3 configurations
132
+ if (hasTls13 && !hasEch) {
133
+ violations.add(analyzer.createViolation(
134
+ filePath: filePath,
135
+ line: analyzer.getLine(lineInfo, node.offset),
136
+ column: analyzer.getColumn(lineInfo, node.offset),
137
+ message:
138
+ 'Consider enabling ECH for TLS 1.3 to protect SNI from network observers',
139
+ ));
140
+ }
141
+ }
142
+ }
143
+
144
+ super.visitMethodInvocation(node);
145
+ }
146
+
147
+ @override
148
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
149
+ final typeName = node.constructorName.type.name2.lexeme.toLowerCase();
150
+ final source = node.toSource().toLowerCase();
151
+
152
+ // Check for SecurityContext or similar
153
+ if (typeName.contains('security') ||
154
+ typeName.contains('ssl') ||
155
+ typeName.contains('tls')) {
156
+ bool hasTls13 = S011EchTlsConfigAnalyzer._tls13Patterns
157
+ .any((p) => source.contains(p));
158
+
159
+ bool hasEch = S011EchTlsConfigAnalyzer._echEnabledPatterns
160
+ .any((p) => source.contains(p));
161
+
162
+ if (hasTls13 && !hasEch) {
163
+ violations.add(analyzer.createViolation(
164
+ filePath: filePath,
165
+ line: analyzer.getLine(lineInfo, node.offset),
166
+ column: analyzer.getColumn(lineInfo, node.offset),
167
+ message:
168
+ 'TLS configuration with 1.3 should consider ECH for protecting server name',
169
+ ));
170
+ }
171
+ }
172
+
173
+ super.visitInstanceCreationExpression(node);
174
+ }
175
+ }
@@ -0,0 +1,255 @@
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
+ /// S012: Hardcoded Secrets
10
+ /// Detect hardcoded secrets, API keys, passwords in source code
11
+ class S012HardcodedSecretsAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S012';
14
+
15
+ // Secret variable name patterns (only truly sensitive ones)
16
+ static const _secretPatterns = [
17
+ 'password', 'passwd', 'pwd', 'secret',
18
+ 'privatekey', 'private_key', 'secretkey', 'secret_key',
19
+ 'authtoken', 'auth_token', 'accesstoken', 'access_token',
20
+ 'bearer', 'connectionstring', 'connection_string',
21
+ 'dbpassword', 'db_password', 'encryptionkey', 'encryption_key',
22
+ ];
23
+
24
+ // Error code class patterns - these contain error strings, not secrets
25
+ static const _errorCodeClassPatterns = [
26
+ 'errorcode', 'error_code', 'errorcodes', 'error_codes',
27
+ 'autherrorcode', 'auth_error_code',
28
+ ];
29
+
30
+ // Files that are typically auto-generated and contain public keys (not secrets)
31
+ static const _excludedFiles = [
32
+ 'firebase_options.dart',
33
+ 'generated_plugin_registrant.dart',
34
+ 'GeneratedPluginRegistrant.java',
35
+ 'AppDelegate.swift',
36
+ ];
37
+
38
+ // Parameter names that are public identifiers, not secrets
39
+ static const _publicKeyParams = [
40
+ 'apikey', 'api_key', // Firebase/Google API keys are public
41
+ 'appid', 'app_id', // App identifiers
42
+ 'projectid', 'project_id', // Project identifiers
43
+ 'messagingsenderid', 'messaging_sender_id',
44
+ 'storagebucket', 'storage_bucket',
45
+ 'measurementid', 'measurement_id',
46
+ 'iosbundleid', 'ios_bundle_id',
47
+ 'clientid', 'client_id', // Public OAuth client ID
48
+ ];
49
+
50
+ // Patterns that look like actual secrets (more specific patterns)
51
+ static const _secretValuePatterns = [
52
+ // Specific API key patterns (not generic Base64)
53
+ RegexPattern(r'^sk_[a-zA-Z0-9]{24,}$', 'Stripe secret key'),
54
+ RegexPattern(r'^pk_[a-zA-Z0-9]{24,}$', 'Stripe publishable key'),
55
+ RegexPattern(r'^ghp_[a-zA-Z0-9]{36,}$', 'GitHub personal access token'),
56
+ RegexPattern(r'^gho_[a-zA-Z0-9]{36,}$', 'GitHub OAuth token'),
57
+ RegexPattern(r'^xox[baprs]-[a-zA-Z0-9-]+$', 'Slack token'),
58
+ RegexPattern(r'^AKIA[0-9A-Z]{16}$', 'AWS access key'),
59
+ // Note: Removed generic Base64 pattern - too many false positives
60
+ ];
61
+
62
+ // Patterns to exclude (not secrets)
63
+ static const _excludePatterns = [
64
+ 'route', 'screen', 'page', 'widget', 'args', 'params',
65
+ 'hash_code', 'gkc_', 'generated', 'color', 'theme',
66
+ 'icon', 'image', 'asset', 'font', 'style',
67
+ ];
68
+
69
+ // Variable names that are preference/storage keys, not actual secrets
70
+ static const _keyNamePatterns = [
71
+ 'key', // ends with 'key' like _accessTokenKey
72
+ '_key', // contains '_key'
73
+ 'pref', // preference related
74
+ ];
75
+
76
+ // String values that look like storage keys or identifiers (not secrets)
77
+ static bool _isStorageKeyValue(String value) {
78
+ final lower = value.toLowerCase();
79
+ // Pattern: word_word_key or wordWordKey - these are storage key names
80
+ if ((lower.endsWith('_key') || lower.endsWith('key')) &&
81
+ (lower.contains('_') || RegExp(r'^[a-z_]+$').hasMatch(lower)) &&
82
+ !lower.contains('=') &&
83
+ value.length < 50) {
84
+ return true;
85
+ }
86
+ // Pattern: simple identifiers like 'accessToken', 'password_empty', 'X-Access-Token'
87
+ // These are preference keys, header names, or localization keys - not secrets
88
+ if (RegExp(r'^[a-zA-Z][a-zA-Z0-9_-]*$').hasMatch(value) && value.length < 50) {
89
+ return true;
90
+ }
91
+ return false;
92
+ }
93
+
94
+ /// Check if file is an error code file (contains error strings, not secrets)
95
+ static bool isErrorCodeFile(String filePath) {
96
+ final fileNameLower = filePath.split('/').last.toLowerCase().replaceAll('_', '');
97
+ return _errorCodeClassPatterns.any((p) => fileNameLower.contains(p.replaceAll('_', '')));
98
+ }
99
+
100
+ @override
101
+ List<Violation> analyze({
102
+ required CompilationUnit unit,
103
+ required String filePath,
104
+ required Rule rule,
105
+ required LineInfo lineInfo,
106
+ }) {
107
+ // Skip excluded files (auto-generated configs with public keys)
108
+ final fileName = filePath.split('/').last;
109
+ if (_excludedFiles.any((f) => fileName.contains(f))) {
110
+ return [];
111
+ }
112
+
113
+ // Skip error code files (contain error strings, not secrets)
114
+ if (isErrorCodeFile(filePath)) {
115
+ return [];
116
+ }
117
+
118
+ final violations = <Violation>[];
119
+ final visitor = _S012Visitor(
120
+ filePath: filePath,
121
+ lineInfo: lineInfo,
122
+ violations: violations,
123
+ analyzer: this,
124
+ );
125
+ unit.accept(visitor);
126
+ return violations;
127
+ }
128
+
129
+ /// Check if parameter name is a public key (not a secret)
130
+ static bool isPublicKeyParam(String paramName) {
131
+ return _publicKeyParams.any((p) => paramName.contains(p));
132
+ }
133
+ }
134
+
135
+ class RegexPattern {
136
+ final String pattern;
137
+ final String description;
138
+ const RegexPattern(this.pattern, this.description);
139
+ }
140
+
141
+ class _S012Visitor extends RecursiveAstVisitor<void> {
142
+ final String filePath;
143
+ final LineInfo lineInfo;
144
+ final List<Violation> violations;
145
+ final S012HardcodedSecretsAnalyzer analyzer;
146
+
147
+ _S012Visitor({
148
+ required this.filePath,
149
+ required this.lineInfo,
150
+ required this.violations,
151
+ required this.analyzer,
152
+ });
153
+
154
+ @override
155
+ void visitVariableDeclaration(VariableDeclaration node) {
156
+ final varName = node.name.lexeme.toLowerCase();
157
+ final initializer = node.initializer;
158
+
159
+ // Skip if variable name ends with 'key' (it's a storage key name, not a secret)
160
+ // e.g., _accessTokenKey, _refreshTokenKey are key names for SharedPreferences
161
+ if (varName.endsWith('key') || varName.contains('_key')) {
162
+ super.visitVariableDeclaration(node);
163
+ return;
164
+ }
165
+
166
+ // Check if variable name suggests a secret
167
+ bool isSecretVar = S012HardcodedSecretsAnalyzer._secretPatterns
168
+ .any((p) => varName.contains(p));
169
+
170
+ if (isSecretVar && initializer is StringLiteral) {
171
+ final value = initializer is SimpleStringLiteral ? initializer.value : '';
172
+
173
+ // Skip empty strings, environment variable references, and storage key values
174
+ if (value.isNotEmpty &&
175
+ !value.contains('env') &&
176
+ !value.contains('ENV') &&
177
+ !value.startsWith('\$') &&
178
+ !S012HardcodedSecretsAnalyzer._isStorageKeyValue(value) &&
179
+ value.length > 3) {
180
+ violations.add(analyzer.createViolation(
181
+ filePath: filePath,
182
+ line: analyzer.getLine(lineInfo, node.offset),
183
+ column: analyzer.getColumn(lineInfo, node.offset),
184
+ message: 'Hardcoded secret detected in "$varName" - use environment variables',
185
+ ));
186
+ }
187
+ }
188
+
189
+ super.visitVariableDeclaration(node);
190
+ }
191
+
192
+ @override
193
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
194
+ final value = node.value;
195
+
196
+ // Skip short strings and common non-secret patterns
197
+ if (value.length < 20) {
198
+ super.visitSimpleStringLiteral(node);
199
+ return;
200
+ }
201
+
202
+ // Skip strings that contain exclude patterns (route names, colors, etc.)
203
+ final lowerValue = value.toLowerCase();
204
+ if (S012HardcodedSecretsAnalyzer._excludePatterns.any((p) => lowerValue.contains(p))) {
205
+ super.visitSimpleStringLiteral(node);
206
+ return;
207
+ }
208
+
209
+ // Check for specific secret patterns in string values
210
+ for (final regexPattern in S012HardcodedSecretsAnalyzer._secretValuePatterns) {
211
+ final regex = RegExp(regexPattern.pattern);
212
+ if (regex.hasMatch(value)) {
213
+ violations.add(analyzer.createViolation(
214
+ filePath: filePath,
215
+ line: analyzer.getLine(lineInfo, node.offset),
216
+ column: analyzer.getColumn(lineInfo, node.offset),
217
+ message: '${regexPattern.description} detected - use environment variables',
218
+ ));
219
+ break;
220
+ }
221
+ }
222
+
223
+ super.visitSimpleStringLiteral(node);
224
+ }
225
+
226
+ @override
227
+ void visitNamedExpression(NamedExpression node) {
228
+ final paramName = node.name.label.name.toLowerCase();
229
+ final expression = node.expression;
230
+
231
+ // Skip public key parameters (Firebase API keys, app IDs, etc.)
232
+ if (S012HardcodedSecretsAnalyzer.isPublicKeyParam(paramName)) {
233
+ super.visitNamedExpression(node);
234
+ return;
235
+ }
236
+
237
+ // Check if named parameter is a secret
238
+ bool isSecretParam = S012HardcodedSecretsAnalyzer._secretPatterns
239
+ .any((p) => paramName.contains(p));
240
+
241
+ if (isSecretParam && expression is StringLiteral) {
242
+ final value = expression is SimpleStringLiteral ? expression.value : '';
243
+ if (value.isNotEmpty && value.length > 3) {
244
+ violations.add(analyzer.createViolation(
245
+ filePath: filePath,
246
+ line: analyzer.getLine(lineInfo, node.offset),
247
+ column: analyzer.getColumn(lineInfo, node.offset),
248
+ message: 'Hardcoded secret in parameter "$paramName" - use environment variables',
249
+ ));
250
+ }
251
+ }
252
+
253
+ super.visitNamedExpression(node);
254
+ }
255
+ }
@@ -0,0 +1,148 @@
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
+ /// S013: TLS Enforcement
10
+ /// Ensure HTTPS/TLS is enforced for all external communications
11
+ class S013TlsEnforcementAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S013';
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 = _S013Visitor(
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 _S013Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S013TlsEnforcementAnalyzer analyzer;
39
+
40
+ _S013Visitor({
41
+ required this.filePath,
42
+ required this.lineInfo,
43
+ required this.violations,
44
+ required this.analyzer,
45
+ });
46
+
47
+ // Allowed localhost patterns
48
+ static const _localhostPatterns = [
49
+ 'localhost', '127.0.0.1', '0.0.0.0', '::1',
50
+ ];
51
+
52
+ @override
53
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
54
+ final value = node.value.toLowerCase();
55
+
56
+ // Check for HTTP URLs (not HTTPS)
57
+ if (value.startsWith('http://')) {
58
+ // Allow localhost for development
59
+ bool isLocalhost = _localhostPatterns.any((p) => value.contains(p));
60
+
61
+ if (!isLocalhost) {
62
+ violations.add(analyzer.createViolation(
63
+ filePath: filePath,
64
+ line: analyzer.getLine(lineInfo, node.offset),
65
+ column: analyzer.getColumn(lineInfo, node.offset),
66
+ message: 'Use HTTPS instead of HTTP for secure communication',
67
+ ));
68
+ }
69
+ }
70
+
71
+ // Check for ws:// instead of wss://
72
+ if (value.startsWith('ws://')) {
73
+ bool isLocalhost = _localhostPatterns.any((p) => value.contains(p));
74
+
75
+ if (!isLocalhost) {
76
+ violations.add(analyzer.createViolation(
77
+ filePath: filePath,
78
+ line: analyzer.getLine(lineInfo, node.offset),
79
+ column: analyzer.getColumn(lineInfo, node.offset),
80
+ message: 'Use WSS instead of WS for secure WebSocket communication',
81
+ ));
82
+ }
83
+ }
84
+
85
+ super.visitSimpleStringLiteral(node);
86
+ }
87
+
88
+ @override
89
+ void visitMethodInvocation(MethodInvocation node) {
90
+ final source = node.toSource().toLowerCase();
91
+
92
+ // Check for SSL certificate verification bypass
93
+ if (source.contains('badcertificatecallback') ||
94
+ source.contains('allowbadcertificates') ||
95
+ source.contains('trustbadcertificate')) {
96
+ violations.add(analyzer.createViolation(
97
+ filePath: filePath,
98
+ line: analyzer.getLine(lineInfo, node.offset),
99
+ column: analyzer.getColumn(lineInfo, node.offset),
100
+ message: 'Do not bypass SSL certificate verification',
101
+ ));
102
+ }
103
+
104
+ super.visitMethodInvocation(node);
105
+ }
106
+
107
+ @override
108
+ void visitNamedExpression(NamedExpression node) {
109
+ final paramName = node.name.label.name.toLowerCase();
110
+ final expression = node.expression;
111
+
112
+ // Check for SSL verification disable flags
113
+ // Must be specifically SSL/TLS/certificate related, not general "verify" parameters
114
+ // Exclude: isFromVerifyEmail, isVerified, emailVerified, etc.
115
+ bool isSSLRelated = (paramName.contains('ssl') ||
116
+ paramName.contains('tls') ||
117
+ paramName.contains('certificate') ||
118
+ paramName == 'verify' || // Only exact match for "verify" param
119
+ paramName.contains('verifycert') ||
120
+ paramName.contains('verifyssl') ||
121
+ paramName.contains('verifytls') ||
122
+ paramName.contains('sslverify') ||
123
+ paramName.contains('tlsverify'));
124
+
125
+ // Exclude common non-SSL verify patterns
126
+ bool isNonSSLVerify = paramName.contains('email') ||
127
+ paramName.contains('phone') ||
128
+ paramName.contains('user') ||
129
+ paramName.contains('account') ||
130
+ paramName.contains('identity') ||
131
+ paramName.contains('from') ||
132
+ paramName.contains('otp') ||
133
+ paramName.contains('code');
134
+
135
+ if (isSSLRelated && !isNonSSLVerify) {
136
+ if (expression is BooleanLiteral && !expression.value) {
137
+ violations.add(analyzer.createViolation(
138
+ filePath: filePath,
139
+ line: analyzer.getLine(lineInfo, node.offset),
140
+ column: analyzer.getColumn(lineInfo, node.offset),
141
+ message: 'Do not disable SSL/TLS certificate verification',
142
+ ));
143
+ }
144
+ }
145
+
146
+ super.visitNamedExpression(node);
147
+ }
148
+ }
@@ -0,0 +1,117 @@
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
+ /// S014: TLS Version Enforcement
10
+ /// Enforce minimum TLS version (1.2 or higher)
11
+ class S014TlsVersionEnforcementAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S014';
14
+
15
+ // Insecure TLS/SSL versions
16
+ static const _insecureVersions = [
17
+ 'ssl2', 'ssl3', 'sslv2', 'sslv3',
18
+ 'tls1.0', 'tls1_0', 'tlsv1', 'tlsv1.0', 'tlsv1_0',
19
+ 'tls1.1', 'tls1_1', 'tlsv1.1', 'tlsv1_1',
20
+ ];
21
+
22
+ @override
23
+ List<Violation> analyze({
24
+ required CompilationUnit unit,
25
+ required String filePath,
26
+ required Rule rule,
27
+ required LineInfo lineInfo,
28
+ }) {
29
+ final violations = <Violation>[];
30
+ final visitor = _S014Visitor(
31
+ filePath: filePath,
32
+ lineInfo: lineInfo,
33
+ violations: violations,
34
+ analyzer: this,
35
+ );
36
+ unit.accept(visitor);
37
+ return violations;
38
+ }
39
+ }
40
+
41
+ class _S014Visitor extends RecursiveAstVisitor<void> {
42
+ final String filePath;
43
+ final LineInfo lineInfo;
44
+ final List<Violation> violations;
45
+ final S014TlsVersionEnforcementAnalyzer analyzer;
46
+
47
+ _S014Visitor({
48
+ required this.filePath,
49
+ required this.lineInfo,
50
+ required this.violations,
51
+ required this.analyzer,
52
+ });
53
+
54
+ @override
55
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
56
+ final value = node.value.toLowerCase().replaceAll(' ', '');
57
+
58
+ for (final version in S014TlsVersionEnforcementAnalyzer._insecureVersions) {
59
+ if (value.contains(version)) {
60
+ violations.add(analyzer.createViolation(
61
+ filePath: filePath,
62
+ line: analyzer.getLine(lineInfo, node.offset),
63
+ column: analyzer.getColumn(lineInfo, node.offset),
64
+ message: 'Insecure TLS/SSL version detected - use TLS 1.2 or higher',
65
+ ));
66
+ break;
67
+ }
68
+ }
69
+
70
+ super.visitSimpleStringLiteral(node);
71
+ }
72
+
73
+ @override
74
+ void visitPrefixedIdentifier(PrefixedIdentifier node) {
75
+ final source = node.toSource().toLowerCase();
76
+
77
+ for (final version in S014TlsVersionEnforcementAnalyzer._insecureVersions) {
78
+ if (source.contains(version)) {
79
+ violations.add(analyzer.createViolation(
80
+ filePath: filePath,
81
+ line: analyzer.getLine(lineInfo, node.offset),
82
+ column: analyzer.getColumn(lineInfo, node.offset),
83
+ message: 'Insecure TLS/SSL version - use TLS 1.2 or higher',
84
+ ));
85
+ break;
86
+ }
87
+ }
88
+
89
+ super.visitPrefixedIdentifier(node);
90
+ }
91
+
92
+ @override
93
+ void visitNamedExpression(NamedExpression node) {
94
+ final paramName = node.name.label.name.toLowerCase();
95
+
96
+ if (paramName.contains('minversion') ||
97
+ paramName.contains('min_version') ||
98
+ paramName.contains('sslversion') ||
99
+ paramName.contains('tlsversion')) {
100
+ final expression = node.expression.toSource().toLowerCase();
101
+
102
+ for (final version in S014TlsVersionEnforcementAnalyzer._insecureVersions) {
103
+ if (expression.contains(version)) {
104
+ violations.add(analyzer.createViolation(
105
+ filePath: filePath,
106
+ line: analyzer.getLine(lineInfo, node.offset),
107
+ column: analyzer.getColumn(lineInfo, node.offset),
108
+ message: 'Minimum TLS version should be 1.2 or higher',
109
+ ));
110
+ break;
111
+ }
112
+ }
113
+ }
114
+
115
+ super.visitNamedExpression(node);
116
+ }
117
+ }