@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,211 @@
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
+ /// S008: Validate and sanitize SVG content
10
+ /// Detect unsafe SVG handling that could allow script injection
11
+ /// NOTE: Skip local assets (SvgPicture.asset) as they are bundled with the app
12
+ class S008SvgContentSanitizationAnalyzer extends BaseAnalyzer {
13
+ @override
14
+ String get ruleId => 'S008';
15
+
16
+ // Dangerous SVG elements that should be sanitized
17
+ static const _dangerousSvgElements = [
18
+ '<script',
19
+ 'foreignobject',
20
+ 'onclick',
21
+ 'onload',
22
+ 'onerror',
23
+ 'onmouseover',
24
+ 'xlink:href',
25
+ ];
26
+
27
+ // SVG handling patterns
28
+ static const _svgHandlingPatterns = [
29
+ 'svg',
30
+ 'svgpicture',
31
+ 'svgimage',
32
+ 'flutter_svg',
33
+ ];
34
+
35
+ // Safe sanitization patterns
36
+ static const _sanitizationPatterns = [
37
+ 'sanitize',
38
+ 'dompurify',
39
+ 'allowedtags',
40
+ 'allowedelements',
41
+ 'removeattributes',
42
+ 'stripscripts',
43
+ ];
44
+
45
+ // Safe asset patterns - local bundled resources, not user input
46
+ static const _safeAssetPatterns = [
47
+ '.asset(',
48
+ 'assets.', // Generated assets: Assets.svgs.icon.svg()
49
+ 'assets/',
50
+ 'images.', // Generated images
51
+ 'images/',
52
+ 'icons.', // Generated icons
53
+ 'icons/',
54
+ 'svgs.', // Generated SVGs: Assets.svgs.something.svg()
55
+ '.svg(', // SvgGenImage extension method: icon.svg()
56
+ 'res/',
57
+ 'resources/',
58
+ 'assetname',
59
+ 'assetpath',
60
+ 'package:',
61
+ ];
62
+
63
+ @override
64
+ List<Violation> analyze({
65
+ required CompilationUnit unit,
66
+ required String filePath,
67
+ required Rule rule,
68
+ required LineInfo lineInfo,
69
+ }) {
70
+ final violations = <Violation>[];
71
+ final visitor = _S008Visitor(
72
+ filePath: filePath,
73
+ lineInfo: lineInfo,
74
+ violations: violations,
75
+ analyzer: this,
76
+ );
77
+ unit.accept(visitor);
78
+ return violations;
79
+ }
80
+ }
81
+
82
+ class _S008Visitor extends RecursiveAstVisitor<void> {
83
+ final String filePath;
84
+ final LineInfo lineInfo;
85
+ final List<Violation> violations;
86
+ final S008SvgContentSanitizationAnalyzer analyzer;
87
+
88
+ _S008Visitor({
89
+ required this.filePath,
90
+ required this.lineInfo,
91
+ required this.violations,
92
+ required this.analyzer,
93
+ });
94
+
95
+ @override
96
+ void visitMethodInvocation(MethodInvocation node) {
97
+ final source = node.toSource().toLowerCase();
98
+ final methodName = node.methodName.name.toLowerCase();
99
+
100
+ // Check for SVG handling from user input
101
+ bool isSvgHandling = S008SvgContentSanitizationAnalyzer._svgHandlingPatterns
102
+ .any((p) => source.contains(p) || methodName.contains(p));
103
+
104
+ if (isSvgHandling) {
105
+ // Skip if it's a safe asset source (local bundled resources)
106
+ bool isSafeAsset = _isSafeAssetSource(source);
107
+ if (isSafeAsset) {
108
+ super.visitMethodInvocation(node);
109
+ return;
110
+ }
111
+
112
+ // Check if SVG content comes from user/network
113
+ bool isUserInput = _isFromUserInput(node);
114
+
115
+ // Check if sanitization is applied
116
+ bool hasSanitization =
117
+ S008SvgContentSanitizationAnalyzer._sanitizationPatterns
118
+ .any((p) => source.contains(p));
119
+
120
+ if (isUserInput && !hasSanitization) {
121
+ violations.add(analyzer.createViolation(
122
+ filePath: filePath,
123
+ line: analyzer.getLine(lineInfo, node.offset),
124
+ column: analyzer.getColumn(lineInfo, node.offset),
125
+ message:
126
+ 'SVG content from untrusted source should be sanitized to prevent script injection',
127
+ ));
128
+ }
129
+ }
130
+
131
+ // Check for SVG string containing dangerous elements
132
+ for (final arg in node.argumentList.arguments) {
133
+ if (arg is StringLiteral) {
134
+ final content = arg.toSource().toLowerCase();
135
+ if (content.contains('svg')) {
136
+ bool hasDangerousElement =
137
+ S008SvgContentSanitizationAnalyzer._dangerousSvgElements
138
+ .any((e) => content.contains(e));
139
+
140
+ if (hasDangerousElement) {
141
+ violations.add(analyzer.createViolation(
142
+ filePath: filePath,
143
+ line: analyzer.getLine(lineInfo, arg.offset),
144
+ column: analyzer.getColumn(lineInfo, arg.offset),
145
+ message:
146
+ 'SVG contains potentially dangerous elements (script, onclick, foreignObject) - sanitize before use',
147
+ ));
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ super.visitMethodInvocation(node);
154
+ }
155
+
156
+ @override
157
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
158
+ final typeName = node.constructorName.type.name2.lexeme.toLowerCase();
159
+ final source = node.toSource().toLowerCase();
160
+
161
+ // Check for SvgPicture constructors
162
+ if (typeName.contains('svg')) {
163
+ // Skip safe asset sources (SvgPicture.asset, local paths)
164
+ bool isSafeAsset = _isSafeAssetSource(source);
165
+ if (isSafeAsset) {
166
+ super.visitInstanceCreationExpression(node);
167
+ return;
168
+ }
169
+
170
+ bool hasSanitization =
171
+ S008SvgContentSanitizationAnalyzer._sanitizationPatterns
172
+ .any((p) => source.contains(p));
173
+
174
+ // Only flag network/string constructors (untrusted sources)
175
+ if ((source.contains('.network') || source.contains('.string')) &&
176
+ !hasSanitization) {
177
+ violations.add(analyzer.createViolation(
178
+ filePath: filePath,
179
+ line: analyzer.getLine(lineInfo, node.offset),
180
+ column: analyzer.getColumn(lineInfo, node.offset),
181
+ message:
182
+ 'SVG from network/string should be sanitized before rendering',
183
+ ));
184
+ }
185
+ }
186
+
187
+ super.visitInstanceCreationExpression(node);
188
+ }
189
+
190
+ /// Check if source is from safe local assets
191
+ bool _isSafeAssetSource(String source) {
192
+ return S008SvgContentSanitizationAnalyzer._safeAssetPatterns
193
+ .any((p) => source.contains(p));
194
+ }
195
+
196
+ bool _isFromUserInput(MethodInvocation node) {
197
+ final source = node.toSource().toLowerCase();
198
+
199
+ // First check if it's a safe asset - if so, not user input
200
+ if (_isSafeAssetSource(source)) {
201
+ return false;
202
+ }
203
+
204
+ return source.contains('request') ||
205
+ source.contains('upload') ||
206
+ source.contains('network') ||
207
+ source.contains('http') ||
208
+ source.contains('url') ||
209
+ source.contains('input');
210
+ }
211
+ }
@@ -0,0 +1,160 @@
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
+ /// S009: No Insecure Encryption Modes, Padding, or Cryptographic Algorithms
10
+ /// Do not use insecure encryption modes, padding, or cryptographic algorithms
11
+ class S009NoInsecureEncryptionAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S009';
14
+
15
+ // Insecure algorithms and modes - use word boundaries to avoid false positives
16
+ static final _insecurePatterns = {
17
+ RegExp(r'\bdes\b', caseSensitive: false): 'DES is insecure - use AES-256',
18
+ RegExp(r'\b3des\b', caseSensitive: false): '3DES is deprecated - use AES-256',
19
+ RegExp(r'\btripledes\b', caseSensitive: false): 'Triple DES is deprecated - use AES-256',
20
+ RegExp(r'\brc4\b', caseSensitive: false): 'RC4 is broken - use AES-256-GCM',
21
+ RegExp(r'\brc2\b', caseSensitive: false): 'RC2 is insecure - use AES-256',
22
+ RegExp(r'\bblowfish\b', caseSensitive: false): 'Blowfish is outdated - use AES-256',
23
+ RegExp(r'\becb\b', caseSensitive: false): 'ECB mode is insecure - use GCM or CBC with IV',
24
+ RegExp(r'\bmd5\b', caseSensitive: false): 'MD5 is broken for security purposes - use SHA-256+',
25
+ RegExp(r'\bsha1\b', caseSensitive: false): 'SHA1 is weak - use SHA-256 or stronger',
26
+ RegExp(r'\bpkcs1\b', caseSensitive: false): 'PKCS#1 v1.5 is vulnerable - use OAEP padding',
27
+ };
28
+
29
+ @override
30
+ List<Violation> analyze({
31
+ required CompilationUnit unit,
32
+ required String filePath,
33
+ required Rule rule,
34
+ required LineInfo lineInfo,
35
+ }) {
36
+ final violations = <Violation>[];
37
+ final visitor = _S009Visitor(
38
+ filePath: filePath,
39
+ lineInfo: lineInfo,
40
+ violations: violations,
41
+ analyzer: this,
42
+ );
43
+ unit.accept(visitor);
44
+ return violations;
45
+ }
46
+ }
47
+
48
+ class _S009Visitor extends RecursiveAstVisitor<void> {
49
+ final String filePath;
50
+ final LineInfo lineInfo;
51
+ final List<Violation> violations;
52
+ final S009NoInsecureEncryptionAnalyzer analyzer;
53
+
54
+ _S009Visitor({
55
+ required this.filePath,
56
+ required this.lineInfo,
57
+ required this.violations,
58
+ required this.analyzer,
59
+ });
60
+
61
+ @override
62
+ void visitMethodInvocation(MethodInvocation node) {
63
+ final source = node.toSource().toLowerCase();
64
+
65
+ _checkInsecurePatterns(source, node.offset);
66
+ super.visitMethodInvocation(node);
67
+ }
68
+
69
+ @override
70
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
71
+ final source = node.toSource().toLowerCase();
72
+
73
+ _checkInsecurePatterns(source, node.offset);
74
+ super.visitInstanceCreationExpression(node);
75
+ }
76
+
77
+ @override
78
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
79
+ // Skip import/export directives - they are not crypto code
80
+ AstNode? parent = node.parent;
81
+ while (parent != null) {
82
+ if (parent is ImportDirective || parent is ExportDirective || parent is PartDirective) {
83
+ super.visitSimpleStringLiteral(node);
84
+ return;
85
+ }
86
+ parent = parent.parent;
87
+ }
88
+
89
+ final value = node.value.toLowerCase();
90
+
91
+ _checkInsecurePatterns(value, node.offset);
92
+ super.visitSimpleStringLiteral(node);
93
+ }
94
+
95
+ void _checkInsecurePatterns(String source, int offset) {
96
+ for (final entry in S009NoInsecureEncryptionAnalyzer._insecurePatterns.entries) {
97
+ if (entry.key.hasMatch(source)) {
98
+ // Avoid false positives for variable names containing the pattern
99
+ if (_isInCryptoContext(source)) {
100
+ violations.add(analyzer.createViolation(
101
+ filePath: filePath,
102
+ line: analyzer.getLine(lineInfo, offset),
103
+ column: analyzer.getColumn(lineInfo, offset),
104
+ message: entry.value,
105
+ ));
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ bool _isInCryptoContext(String source) {
112
+ // First check for false positive patterns (UI-related, non-crypto)
113
+ final falsePositivePatterns = [
114
+ 'designsize', 'design_size', 'screensize', 'screen_size',
115
+ 'fontsize', 'font_size', 'textsize', 'text_size',
116
+ 'imagesize', 'image_size', 'iconsize', 'icon_size',
117
+ 'mediaquery', 'media_query', 'screenutil', 'screen_util',
118
+ 'description', 'descriptor', 'destiny', 'desktop', 'destroy',
119
+ 'deserialize', 'deserialization',
120
+ ];
121
+ if (falsePositivePatterns.any((p) => source.contains(p))) {
122
+ return false;
123
+ }
124
+
125
+ // Non-security use cases for MD5/SHA1 (acceptable uses)
126
+ // MD5 is fine for: identifiers, cache keys, checksums (non-security)
127
+ // Only flag when used for security purposes (passwords, tokens, signatures)
128
+ final nonSecurityContexts = [
129
+ // Identifier/checksum generation (not security-sensitive)
130
+ 'cid', 'uid', 'id', 'identifier', 'checksum',
131
+ 'cachekey', 'cache_key', 'etag', 'fingerprint',
132
+ // Deep link / URL parameters
133
+ 'deeplink', 'deep_link', 'urlparam', 'url_param',
134
+ // File operations (integrity, not security)
135
+ 'filename', 'file_name', 'filepath', 'file_path',
136
+ ];
137
+ if (nonSecurityContexts.any((p) => source.contains(p))) {
138
+ return false;
139
+ }
140
+
141
+ // Security-critical contexts (must flag)
142
+ final securityCriticalContexts = [
143
+ 'password', 'passwd', 'pwd', 'secret', 'token',
144
+ 'auth', 'credential', 'session', 'signature',
145
+ 'privatekey', 'private_key', 'apikey', 'api_key',
146
+ ];
147
+ if (securityCriticalContexts.any((p) => source.contains(p))) {
148
+ return true;
149
+ }
150
+
151
+ // General crypto context - only flag if clearly doing crypto operations
152
+ final cryptoKeywords = [
153
+ 'cipher', 'encrypt', 'decrypt',
154
+ 'hmac', 'sign', 'verify',
155
+ 'aes', 'rsa', 'createcipher', 'createhash',
156
+ 'pbkdf', 'bcrypt', 'scrypt', 'argon',
157
+ ];
158
+ return cryptoKeywords.any((k) => source.contains(k));
159
+ }
160
+ }
@@ -0,0 +1,184 @@
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
+ /// S010: Must use cryptographically secure random number generators (CSPRNG)
10
+ /// Detect usage of insecure random number generators for security purposes
11
+ class S010UseCsprngAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S010';
14
+
15
+ // Insecure random patterns
16
+ static const _insecureRandomPatterns = [
17
+ 'Random()', // Dart's default Random is not cryptographically secure
18
+ 'Random.nextInt',
19
+ 'Random.nextDouble',
20
+ 'Random.nextBool',
21
+ 'DateTime.now().millisecondsSinceEpoch',
22
+ ];
23
+
24
+ // Security-sensitive contexts - ONLY actual security-related IDs
25
+ // Note: 'id' alone is too generic - causes false positives for alarm IDs, notification IDs, etc.
26
+ static const _securityContexts = [
27
+ 'token', 'session', 'auth', 'key', 'secret', 'password',
28
+ 'nonce', 'salt', 'iv', 'otp', 'code', 'uuid',
29
+ 'apikey', 'api_key', 'accesstoken', 'access_token',
30
+ 'sessionid', 'session_id', 'userid', 'user_id', // Only compound IDs
31
+ 'securityid', 'security_id', 'authid', 'auth_id',
32
+ ];
33
+
34
+ // Non-security contexts - these use IDs but are NOT security sensitive
35
+ static const _nonSecurityContexts = [
36
+ 'alarm', 'notification', 'widget', 'component', 'element',
37
+ 'timer', 'animation', 'screen', 'page', 'view', 'controller',
38
+ 'index', 'counter', 'position', 'offset', 'size', 'duration',
39
+ ];
40
+
41
+ @override
42
+ List<Violation> analyze({
43
+ required CompilationUnit unit,
44
+ required String filePath,
45
+ required Rule rule,
46
+ required LineInfo lineInfo,
47
+ }) {
48
+ final violations = <Violation>[];
49
+ final visitor = _S010Visitor(
50
+ filePath: filePath,
51
+ lineInfo: lineInfo,
52
+ violations: violations,
53
+ analyzer: this,
54
+ );
55
+ unit.accept(visitor);
56
+ return violations;
57
+ }
58
+ }
59
+
60
+ class _S010Visitor extends RecursiveAstVisitor<void> {
61
+ final String filePath;
62
+ final LineInfo lineInfo;
63
+ final List<Violation> violations;
64
+ final S010UseCsprngAnalyzer analyzer;
65
+
66
+ _S010Visitor({
67
+ required this.filePath,
68
+ required this.lineInfo,
69
+ required this.violations,
70
+ required this.analyzer,
71
+ });
72
+
73
+ @override
74
+ void visitVariableDeclaration(VariableDeclaration node) {
75
+ final varName = node.name.lexeme.toLowerCase();
76
+ final initializer = node.initializer;
77
+
78
+ if (initializer != null) {
79
+ final initSource = initializer.toSource().toLowerCase();
80
+
81
+ // Check if this is a non-security context (alarm, notification, etc.)
82
+ // These use IDs but are NOT security sensitive
83
+ bool isNonSecurityContext = S010UseCsprngAnalyzer._nonSecurityContexts
84
+ .any((ctx) => varName.contains(ctx));
85
+
86
+ // Also check parent/enclosing context for non-security patterns
87
+ AstNode? current = node.parent;
88
+ int depth = 0;
89
+ while (current != null && depth < 5 && !isNonSecurityContext) {
90
+ final parentSource = current.toSource().toLowerCase();
91
+ isNonSecurityContext = S010UseCsprngAnalyzer._nonSecurityContexts
92
+ .any((ctx) => parentSource.contains(ctx));
93
+ current = current.parent;
94
+ depth++;
95
+ }
96
+
97
+ if (isNonSecurityContext) {
98
+ super.visitVariableDeclaration(node);
99
+ return;
100
+ }
101
+
102
+ // Check if variable name suggests security context
103
+ bool isSecurityContext = S010UseCsprngAnalyzer._securityContexts
104
+ .any((ctx) => varName.contains(ctx));
105
+
106
+ // Check if using insecure random
107
+ bool usesInsecureRandom = initSource.contains('random(') ||
108
+ initSource.contains('random.next') ||
109
+ initSource.contains('datetime.now');
110
+
111
+ // Check if using secure random
112
+ bool usesSecureRandom = initSource.contains('random.secure') ||
113
+ initSource.contains('securerandom');
114
+
115
+ if (isSecurityContext && usesInsecureRandom && !usesSecureRandom) {
116
+ violations.add(analyzer.createViolation(
117
+ filePath: filePath,
118
+ line: analyzer.getLine(lineInfo, node.offset),
119
+ column: analyzer.getColumn(lineInfo, node.offset),
120
+ message: 'Use Random.secure() instead of Random() for security-sensitive data',
121
+ ));
122
+ }
123
+ }
124
+
125
+ super.visitVariableDeclaration(node);
126
+ }
127
+
128
+ @override
129
+ void visitMethodInvocation(MethodInvocation node) {
130
+ final source = node.toSource().toLowerCase();
131
+
132
+ // Check for Random() usage in security contexts
133
+ if (source.contains('random(') && !source.contains('random.secure')) {
134
+ // Check surrounding context
135
+ final parent = node.parent;
136
+ if (parent != null) {
137
+ final parentSource = parent.toSource().toLowerCase();
138
+ bool isSecurityContext = S010UseCsprngAnalyzer._securityContexts
139
+ .any((ctx) => parentSource.contains(ctx));
140
+
141
+ if (isSecurityContext) {
142
+ violations.add(analyzer.createViolation(
143
+ filePath: filePath,
144
+ line: analyzer.getLine(lineInfo, node.offset),
145
+ column: analyzer.getColumn(lineInfo, node.offset),
146
+ message: 'Use Random.secure() for cryptographically secure random numbers',
147
+ ));
148
+ }
149
+ }
150
+ }
151
+
152
+ super.visitMethodInvocation(node);
153
+ }
154
+
155
+ @override
156
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
157
+ final typeName = node.constructorName.type.name2.lexeme;
158
+
159
+ if (typeName == 'Random') {
160
+ // Check if it's Random.secure()
161
+ final constructorName = node.constructorName.name?.name;
162
+ if (constructorName != 'secure') {
163
+ // Check context
164
+ final parent = node.parent;
165
+ if (parent != null) {
166
+ final parentSource = parent.toSource().toLowerCase();
167
+ bool isSecurityContext = S010UseCsprngAnalyzer._securityContexts
168
+ .any((ctx) => parentSource.contains(ctx));
169
+
170
+ if (isSecurityContext) {
171
+ violations.add(analyzer.createViolation(
172
+ filePath: filePath,
173
+ line: analyzer.getLine(lineInfo, node.offset),
174
+ column: analyzer.getColumn(lineInfo, node.offset),
175
+ message: 'Use Random.secure() instead of Random() for security purposes',
176
+ ));
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ super.visitInstanceCreationExpression(node);
183
+ }
184
+ }