@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,118 @@
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
+ /// S031: Secure Session Cookies
10
+ /// Ensure session cookies have Secure flag enabled
11
+ class S031SecureSessionCookiesAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S031';
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 = _S031Visitor(
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 _S031Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S031SecureSessionCookiesAnalyzer analyzer;
39
+
40
+ _S031Visitor({
41
+ required this.filePath,
42
+ required this.lineInfo,
43
+ required this.violations,
44
+ required this.analyzer,
45
+ });
46
+
47
+ @override
48
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
49
+ final typeName = node.constructorName.type.name2.lexeme.toLowerCase();
50
+
51
+ // Check for Cookie creation
52
+ if (typeName == 'cookie') {
53
+ final source = node.toSource().toLowerCase();
54
+
55
+ // Check for session-related cookies
56
+ bool isSessionCookie = source.contains('session') ||
57
+ source.contains('token') ||
58
+ source.contains('auth');
59
+
60
+ if (isSessionCookie) {
61
+ // Check for Secure flag
62
+ bool hasSecureFlag = source.contains('secure: true') ||
63
+ source.contains('secure:true');
64
+
65
+ if (!hasSecureFlag) {
66
+ violations.add(analyzer.createViolation(
67
+ filePath: filePath,
68
+ line: analyzer.getLine(lineInfo, node.offset),
69
+ column: analyzer.getColumn(lineInfo, node.offset),
70
+ message: 'Session cookie should have Secure flag enabled (secure: true)',
71
+ ));
72
+ }
73
+ }
74
+ }
75
+
76
+ super.visitInstanceCreationExpression(node);
77
+ }
78
+
79
+ @override
80
+ void visitMethodInvocation(MethodInvocation node) {
81
+ final methodName = node.methodName.name.toLowerCase();
82
+ final source = node.toSource().toLowerCase();
83
+
84
+ // Check for cookie setting methods - must be cookie-related context
85
+ bool isCookieContext = source.contains('cookie') ||
86
+ source.contains('setcookie') ||
87
+ source.contains('set-cookie') ||
88
+ (node.target?.toSource().toLowerCase().contains('cookie') ?? false);
89
+
90
+ // Skip if not cookie context (e.g., interceptors.add(), list.add(), etc.)
91
+ if (!isCookieContext) {
92
+ super.visitMethodInvocation(node);
93
+ return;
94
+ }
95
+
96
+ if (methodName == 'setcookie' || methodName == 'set' || methodName == 'add') {
97
+ // Check if it's a session cookie
98
+ bool isSessionCookie = source.contains('session') ||
99
+ source.contains('token') ||
100
+ source.contains('auth');
101
+
102
+ if (isSessionCookie) {
103
+ bool hasSecureFlag = source.contains('secure');
104
+
105
+ if (!hasSecureFlag) {
106
+ violations.add(analyzer.createViolation(
107
+ filePath: filePath,
108
+ line: analyzer.getLine(lineInfo, node.offset),
109
+ column: analyzer.getColumn(lineInfo, node.offset),
110
+ message: 'Session cookie without Secure flag',
111
+ ));
112
+ }
113
+ }
114
+ }
115
+
116
+ super.visitMethodInvocation(node);
117
+ }
118
+ }
@@ -0,0 +1,114 @@
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
+ /// S032: HttpOnly Session Cookies
10
+ /// Ensure session cookies have HttpOnly flag enabled
11
+ class S032HttpOnlySessionCookiesAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S032';
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 = _S032Visitor(
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 _S032Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S032HttpOnlySessionCookiesAnalyzer analyzer;
39
+
40
+ _S032Visitor({
41
+ required this.filePath,
42
+ required this.lineInfo,
43
+ required this.violations,
44
+ required this.analyzer,
45
+ });
46
+
47
+ @override
48
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
49
+ final typeName = node.constructorName.type.name2.lexeme.toLowerCase();
50
+
51
+ if (typeName == 'cookie') {
52
+ final source = node.toSource().toLowerCase();
53
+
54
+ bool isSessionCookie = source.contains('session') ||
55
+ source.contains('token') ||
56
+ source.contains('auth');
57
+
58
+ if (isSessionCookie) {
59
+ bool hasHttpOnly = source.contains('httponly: true') ||
60
+ source.contains('httponly:true');
61
+
62
+ if (!hasHttpOnly) {
63
+ violations.add(analyzer.createViolation(
64
+ filePath: filePath,
65
+ line: analyzer.getLine(lineInfo, node.offset),
66
+ column: analyzer.getColumn(lineInfo, node.offset),
67
+ message: 'Session cookie should have HttpOnly flag (httpOnly: true) to prevent XSS attacks',
68
+ ));
69
+ }
70
+ }
71
+ }
72
+
73
+ super.visitInstanceCreationExpression(node);
74
+ }
75
+
76
+ @override
77
+ void visitMethodInvocation(MethodInvocation node) {
78
+ final methodName = node.methodName.name.toLowerCase();
79
+ final source = node.toSource().toLowerCase();
80
+
81
+ // Check for cookie setting methods - must be cookie-related context
82
+ bool isCookieContext = source.contains('cookie') ||
83
+ source.contains('setcookie') ||
84
+ source.contains('set-cookie') ||
85
+ (node.target?.toSource().toLowerCase().contains('cookie') ?? false);
86
+
87
+ // Skip if not cookie context
88
+ if (!isCookieContext) {
89
+ super.visitMethodInvocation(node);
90
+ return;
91
+ }
92
+
93
+ if (methodName == 'setcookie' || methodName == 'set' || methodName == 'add') {
94
+ bool isSessionCookie = source.contains('session') ||
95
+ source.contains('token') ||
96
+ source.contains('auth');
97
+
98
+ if (isSessionCookie) {
99
+ bool hasHttpOnly = source.contains('httponly');
100
+
101
+ if (!hasHttpOnly) {
102
+ violations.add(analyzer.createViolation(
103
+ filePath: filePath,
104
+ line: analyzer.getLine(lineInfo, node.offset),
105
+ column: analyzer.getColumn(lineInfo, node.offset),
106
+ message: 'Session cookie without HttpOnly flag - vulnerable to XSS',
107
+ ));
108
+ }
109
+ }
110
+ }
111
+
112
+ super.visitMethodInvocation(node);
113
+ }
114
+ }
@@ -0,0 +1,120 @@
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
+ /// S033: SameSite Session Cookies
10
+ /// Ensure session cookies have SameSite attribute set
11
+ class S033SameSiteSessionCookiesAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S033';
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 = _S033Visitor(
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 _S033Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S033SameSiteSessionCookiesAnalyzer analyzer;
39
+
40
+ _S033Visitor({
41
+ required this.filePath,
42
+ required this.lineInfo,
43
+ required this.violations,
44
+ required this.analyzer,
45
+ });
46
+
47
+ @override
48
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
49
+ final typeName = node.constructorName.type.name2.lexeme.toLowerCase();
50
+
51
+ if (typeName == 'cookie') {
52
+ final source = node.toSource().toLowerCase();
53
+
54
+ bool isSessionCookie = source.contains('session') ||
55
+ source.contains('token') ||
56
+ source.contains('auth');
57
+
58
+ if (isSessionCookie) {
59
+ bool hasSameSite = source.contains('samesite');
60
+
61
+ if (!hasSameSite) {
62
+ violations.add(analyzer.createViolation(
63
+ filePath: filePath,
64
+ line: analyzer.getLine(lineInfo, node.offset),
65
+ column: analyzer.getColumn(lineInfo, node.offset),
66
+ message: 'Session cookie should have SameSite attribute (Strict or Lax) to prevent CSRF',
67
+ ));
68
+ } else if (source.contains('samesite: none') || source.contains("samesite:'none'")) {
69
+ violations.add(analyzer.createViolation(
70
+ filePath: filePath,
71
+ line: analyzer.getLine(lineInfo, node.offset),
72
+ column: analyzer.getColumn(lineInfo, node.offset),
73
+ message: 'SameSite=None makes cookies vulnerable to CSRF - use Strict or Lax',
74
+ ));
75
+ }
76
+ }
77
+ }
78
+
79
+ super.visitInstanceCreationExpression(node);
80
+ }
81
+
82
+ @override
83
+ void visitMethodInvocation(MethodInvocation node) {
84
+ final methodName = node.methodName.name.toLowerCase();
85
+ final source = node.toSource().toLowerCase();
86
+
87
+ // Check for cookie setting methods - must be cookie-related context
88
+ bool isCookieContext = source.contains('cookie') ||
89
+ source.contains('setcookie') ||
90
+ source.contains('set-cookie') ||
91
+ (node.target?.toSource().toLowerCase().contains('cookie') ?? false);
92
+
93
+ // Skip if not cookie context
94
+ if (!isCookieContext) {
95
+ super.visitMethodInvocation(node);
96
+ return;
97
+ }
98
+
99
+ if (methodName == 'setcookie' || methodName == 'set' || methodName == 'add') {
100
+ bool isSessionCookie = source.contains('session') ||
101
+ source.contains('token') ||
102
+ source.contains('auth');
103
+
104
+ if (isSessionCookie) {
105
+ bool hasSameSite = source.contains('samesite');
106
+
107
+ if (!hasSameSite) {
108
+ violations.add(analyzer.createViolation(
109
+ filePath: filePath,
110
+ line: analyzer.getLine(lineInfo, node.offset),
111
+ column: analyzer.getColumn(lineInfo, node.offset),
112
+ message: 'Session cookie without SameSite attribute',
113
+ ));
114
+ }
115
+ }
116
+ }
117
+
118
+ super.visitMethodInvocation(node);
119
+ }
120
+ }
@@ -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
+ /// S034: __Host- Prefix Session Cookies
10
+ /// Use __Host- prefix for secure session cookies
11
+ class S034HostPrefixSessionCookiesAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S034';
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 = _S034Visitor(
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 _S034Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S034HostPrefixSessionCookiesAnalyzer analyzer;
39
+
40
+ _S034Visitor({
41
+ required this.filePath,
42
+ required this.lineInfo,
43
+ required this.violations,
44
+ required this.analyzer,
45
+ });
46
+
47
+ @override
48
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
49
+ final typeName = node.constructorName.type.name2.lexeme.toLowerCase();
50
+
51
+ if (typeName == 'cookie') {
52
+ final source = node.toSource().toLowerCase();
53
+
54
+ // Check for session-related cookies - more flexible matching
55
+ // Match patterns like: session_id, sessionid, session_token, sessionToken, auth_token, etc.
56
+ bool isSessionCookie = source.contains('session_id') ||
57
+ source.contains('sessionid') ||
58
+ source.contains('session_token') ||
59
+ source.contains('sessiontoken') ||
60
+ source.contains('auth_token') ||
61
+ source.contains('authtoken') ||
62
+ (source.contains('session') && source.contains('token'));
63
+
64
+ if (isSessionCookie) {
65
+ // Check for __Host- or __Secure- prefix
66
+ bool hasSecurePrefix = source.contains('__host-') || source.contains('__secure-');
67
+
68
+ if (!hasSecurePrefix) {
69
+ violations.add(analyzer.createViolation(
70
+ filePath: filePath,
71
+ line: analyzer.getLine(lineInfo, node.offset),
72
+ column: analyzer.getColumn(lineInfo, node.offset),
73
+ message: 'Consider using __Host- prefix for session cookies for enhanced security',
74
+ ));
75
+ }
76
+ }
77
+ }
78
+
79
+ super.visitInstanceCreationExpression(node);
80
+ }
81
+
82
+ @override
83
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
84
+ final value = node.value.toLowerCase();
85
+
86
+ // Check for cookie name that should have prefix
87
+ if ((value.contains('session') || value.contains('auth')) &&
88
+ (value.contains('id') || value.contains('token'))) {
89
+ if (!value.startsWith('__host-') && !value.startsWith('__secure-')) {
90
+ // Check if this is a cookie name (heuristic)
91
+ final parent = node.parent;
92
+ if (parent != null) {
93
+ final parentSource = parent.toSource().toLowerCase();
94
+
95
+ // Skip non-cookie contexts (JSON keys, API params, annotations, etc.)
96
+ bool isNonCookieContext =
97
+ parentSource.contains('jsonkey') ||
98
+ parentSource.contains('json_key') ||
99
+ parentSource.contains('@jsonkey') ||
100
+ parentSource.contains('request') ||
101
+ parentSource.contains('postinstall') ||
102
+ parentSource.contains('apiresponse') ||
103
+ parentSource.contains('api_response') ||
104
+ parentSource.contains('factory');
105
+
106
+ if (isNonCookieContext) {
107
+ super.visitSimpleStringLiteral(node);
108
+ return;
109
+ }
110
+
111
+ // Only report if it's actually cookie-related
112
+ if (parentSource.contains('cookie') ||
113
+ (parentSource.contains('set-cookie') || parentSource.contains('setcookie'))) {
114
+ violations.add(analyzer.createViolation(
115
+ filePath: filePath,
116
+ line: analyzer.getLine(lineInfo, node.offset),
117
+ column: analyzer.getColumn(lineInfo, node.offset),
118
+ message: 'Session cookie should use __Host- prefix for security',
119
+ ));
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ super.visitSimpleStringLiteral(node);
126
+ }
127
+
128
+ @override
129
+ void visitMethodInvocation(MethodInvocation node) {
130
+ final methodName = node.methodName.name.toLowerCase();
131
+
132
+ // Check for cookie add/set methods
133
+ if (methodName == 'setcookie' || methodName == 'set' || methodName == 'add') {
134
+ final source = node.toSource().toLowerCase();
135
+
136
+ // Check for session-related cookies
137
+ bool isSessionCookie = source.contains('session_id') ||
138
+ source.contains('sessionid') ||
139
+ source.contains('session_token') ||
140
+ source.contains('sessiontoken') ||
141
+ source.contains('auth_token') ||
142
+ source.contains('authtoken');
143
+
144
+ if (isSessionCookie) {
145
+ bool hasSecurePrefix = source.contains('__host-') || source.contains('__secure-');
146
+
147
+ if (!hasSecurePrefix) {
148
+ violations.add(analyzer.createViolation(
149
+ filePath: filePath,
150
+ line: analyzer.getLine(lineInfo, node.offset),
151
+ column: analyzer.getColumn(lineInfo, node.offset),
152
+ message: 'Session cookie should use __Host- prefix',
153
+ ));
154
+ }
155
+ }
156
+ }
157
+
158
+ super.visitMethodInvocation(node);
159
+ }
160
+ }
@@ -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
+ /// S035: Host separate applications on different hostnames
10
+ /// Detect patterns suggesting multiple apps sharing same origin (path-based routing)
11
+ class S035SeparateAppHostnamesAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'S035';
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 = _S035Visitor(
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 _S035Visitor extends RecursiveAstVisitor<void> {
35
+ final String filePath;
36
+ final LineInfo lineInfo;
37
+ final List<Violation> violations;
38
+ final S035SeparateAppHostnamesAnalyzer analyzer;
39
+
40
+ // Patterns indicating path-based multi-app on same origin
41
+ static final _pathBasedAppPatterns = [
42
+ RegExp(r'/app[12]/'),
43
+ RegExp(r'/admin/'),
44
+ RegExp(r'/portal/'),
45
+ RegExp(r'/dashboard/'),
46
+ ];
47
+
48
+ _S035Visitor({
49
+ required this.filePath,
50
+ required this.lineInfo,
51
+ required this.violations,
52
+ required this.analyzer,
53
+ });
54
+
55
+ @override
56
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
57
+ final value = node.value;
58
+
59
+ // Check for patterns suggesting path-based multi-app routing
60
+ // e.g., "https://example.com/app1/api" or "https://example.com/admin/"
61
+ if (_isPathBasedMultiAppUrl(value)) {
62
+ violations.add(analyzer.createViolation(
63
+ filePath: filePath,
64
+ line: analyzer.getLine(lineInfo, node.offset),
65
+ column: analyzer.getColumn(lineInfo, node.offset),
66
+ message: 'Path-based app routing detected. Consider using separate hostnames for security isolation: "$value"',
67
+ ));
68
+ }
69
+
70
+ super.visitSimpleStringLiteral(node);
71
+ }
72
+
73
+ @override
74
+ void visitMethodInvocation(MethodInvocation node) {
75
+ final methodName = node.methodName.name.toLowerCase();
76
+ final source = node.toSource().toLowerCase();
77
+
78
+ // Check for route registration with path-based app separation
79
+ if (methodName == 'route' || methodName == 'addroute' || methodName == 'use') {
80
+ for (final pattern in _pathBasedAppPatterns) {
81
+ if (pattern.hasMatch(source)) {
82
+ violations.add(analyzer.createViolation(
83
+ filePath: filePath,
84
+ line: analyzer.getLine(lineInfo, node.offset),
85
+ column: analyzer.getColumn(lineInfo, node.offset),
86
+ message: 'Path-based application routing. Consider separate hostnames for same-origin policy benefits',
87
+ ));
88
+ break;
89
+ }
90
+ }
91
+ }
92
+
93
+ super.visitMethodInvocation(node);
94
+ }
95
+
96
+ bool _isPathBasedMultiAppUrl(String value) {
97
+ // Skip if not a URL
98
+ if (!value.startsWith('http://') && !value.startsWith('https://')) {
99
+ return false;
100
+ }
101
+
102
+ // Skip localhost/development URLs
103
+ if (value.contains('localhost') || value.contains('127.0.0.1')) {
104
+ return false;
105
+ }
106
+
107
+ // Check for path-based app patterns after the domain
108
+ // e.g., https://example.com/app1/... or https://example.com/admin/...
109
+ for (final pattern in _pathBasedAppPatterns) {
110
+ if (pattern.hasMatch(value)) {
111
+ return true;
112
+ }
113
+ }
114
+
115
+ return false;
116
+ }
117
+ }