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