@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,201 @@
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
+ /// S041: Session Token Invalidation
10
+ /// Ensure session tokens are properly invalidated on logout
11
+ class S041SessionTokenInvalidationAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S041';
14
+
15
+ // Logout method indicators
16
+ static const _logoutMethods = [
17
+ 'logout', 'log_out', 'signout', 'sign_out', 'logoff', 'log_off',
18
+ ];
19
+
20
+ // Session invalidation methods
21
+ static const _invalidateMethods = [
22
+ 'destroy', 'invalidate', 'clear', 'delete', 'remove', 'revoke',
23
+ 'terminate', 'expire', 'end',
24
+ ];
25
+
26
+ // SDK logout patterns that handle token invalidation internally
27
+ static const _sdkLogoutPatterns = [
28
+ // Auth0
29
+ 'auth0', 'webauthentication.logout', 'credentialsmanager.clearcredentials',
30
+ 'clearcredentials',
31
+ // Firebase
32
+ 'firebaseauth', 'firebase_auth', 'signout',
33
+ // Google Sign-In
34
+ 'googlesignin', 'google_sign_in',
35
+ // Apple Sign-In
36
+ 'signinwithapple', 'sign_in_with_apple',
37
+ // Generic OAuth/OIDC
38
+ 'oauth', 'oidc', 'openidconnect',
39
+ // Flutter Secure Storage
40
+ 'fluttersecurestorage', 'flutter_secure_storage', 'securestorage',
41
+ // SharedPreferences clear
42
+ 'sharedpreferences',
43
+ ];
44
+
45
+ // Patterns that indicate proper delegation to another logout method
46
+ static const _delegationPatterns = [
47
+ 'await_logout', 'awaitlogout', // await _logout() or await logout()
48
+ '_logout(', 'logout(', // calling internal logout method
49
+ '_authservice', '_authmanager',
50
+ 'authservice.logout', 'authmanager.logout', 'auth.logout',
51
+ 'viewmodel.logout', '_auth.logout',
52
+ ];
53
+
54
+ @override
55
+ List<Violation> analyze({
56
+ required CompilationUnit unit,
57
+ required String filePath,
58
+ required Rule rule,
59
+ required LineInfo lineInfo,
60
+ }) {
61
+ final violations = <Violation>[];
62
+ final visitor = _S041Visitor(
63
+ filePath: filePath,
64
+ lineInfo: lineInfo,
65
+ violations: violations,
66
+ analyzer: this,
67
+ );
68
+ unit.accept(visitor);
69
+ return violations;
70
+ }
71
+ }
72
+
73
+ class _S041Visitor extends RecursiveAstVisitor<void> {
74
+ final String filePath;
75
+ final LineInfo lineInfo;
76
+ final List<Violation> violations;
77
+ final S041SessionTokenInvalidationAnalyzer analyzer;
78
+
79
+ _S041Visitor({
80
+ required this.filePath,
81
+ required this.lineInfo,
82
+ required this.violations,
83
+ required this.analyzer,
84
+ });
85
+
86
+ @override
87
+ void visitMethodDeclaration(MethodDeclaration node) {
88
+ // Skip abstract methods (interface declarations) - they have no body to check
89
+ if (node.isAbstract || node.body == null || node.body is EmptyFunctionBody) {
90
+ super.visitMethodDeclaration(node);
91
+ return;
92
+ }
93
+
94
+ final methodName = node.name.lexeme.toLowerCase();
95
+
96
+ // Skip setter/getter methods like setClickLogout, isLogout, getLogoutState
97
+ // These are NOT actual logout implementations
98
+ if (methodName.startsWith('set') || methodName.startsWith('get') ||
99
+ methodName.startsWith('is') || methodName.startsWith('has') ||
100
+ methodName.startsWith('can') || methodName.startsWith('should') ||
101
+ methodName.contains('click') || methodName.contains('state') ||
102
+ methodName.contains('status') || methodName.contains('flag')) {
103
+ super.visitMethodDeclaration(node);
104
+ return;
105
+ }
106
+
107
+ bool isLogoutMethod = S041SessionTokenInvalidationAnalyzer._logoutMethods
108
+ .any((m) => methodName.contains(m));
109
+
110
+ if (isLogoutMethod) {
111
+ final body = node.body;
112
+ if (body != null) {
113
+ final bodySource = body.toSource().toLowerCase().replaceAll('_', '');
114
+
115
+ // Check if using SDK that handles logout internally
116
+ bool usesAuthSdk = S041SessionTokenInvalidationAnalyzer._sdkLogoutPatterns
117
+ .any((p) => bodySource.contains(p.replaceAll('_', '')));
118
+
119
+ if (usesAuthSdk) {
120
+ // SDK handles token invalidation, skip this method
121
+ super.visitMethodDeclaration(node);
122
+ return;
123
+ }
124
+
125
+ // Check if delegating to another logout method
126
+ bool delegatesToLogout = S041SessionTokenInvalidationAnalyzer._delegationPatterns
127
+ .any((p) => bodySource.contains(p.replaceAll('_', '').replaceAll(' ', '')));
128
+
129
+ if (delegatesToLogout) {
130
+ // Delegating to another method, skip
131
+ super.visitMethodDeclaration(node);
132
+ return;
133
+ }
134
+
135
+ bool hasInvalidation = S041SessionTokenInvalidationAnalyzer._invalidateMethods
136
+ .any((m) => bodySource.contains(m));
137
+
138
+ bool hasSessionOp = bodySource.contains('session') || bodySource.contains('token') ||
139
+ bodySource.contains('credential');
140
+
141
+ if (!hasInvalidation || !hasSessionOp) {
142
+ violations.add(analyzer.createViolation(
143
+ filePath: filePath,
144
+ line: analyzer.getLine(lineInfo, node.offset),
145
+ column: analyzer.getColumn(lineInfo, node.offset),
146
+ message: 'Logout should invalidate/destroy session token',
147
+ ));
148
+ }
149
+ }
150
+ }
151
+
152
+ super.visitMethodDeclaration(node);
153
+ }
154
+
155
+ @override
156
+ void visitFunctionDeclaration(FunctionDeclaration node) {
157
+ final funcName = node.name.lexeme.toLowerCase();
158
+
159
+ bool isLogoutFunc = S041SessionTokenInvalidationAnalyzer._logoutMethods
160
+ .any((m) => funcName.contains(m));
161
+
162
+ if (isLogoutFunc) {
163
+ final body = node.functionExpression.body;
164
+ if (body != null) {
165
+ final bodySource = body.toSource().toLowerCase().replaceAll('_', '');
166
+
167
+ // Check if using SDK that handles logout internally
168
+ bool usesAuthSdk = S041SessionTokenInvalidationAnalyzer._sdkLogoutPatterns
169
+ .any((p) => bodySource.contains(p.replaceAll('_', '')));
170
+
171
+ if (usesAuthSdk) {
172
+ super.visitFunctionDeclaration(node);
173
+ return;
174
+ }
175
+
176
+ // Check if delegating to another logout method
177
+ bool delegatesToLogout = S041SessionTokenInvalidationAnalyzer._delegationPatterns
178
+ .any((p) => bodySource.contains(p.replaceAll('_', '').replaceAll(' ', '')));
179
+
180
+ if (delegatesToLogout) {
181
+ super.visitFunctionDeclaration(node);
182
+ return;
183
+ }
184
+
185
+ bool hasInvalidation = S041SessionTokenInvalidationAnalyzer._invalidateMethods
186
+ .any((m) => bodySource.contains(m));
187
+
188
+ if (!hasInvalidation) {
189
+ violations.add(analyzer.createViolation(
190
+ filePath: filePath,
191
+ line: analyzer.getLine(lineInfo, node.offset),
192
+ column: analyzer.getColumn(lineInfo, node.offset),
193
+ message: 'Logout function should invalidate session',
194
+ ));
195
+ }
196
+ }
197
+ }
198
+
199
+ super.visitFunctionDeclaration(node);
200
+ }
201
+ }
@@ -0,0 +1,158 @@
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
+ /// S042: Require Re-authentication for Long-Lived Sessions
10
+ /// Sensitive operations should require re-authentication for long-lived sessions
11
+ class S042RequireReAuthenticationForLongLivedAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S042';
14
+
15
+ // Sensitive operations that need re-auth (critical account/security changes only)
16
+ static const _sensitiveOperations = [
17
+ // Password changes
18
+ 'changepassword', 'change_password', 'updatepassword', 'update_password',
19
+ 'resetpassword', 'reset_password',
20
+ // Email address changes (not notification settings)
21
+ 'changeemail', 'change_email', 'changeemailaddress', 'change_email_address',
22
+ // Account deletion
23
+ 'deleteaccount', 'delete_account', 'removeaccount', 'remove_account',
24
+ // Payment/financial
25
+ 'updatepayment', 'update_payment', 'changepayment', 'change_payment',
26
+ 'addpayment', 'add_payment', 'deletepayment', 'delete_payment',
27
+ // Security settings
28
+ 'changesecurity', 'change_security', 'enable2fa', 'disable2fa',
29
+ 'enablemfa', 'disablemfa', 'setup2fa', 'remove2fa',
30
+ ];
31
+
32
+ // Patterns to exclude (not sensitive despite similar names)
33
+ static const _excludePatterns = [
34
+ 'notification', 'notificationsetting', 'emailnotification',
35
+ 'pushnotification', 'settings', 'preference', 'consent', 'terms',
36
+ // Forgot password flow - user is NOT logged in, can't require re-auth
37
+ 'forgot', 'forgotpassword', 'forgot_password',
38
+ 'canreset', 'can_reset', 'checkreset', 'check_reset',
39
+ // Validation/getter methods (not actual operations)
40
+ 'validate', 'check', 'verify', 'isvalid', 'is_valid',
41
+ 'cansend', 'can_send', 'isempty', 'is_empty',
42
+ // Getter methods like canChangePassword, canChangeEmail - these are NOT operations
43
+ 'canchange', 'can_change',
44
+ // Setter/state methods - these just set UI state, not actual operations
45
+ 'setclick', 'set_click', // setClickDeleteAccount, setClickChangePassword
46
+ 'isclick', 'is_click', // isClickDeleteAccount
47
+ // Handler methods that run AFTER user confirmation (cleanup, not initiation)
48
+ 'handlelogout', 'handle_logout', 'handledelete', 'handle_delete',
49
+ 'handlelogoutordelete', 'handle_logout_or_delete',
50
+ 'onlogout', 'on_logout', 'ondelete', 'on_delete',
51
+ 'dologout', 'do_logout', 'dodelete', 'do_delete',
52
+ ];
53
+
54
+ // Files/layers where re-auth is NOT expected (data layer, not UI)
55
+ // Re-auth should be handled in UI/presentation layer, not repository/datasource
56
+ static const _excludedFileSuffixes = [
57
+ 'repository_impl.dart', 'repository.dart',
58
+ 'data_source.dart', 'datasource.dart',
59
+ 'remote_data_source.dart', 'local_data_source.dart',
60
+ 'api_service.dart', 'api_client.dart',
61
+ ];
62
+
63
+ // Re-auth check indicators
64
+ static const _reAuthIndicators = [
65
+ 'verifypassword', 'verify_password', 'confirmpassword', 'confirm_password',
66
+ 'reauthenticate', 're_authenticate', 'requireauth', 'require_auth',
67
+ 'checkpassword', 'check_password', 'validatepassword', 'validate_password',
68
+ 'confirmdialog', 'confirm_dialog', 'showconfirm', // UI confirmation
69
+ // Firebase/OAuth re-authentication (user must provide password again)
70
+ 'signinwithemailandpassword', 'signin_with_email_and_password',
71
+ 'signinwithcredential', 'signin_with_credential',
72
+ 'reauthenticatewithcredential', 'reauthenticate_with_credential',
73
+ // Password confirmation - user provides current password to verify identity
74
+ 'currentpassword', 'current_password', 'oldpassword', 'old_password',
75
+ ];
76
+
77
+ @override
78
+ List<Violation> analyze({
79
+ required CompilationUnit unit,
80
+ required String filePath,
81
+ required Rule rule,
82
+ required LineInfo lineInfo,
83
+ }) {
84
+ // Skip data layer files - re-auth is UI/presentation layer concern
85
+ final fileNameLower = filePath.toLowerCase();
86
+ if (_excludedFileSuffixes.any((suffix) => fileNameLower.endsWith(suffix))) {
87
+ return [];
88
+ }
89
+
90
+ final violations = <Violation>[];
91
+ final visitor = _S042Visitor(
92
+ filePath: filePath,
93
+ lineInfo: lineInfo,
94
+ violations: violations,
95
+ analyzer: this,
96
+ );
97
+ unit.accept(visitor);
98
+ return violations;
99
+ }
100
+ }
101
+
102
+ class _S042Visitor extends RecursiveAstVisitor<void> {
103
+ final String filePath;
104
+ final LineInfo lineInfo;
105
+ final List<Violation> violations;
106
+ final S042RequireReAuthenticationForLongLivedAnalyzer analyzer;
107
+
108
+ _S042Visitor({
109
+ required this.filePath,
110
+ required this.lineInfo,
111
+ required this.violations,
112
+ required this.analyzer,
113
+ });
114
+
115
+ @override
116
+ void visitMethodDeclaration(MethodDeclaration node) {
117
+ // Skip abstract methods (interface declarations) - they have no body to check
118
+ if (node.isAbstract || node.body == null || node.body is EmptyFunctionBody) {
119
+ super.visitMethodDeclaration(node);
120
+ return;
121
+ }
122
+
123
+ final methodName = node.name.lexeme.toLowerCase().replaceAll('_', '');
124
+
125
+ // Check if method name matches exclude patterns (not sensitive)
126
+ bool isExcluded = S042RequireReAuthenticationForLongLivedAnalyzer._excludePatterns
127
+ .any((p) => methodName.contains(p.replaceAll('_', '')));
128
+
129
+ if (isExcluded) {
130
+ super.visitMethodDeclaration(node);
131
+ return;
132
+ }
133
+
134
+ bool isSensitiveOp = S042RequireReAuthenticationForLongLivedAnalyzer._sensitiveOperations
135
+ .any((op) => methodName.contains(op.replaceAll('_', '')));
136
+
137
+ if (isSensitiveOp) {
138
+ final body = node.body;
139
+ if (body != null) {
140
+ final bodySource = body.toSource().toLowerCase().replaceAll('_', '');
141
+
142
+ bool hasReAuth = S042RequireReAuthenticationForLongLivedAnalyzer._reAuthIndicators
143
+ .any((r) => bodySource.contains(r.replaceAll('_', '')));
144
+
145
+ if (!hasReAuth) {
146
+ violations.add(analyzer.createViolation(
147
+ filePath: filePath,
148
+ line: analyzer.getLine(lineInfo, node.offset),
149
+ column: analyzer.getColumn(lineInfo, node.offset),
150
+ message: 'Sensitive operation should require re-authentication',
151
+ ));
152
+ }
153
+ }
154
+ }
155
+
156
+ super.visitMethodDeclaration(node);
157
+ }
158
+ }
@@ -0,0 +1,88 @@
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
+ /// S043: Password Changes Should Invalidate All Sessions
10
+ /// When password is changed, all existing sessions should be invalidated
11
+ class S043PasswordChangesInvalidateAllSessionsAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S043';
14
+
15
+ // Password change method indicators
16
+ static const _passwordChangeMethods = [
17
+ 'changepassword', 'change_password', 'updatepassword', 'update_password',
18
+ 'resetpassword', 'reset_password', 'setpassword', 'set_password',
19
+ ];
20
+
21
+ // Session invalidation indicators
22
+ static const _invalidateAllIndicators = [
23
+ 'invalidateall', 'invalidate_all', 'revokeall', 'revoke_all',
24
+ 'destroyall', 'destroy_all', 'clearallsessions', 'clear_all_sessions',
25
+ 'logoutall', 'logout_all', 'terminateall', 'terminate_all',
26
+ ];
27
+
28
+ @override
29
+ List<Violation> analyze({
30
+ required CompilationUnit unit,
31
+ required String filePath,
32
+ required Rule rule,
33
+ required LineInfo lineInfo,
34
+ }) {
35
+ final violations = <Violation>[];
36
+ final visitor = _S043Visitor(
37
+ filePath: filePath,
38
+ lineInfo: lineInfo,
39
+ violations: violations,
40
+ analyzer: this,
41
+ );
42
+ unit.accept(visitor);
43
+ return violations;
44
+ }
45
+ }
46
+
47
+ class _S043Visitor extends RecursiveAstVisitor<void> {
48
+ final String filePath;
49
+ final LineInfo lineInfo;
50
+ final List<Violation> violations;
51
+ final S043PasswordChangesInvalidateAllSessionsAnalyzer analyzer;
52
+
53
+ _S043Visitor({
54
+ required this.filePath,
55
+ required this.lineInfo,
56
+ required this.violations,
57
+ required this.analyzer,
58
+ });
59
+
60
+ @override
61
+ void visitMethodDeclaration(MethodDeclaration node) {
62
+ final methodName = node.name.lexeme.toLowerCase().replaceAll('_', '');
63
+
64
+ bool isPasswordChange = S043PasswordChangesInvalidateAllSessionsAnalyzer._passwordChangeMethods
65
+ .any((m) => methodName.contains(m.replaceAll('_', '')));
66
+
67
+ if (isPasswordChange) {
68
+ final body = node.body;
69
+ if (body != null) {
70
+ final bodySource = body.toSource().toLowerCase().replaceAll('_', '');
71
+
72
+ bool invalidatesAll = S043PasswordChangesInvalidateAllSessionsAnalyzer._invalidateAllIndicators
73
+ .any((i) => bodySource.contains(i.replaceAll('_', '')));
74
+
75
+ if (!invalidatesAll) {
76
+ violations.add(analyzer.createViolation(
77
+ filePath: filePath,
78
+ line: analyzer.getLine(lineInfo, node.offset),
79
+ column: analyzer.getColumn(lineInfo, node.offset),
80
+ message: 'Password change should invalidate all existing sessions',
81
+ ));
82
+ }
83
+ }
84
+ }
85
+
86
+ super.visitMethodDeclaration(node);
87
+ }
88
+ }
@@ -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
+ /// S044: Re-Authentication Required for Sensitive Actions
10
+ /// Require re-authentication before performing sensitive actions
11
+ /// Note: This rule focuses on critical data/permission changes
12
+ /// S042 covers account-level operations like deleteAccount, changePassword
13
+ class S044ReAuthenticationRequiredAnalyzer extends BaseAnalyzer {
14
+ @override
15
+ String get ruleId => 'S044';
16
+
17
+ // Sensitive action patterns - focus on data/permission changes
18
+ // Note: deleteaccount, changepassword are handled by S042
19
+ static const _sensitiveActions = [
20
+ 'revoke', 'grant', 'transfer', 'withdraw',
21
+ 'admin', 'privilege', 'permission', 'role',
22
+ ];
23
+
24
+ // Skip patterns already covered by S042
25
+ static const _s042Patterns = [
26
+ 'deleteaccount', 'delete_account', 'removeaccount', 'remove_account',
27
+ 'changepassword', 'change_password', 'updatepassword', 'update_password',
28
+ 'changeemail', 'change_email',
29
+ ];
30
+
31
+ @override
32
+ List<Violation> analyze({
33
+ required CompilationUnit unit,
34
+ required String filePath,
35
+ required Rule rule,
36
+ required LineInfo lineInfo,
37
+ }) {
38
+ final violations = <Violation>[];
39
+ final visitor = _S044Visitor(
40
+ filePath: filePath,
41
+ lineInfo: lineInfo,
42
+ violations: violations,
43
+ analyzer: this,
44
+ );
45
+ unit.accept(visitor);
46
+ return violations;
47
+ }
48
+ }
49
+
50
+ class _S044Visitor extends RecursiveAstVisitor<void> {
51
+ final String filePath;
52
+ final LineInfo lineInfo;
53
+ final List<Violation> violations;
54
+ final S044ReAuthenticationRequiredAnalyzer analyzer;
55
+
56
+ _S044Visitor({
57
+ required this.filePath,
58
+ required this.lineInfo,
59
+ required this.violations,
60
+ required this.analyzer,
61
+ });
62
+
63
+ @override
64
+ void visitMethodDeclaration(MethodDeclaration node) {
65
+ // Skip abstract methods (interface declarations) - they have no body to check
66
+ if (node.isAbstract || node.body == null || node.body is EmptyFunctionBody) {
67
+ super.visitMethodDeclaration(node);
68
+ return;
69
+ }
70
+
71
+ final methodName = node.name.lexeme.toLowerCase().replaceAll('_', '');
72
+
73
+ // Skip patterns already covered by S042 to avoid duplicate warnings
74
+ bool isCoveredByS042 = S044ReAuthenticationRequiredAnalyzer._s042Patterns
75
+ .any((p) => methodName.contains(p.replaceAll('_', '')));
76
+
77
+ if (isCoveredByS042) {
78
+ super.visitMethodDeclaration(node);
79
+ return;
80
+ }
81
+
82
+ // Check if this is a sensitive action
83
+ bool isSensitive = S044ReAuthenticationRequiredAnalyzer._sensitiveActions
84
+ .any((a) => methodName.contains(a));
85
+
86
+ // Check if it's related to user/account/data
87
+ bool isUserRelated = methodName.contains('user') ||
88
+ methodName.contains('account') ||
89
+ methodName.contains('profile') ||
90
+ methodName.contains('member');
91
+
92
+ if (isSensitive && isUserRelated) {
93
+ final body = node.body;
94
+ if (body != null) {
95
+ final bodySource = body.toSource().toLowerCase();
96
+
97
+ bool hasReAuth = bodySource.contains('reauth') ||
98
+ bodySource.contains('re_auth') ||
99
+ bodySource.contains('verifypassword') ||
100
+ bodySource.contains('verify_password') ||
101
+ bodySource.contains('confirmidentity') ||
102
+ bodySource.contains('confirm_identity') ||
103
+ bodySource.contains('confirmdialog') ||
104
+ bodySource.contains('confirm_dialog');
105
+
106
+ if (!hasReAuth) {
107
+ violations.add(analyzer.createViolation(
108
+ filePath: filePath,
109
+ line: analyzer.getLine(lineInfo, node.offset),
110
+ column: analyzer.getColumn(lineInfo, node.offset),
111
+ message: 'Sensitive user action should require re-authentication',
112
+ ));
113
+ }
114
+ }
115
+ }
116
+
117
+ super.visitMethodDeclaration(node);
118
+ }
119
+ }