@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,253 @@
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
+ /// S045: Brute Force Protection
10
+ /// Implement rate limiting and account lockout for authentication
11
+ /// Note: For mobile apps using Auth SDKs (Auth0, Firebase, etc.),
12
+ /// rate limiting is handled server-side by the SDK provider
13
+ class S045BruteForceProtectionAnalyzer extends BaseAnalyzer {
14
+ @override
15
+ String get ruleId => 'S045';
16
+
17
+ // Authentication method indicators
18
+ static const _authMethods = [
19
+ 'login', 'signin', 'sign_in', 'authenticate', 'verifypassword',
20
+ 'verify_password', 'checkpassword', 'check_password',
21
+ ];
22
+
23
+ // Method name patterns that contain auth words but are NOT actual authentication
24
+ // These are helper methods, UI methods, or unrelated functionality
25
+ static const _nonAuthMethodPatterns = [
26
+ // URL/navigation checkers
27
+ 'isopen', 'is_open', // isOpenLogin, isOpenLoginIntro - URL checks
28
+ // Login bonus/reward related (not authentication)
29
+ 'loginbonus', 'login_bonus', // resetLoginBonus, getDailyLoginBonus
30
+ 'dailylogin', 'daily_login',
31
+ // UI builders
32
+ 'build', // _buildLoginWidget, buildLoginScreen
33
+ // State getters/setters (checking login state, not performing auth)
34
+ 'getlogin', 'get_login', 'setlogin', 'set_login',
35
+ 'isloggedin', 'is_logged_in',
36
+ // Check/validate UI state (not actual auth)
37
+ 'checklogin', 'check_login', // checkLogin() - UI state check
38
+ 'checkrelogin', 'check_relogin', // checkReLogin() - UI state check
39
+ 'checkuserlogin', 'check_user_login', // checkUserLogin() - check login state
40
+ 'checkuserlogintime', 'check_user_login_time', // checkUserLoginTime() - session check
41
+ 'validatepassword', 'validate_password', // validatePassword() - input validation
42
+ // Reset/clear operations (not auth attempts)
43
+ 'resetlogin', 'reset_login', 'clearlogin', 'clear_login',
44
+ // Get from local/cache
45
+ 'fromlocal', 'from_local', 'fromcache', 'from_cache',
46
+ // Notification related
47
+ 'notification', 'push', 'fcm', 'sendlog', 'send_log',
48
+ // Update/show dialog (UI related)
49
+ 'updateshow', 'update_show', 'showdialog', 'show_dialog',
50
+ // Badge related (app notification badges, not auth)
51
+ 'badge', 'removebadge', 'remove_badge', 'updatebadge', 'update_badge',
52
+ 'clearbadge', 'clear_badge', 'appbadge', 'app_badge',
53
+ // Logging utilities (log writing, not login)
54
+ 'loginfo', 'log_info', 'logerror', 'log_error', 'logdebug', 'log_debug',
55
+ 'logutils', 'log_utils', 'writelog', 'write_log',
56
+ 'logupload', 'log_upload', 'uploadinfo', 'upload_info',
57
+ // "notlogin" or "notloggedin" checks - checking if user is NOT logged in
58
+ 'notlogin', 'not_login', 'notloggedin', 'not_logged_in',
59
+ 'whennotlogin', 'when_not_login',
60
+ ];
61
+
62
+ // Rate limiting indicators
63
+ static const _rateLimitIndicators = [
64
+ 'ratelimit', 'rate_limit', 'throttle', 'lockout', 'lock_out',
65
+ 'maxattempts', 'max_attempts', 'failedattempts', 'failed_attempts',
66
+ 'cooldown', 'cool_down', 'delay', 'backoff',
67
+ ];
68
+
69
+ // Auth SDKs that handle rate limiting server-side
70
+ // Mobile apps using these don't need client-side rate limiting
71
+ static const _authSdkPatterns = [
72
+ // Auth0
73
+ 'auth0', 'webauthentication', 'credentialsmanager',
74
+ // Firebase Auth
75
+ 'firebaseauth', 'firebase_auth',
76
+ // Google Sign-In
77
+ 'googlesignin', 'google_sign_in',
78
+ // Apple Sign-In
79
+ 'signinwithapple', 'sign_in_with_apple',
80
+ // AWS Amplify
81
+ 'amplify', 'cognitoauth',
82
+ // Generic OAuth
83
+ 'oauth', 'oidc',
84
+ ];
85
+
86
+ // Delegation patterns - method delegates to another auth method
87
+ static const _delegationPatterns = [
88
+ 'usecase', 'use_case', // Clean architecture use case
89
+ 'repository', 'service', 'manager', 'provider',
90
+ 'authmanager', 'auth_manager', 'authservice', 'auth_service',
91
+ ];
92
+
93
+ // Repository/data layer patterns - rate limiting handled by backend
94
+ // These classes only call APIs, rate limiting is server-side
95
+ static const _repositoryClassPatterns = [
96
+ 'repositoryimpl', 'repository_impl',
97
+ 'datasource', 'data_source',
98
+ 'remotedatasource', 'remote_data_source',
99
+ ];
100
+
101
+ // GraphQL/API call patterns - backend handles rate limiting
102
+ static const _apiCallPatterns = [
103
+ 'greq', 'gql', 'graphql', // GraphQL requests
104
+ 'clienthelper', 'client_helper',
105
+ 'dio.', 'http.', 'request(',
106
+ ];
107
+
108
+ @override
109
+ List<Violation> analyze({
110
+ required CompilationUnit unit,
111
+ required String filePath,
112
+ required Rule rule,
113
+ required LineInfo lineInfo,
114
+ }) {
115
+ final violations = <Violation>[];
116
+ final visitor = _S045Visitor(
117
+ filePath: filePath,
118
+ lineInfo: lineInfo,
119
+ violations: violations,
120
+ analyzer: this,
121
+ );
122
+ unit.accept(visitor);
123
+ return violations;
124
+ }
125
+ }
126
+
127
+ class _S045Visitor extends RecursiveAstVisitor<void> {
128
+ final String filePath;
129
+ final LineInfo lineInfo;
130
+ final List<Violation> violations;
131
+ final S045BruteForceProtectionAnalyzer analyzer;
132
+
133
+ _S045Visitor({
134
+ required this.filePath,
135
+ required this.lineInfo,
136
+ required this.violations,
137
+ required this.analyzer,
138
+ });
139
+
140
+ // Track if the containing class has rate limiting
141
+ bool _classHasRateLimit = false;
142
+ // Track if class is a repository/data layer (rate limiting handled by backend)
143
+ bool _isRepositoryClass = false;
144
+
145
+ @override
146
+ void visitClassDeclaration(ClassDeclaration node) {
147
+ final className = node.name.lexeme.toLowerCase().replaceAll('_', '');
148
+
149
+ // Check class level for rate limit indicators
150
+ final classSource = node.toSource().toLowerCase().replaceAll('_', '');
151
+ _classHasRateLimit = S045BruteForceProtectionAnalyzer._rateLimitIndicators
152
+ .any((r) => classSource.contains(r.replaceAll('_', '')));
153
+
154
+ // Check if this is a repository/data layer class
155
+ _isRepositoryClass = S045BruteForceProtectionAnalyzer._repositoryClassPatterns
156
+ .any((p) => className.contains(p.replaceAll('_', '')));
157
+
158
+ super.visitClassDeclaration(node);
159
+
160
+ // Reset after processing class
161
+ _classHasRateLimit = false;
162
+ _isRepositoryClass = false;
163
+ }
164
+
165
+ @override
166
+ void visitMethodDeclaration(MethodDeclaration node) {
167
+ final methodName = node.name.lexeme.toLowerCase();
168
+ final methodNameNormalized = methodName.replaceAll('_', '');
169
+
170
+ bool isAuthMethod = S045BruteForceProtectionAnalyzer._authMethods
171
+ .any((m) => methodName.contains(m));
172
+
173
+ // Skip methods that contain auth words but are not actual authentication
174
+ bool isNonAuthMethod = S045BruteForceProtectionAnalyzer._nonAuthMethodPatterns
175
+ .any((p) => methodNameNormalized.contains(p.replaceAll('_', '')));
176
+
177
+ if (isNonAuthMethod) {
178
+ super.visitMethodDeclaration(node);
179
+ return;
180
+ }
181
+
182
+ if (isAuthMethod) {
183
+ final body = node.body;
184
+ if (body != null) {
185
+ // Skip arrow functions (helper functions like `=> true`)
186
+ if (body is ExpressionFunctionBody) {
187
+ super.visitMethodDeclaration(node);
188
+ return;
189
+ }
190
+
191
+ final bodySource = body.toSource().toLowerCase().replaceAll('_', '');
192
+
193
+ // Skip very short helper functions
194
+ if (bodySource.length < 30) {
195
+ super.visitMethodDeclaration(node);
196
+ return;
197
+ }
198
+
199
+ // Skip repository/data layer classes - rate limiting is backend responsibility
200
+ if (_isRepositoryClass) {
201
+ super.visitMethodDeclaration(node);
202
+ return;
203
+ }
204
+
205
+ // Check if using Auth SDK that handles rate limiting server-side
206
+ bool usesAuthSdk = S045BruteForceProtectionAnalyzer._authSdkPatterns
207
+ .any((p) => bodySource.contains(p.replaceAll('_', '')));
208
+
209
+ if (usesAuthSdk) {
210
+ // Auth SDK handles rate limiting on server-side
211
+ super.visitMethodDeclaration(node);
212
+ return;
213
+ }
214
+
215
+ // Check if method makes API/GraphQL calls - rate limiting handled by backend
216
+ bool makesApiCall = S045BruteForceProtectionAnalyzer._apiCallPatterns
217
+ .any((p) => bodySource.contains(p.replaceAll('_', '')));
218
+
219
+ if (makesApiCall) {
220
+ // API calls - rate limiting is server-side responsibility
221
+ super.visitMethodDeclaration(node);
222
+ return;
223
+ }
224
+
225
+ // Check if method delegates to another auth service/use case
226
+ bool delegatesToAuth = S045BruteForceProtectionAnalyzer._delegationPatterns
227
+ .any((p) => bodySource.contains(p.replaceAll('_', '')));
228
+
229
+ if (delegatesToAuth) {
230
+ // Delegating to another layer that may handle rate limiting
231
+ super.visitMethodDeclaration(node);
232
+ return;
233
+ }
234
+
235
+ // Check method body for rate limiting
236
+ bool hasRateLimit = S045BruteForceProtectionAnalyzer._rateLimitIndicators
237
+ .any((r) => bodySource.contains(r.replaceAll('_', '')));
238
+
239
+ // Also consider class-level rate limiting
240
+ if (!hasRateLimit && !_classHasRateLimit) {
241
+ violations.add(analyzer.createViolation(
242
+ filePath: filePath,
243
+ line: analyzer.getLine(lineInfo, node.offset),
244
+ column: analyzer.getColumn(lineInfo, node.offset),
245
+ message: 'Authentication should have brute force protection (rate limiting/lockout)',
246
+ ));
247
+ }
248
+ }
249
+ }
250
+
251
+ super.visitMethodDeclaration(node);
252
+ }
253
+ }
@@ -0,0 +1,113 @@
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
+ /// S046: Use algorithm allowlist for self-contained tokens
10
+ /// Detect JWT/token verification without algorithm restriction
11
+ class S046JwtAlgorithmAllowlistAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S046';
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 = _S046Visitor(
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 _S046Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S046JwtAlgorithmAllowlistAnalyzer analyzer;
39
+
40
+ _S046Visitor({
41
+ required this.filePath,
42
+ required this.lineInfo,
43
+ required this.violations,
44
+ required this.analyzer,
45
+ });
46
+
47
+ @override
48
+ void visitMethodInvocation(MethodInvocation node) {
49
+ final methodName = node.methodName.name.toLowerCase();
50
+ final source = node.toSource().toLowerCase();
51
+
52
+ // Check for JWT decode without verification (security risk)
53
+ if (methodName == 'decode' && source.contains('jwt')) {
54
+ // Check if verify is false or not specified
55
+ if (!source.contains('verify') || source.contains('verify: false')) {
56
+ violations.add(analyzer.createViolation(
57
+ filePath: filePath,
58
+ line: analyzer.getLine(lineInfo, node.offset),
59
+ column: analyzer.getColumn(lineInfo, node.offset),
60
+ message:
61
+ 'JWT decode without verification - use verify() with algorithm allowlist',
62
+ ));
63
+ }
64
+ }
65
+
66
+ // Check for JWT verify without algorithm specification
67
+ if (methodName == 'verify' && source.contains('jwt')) {
68
+ // Look for algorithm specification in arguments
69
+ if (!source.contains('algorithm') && !source.contains('alg')) {
70
+ violations.add(analyzer.createViolation(
71
+ filePath: filePath,
72
+ line: analyzer.getLine(lineInfo, node.offset),
73
+ column: analyzer.getColumn(lineInfo, node.offset),
74
+ message:
75
+ 'JWT verify without algorithm specification - specify allowed algorithms',
76
+ ));
77
+ }
78
+ }
79
+
80
+ super.visitMethodInvocation(node);
81
+ }
82
+
83
+ @override
84
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
85
+ final value = node.value.toLowerCase();
86
+
87
+ // Check for 'none' algorithm in JWT context
88
+ if (value == 'none') {
89
+ // Check surrounding context for JWT-related code
90
+ AstNode? current = node.parent;
91
+ int depth = 0;
92
+ while (current != null && depth < 5) {
93
+ final parentSource = current.toSource().toLowerCase();
94
+ if (parentSource.contains('jwt') ||
95
+ parentSource.contains('algorithm') ||
96
+ parentSource.contains('token')) {
97
+ violations.add(analyzer.createViolation(
98
+ filePath: filePath,
99
+ line: analyzer.getLine(lineInfo, node.offset),
100
+ column: analyzer.getColumn(lineInfo, node.offset),
101
+ message:
102
+ "Algorithm 'none' detected - this disables signature verification",
103
+ ));
104
+ break;
105
+ }
106
+ current = current.parent;
107
+ depth++;
108
+ }
109
+ }
110
+
111
+ super.visitSimpleStringLiteral(node);
112
+ }
113
+ }
@@ -0,0 +1,124 @@
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
+ /// S047: Protect OAuth code flow against CSRF attacks
10
+ /// Detect OAuth implementations without PKCE or state parameter
11
+ class S047OAuthPkceProtectionAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S047';
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 = _S047Visitor(
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 _S047Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S047OAuthPkceProtectionAnalyzer analyzer;
39
+
40
+ // Track OAuth-related context
41
+ bool _inOAuthContext = false;
42
+
43
+ _S047Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ @override
51
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
52
+ final value = node.value.toLowerCase();
53
+
54
+ // Check for OAuth authorization URLs
55
+ if (value.contains('authorize') && value.contains('client_id')) {
56
+ // Check if URL has code_challenge (PKCE) or state parameter
57
+ final hasCodeChallenge = value.contains('code_challenge');
58
+ final hasState = value.contains('state=');
59
+
60
+ if (!hasCodeChallenge && !hasState) {
61
+ violations.add(analyzer.createViolation(
62
+ filePath: filePath,
63
+ line: analyzer.getLine(lineInfo, node.offset),
64
+ column: analyzer.getColumn(lineInfo, node.offset),
65
+ message:
66
+ 'OAuth URL missing PKCE code_challenge or state parameter for CSRF protection',
67
+ ));
68
+ }
69
+ }
70
+
71
+ // Check for hardcoded state values (weak protection)
72
+ if (value.contains('state=')) {
73
+ // Extract state value and check if it looks hardcoded
74
+ final stateMatch = RegExp(r'state=([^&\s]+)').firstMatch(value);
75
+ if (stateMatch != null) {
76
+ final stateValue = stateMatch.group(1) ?? '';
77
+ // Short, simple state values are likely hardcoded
78
+ if (stateValue.length < 16 &&
79
+ !stateValue.contains(r'$') &&
80
+ !stateValue.contains('{')) {
81
+ violations.add(analyzer.createViolation(
82
+ filePath: filePath,
83
+ line: analyzer.getLine(lineInfo, node.offset),
84
+ column: analyzer.getColumn(lineInfo, node.offset),
85
+ message:
86
+ 'OAuth state parameter appears hardcoded - use cryptographically random value',
87
+ ));
88
+ }
89
+ }
90
+ }
91
+
92
+ super.visitSimpleStringLiteral(node);
93
+ }
94
+
95
+ @override
96
+ void visitMethodInvocation(MethodInvocation node) {
97
+ final methodName = node.methodName.name.toLowerCase();
98
+ final source = node.toSource().toLowerCase();
99
+
100
+ // Check for OAuth/OpenID connect methods without PKCE
101
+ if ((methodName.contains('oauth') ||
102
+ methodName.contains('authorize') ||
103
+ methodName.contains('authenticate')) &&
104
+ source.contains('client')) {
105
+ // Check if PKCE parameters are present
106
+ if (!source.contains('code_challenge') &&
107
+ !source.contains('codechallenge') &&
108
+ !source.contains('pkce')) {
109
+ // Also check for state parameter
110
+ if (!source.contains('state')) {
111
+ violations.add(analyzer.createViolation(
112
+ filePath: filePath,
113
+ line: analyzer.getLine(lineInfo, node.offset),
114
+ column: analyzer.getColumn(lineInfo, node.offset),
115
+ message:
116
+ 'OAuth flow without PKCE or state parameter - vulnerable to CSRF',
117
+ ));
118
+ }
119
+ }
120
+ }
121
+
122
+ super.visitMethodInvocation(node);
123
+ }
124
+ }
@@ -0,0 +1,134 @@
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
+ /// S048: Validate OAuth redirect URIs with exact string comparison
10
+ /// Detect insecure OAuth redirect URI validation
11
+ class S048OAuthRedirectUriValidationAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S048';
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 = _S048Visitor(
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 _S048Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S048OAuthRedirectUriValidationAnalyzer analyzer;
39
+
40
+ _S048Visitor({
41
+ required this.filePath,
42
+ required this.lineInfo,
43
+ required this.violations,
44
+ required this.analyzer,
45
+ });
46
+
47
+ @override
48
+ void visitMethodInvocation(MethodInvocation node) {
49
+ final methodName = node.methodName.name.toLowerCase();
50
+ final source = node.toSource().toLowerCase();
51
+
52
+ // Check for redirect URI with partial matching (insecure)
53
+ if (source.contains('redirect') || source.contains('callback')) {
54
+ // Check for startsWith validation (insecure)
55
+ if (methodName == 'startswith') {
56
+ violations.add(analyzer.createViolation(
57
+ filePath: filePath,
58
+ line: analyzer.getLine(lineInfo, node.offset),
59
+ column: analyzer.getColumn(lineInfo, node.offset),
60
+ message:
61
+ 'Redirect URI validated with startsWith() - use exact string comparison',
62
+ ));
63
+ }
64
+
65
+ // Check for contains validation (insecure)
66
+ if (methodName == 'contains' && source.contains('uri')) {
67
+ violations.add(analyzer.createViolation(
68
+ filePath: filePath,
69
+ line: analyzer.getLine(lineInfo, node.offset),
70
+ column: analyzer.getColumn(lineInfo, node.offset),
71
+ message:
72
+ 'Redirect URI validated with contains() - use exact string comparison',
73
+ ));
74
+ }
75
+
76
+ // Check for regex matching (insecure)
77
+ if (methodName == 'hasmatch' || methodName == 'firstmatch') {
78
+ if (source.contains('redirect') || source.contains('callback')) {
79
+ violations.add(analyzer.createViolation(
80
+ filePath: filePath,
81
+ line: analyzer.getLine(lineInfo, node.offset),
82
+ column: analyzer.getColumn(lineInfo, node.offset),
83
+ message:
84
+ 'Redirect URI validated with regex - use exact string comparison',
85
+ ));
86
+ }
87
+ }
88
+ }
89
+
90
+ super.visitMethodInvocation(node);
91
+ }
92
+
93
+ @override
94
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
95
+ final value = node.value;
96
+
97
+ // Check for wildcard patterns in redirect URIs
98
+ if (value.contains('*.') &&
99
+ (value.contains('redirect') || value.contains('callback'))) {
100
+ violations.add(analyzer.createViolation(
101
+ filePath: filePath,
102
+ line: analyzer.getLine(lineInfo, node.offset),
103
+ column: analyzer.getColumn(lineInfo, node.offset),
104
+ message:
105
+ 'Wildcard domain pattern for redirect URI - use exact URIs only',
106
+ ));
107
+ }
108
+
109
+ // Check for wildcard in URI list context
110
+ if (value.startsWith('*.')) {
111
+ AstNode? current = node.parent;
112
+ int depth = 0;
113
+ while (current != null && depth < 5) {
114
+ final parentSource = current.toSource().toLowerCase();
115
+ if (parentSource.contains('redirect') ||
116
+ parentSource.contains('callback') ||
117
+ parentSource.contains('alloweduri') ||
118
+ parentSource.contains('whitelist')) {
119
+ violations.add(analyzer.createViolation(
120
+ filePath: filePath,
121
+ line: analyzer.getLine(lineInfo, node.offset),
122
+ column: analyzer.getColumn(lineInfo, node.offset),
123
+ message: 'Wildcard in allowed redirect URIs - use exact URIs only',
124
+ ));
125
+ break;
126
+ }
127
+ current = current.parent;
128
+ depth++;
129
+ }
130
+ }
131
+
132
+ super.visitSimpleStringLiteral(node);
133
+ }
134
+ }