@sun-asterisk/sunlint 1.3.36 → 1.3.37

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 (103) hide show
  1. package/cli.js +33 -0
  2. package/config/rules/enhanced-rules-registry.json +354 -98
  3. package/config/rules/rules-registry-generated.json +197 -171
  4. package/core/architecture-integration.js +115 -17
  5. package/core/cli-action-handler.js +101 -27
  6. package/core/cli-program.js +5 -0
  7. package/core/github-annotate-service.js +62 -0
  8. package/core/impact-integration.js +31 -16
  9. package/core/init-command.js +227 -0
  10. package/core/output-service.js +53 -5
  11. package/core/summary-report-service.js +46 -0
  12. package/core/unified-rule-registry.js +2 -1
  13. package/engines/eslint-engine.js +6 -0
  14. package/engines/impact/core/detectors/database-detector.js +1 -1
  15. package/engines/impact/core/detectors/endpoint-detector.js +1 -1
  16. package/engines/impact/core/report-generator.js +235 -73
  17. package/origin-rules/security-en.md +470 -282
  18. package/package.json +1 -1
  19. package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
  20. package/rules/security/S001_backend_auth_communications/index.js +87 -0
  21. package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
  22. package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
  23. package/rules/security/S002_os_command_injection/index.js +87 -0
  24. package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
  25. package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
  26. package/rules/security/S008_svg_content_validation/index.js +87 -0
  27. package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
  28. package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
  29. package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
  30. package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
  31. package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
  32. package/rules/security/S021_referrer_policy/index.js +86 -0
  33. package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
  34. package/rules/security/S023_no_json_injection/config.json +133 -44
  35. package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
  36. package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
  37. package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
  38. package/rules/security/S026_tls_all_connections/config.json +30 -0
  39. package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
  40. package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
  41. package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
  42. package/rules/security/S035_separate_app_hostnames/config.json +28 -0
  43. package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
  44. package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
  45. package/rules/security/S039_tls_certificate_validation/config.json +29 -0
  46. package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
  47. package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
  48. package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
  49. package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
  50. package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
  51. package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
  52. package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
  53. package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
  54. package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
  55. package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
  56. package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
  57. package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
  58. package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
  59. package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
  60. package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
  61. package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
  62. package/rules/security/S053_generic_error_messages/config.json +28 -0
  63. package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
  64. package/rules/security/S053_generic_error_messages/index.js +86 -0
  65. package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
  66. package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
  67. package/rules/security/S059_disable_debug_mode/config.json +28 -0
  68. package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
  69. package/rules/security/S059_disable_debug_mode/index.js +86 -0
  70. package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
  71. package/rules/security/S060_password_minimum_length/config.json +28 -0
  72. package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
  73. package/rules/security/S060_password_minimum_length/index.js +86 -0
  74. package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
  75. package/rules/security/S026_json_schema_validation/config.json +0 -27
  76. package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
  77. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  78. package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
  79. package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
  80. package/rules/security/S035_path_session_cookies/config.json +0 -99
  81. package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
  82. package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
  83. package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
  84. package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
  85. package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
  86. package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
  87. package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
  88. package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
  89. package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
  90. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
  91. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
  92. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
  93. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
  94. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
  95. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
  96. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
  97. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
  98. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
  99. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
  100. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
  101. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
  102. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
  103. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/typescript/README.md +0 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * S008 Rule Router - SVG Content Validation
3
+ *
4
+ * Routes analysis to the appropriate language-specific analyzer.
5
+ * Supports: TypeScript, JavaScript, Dart
6
+ *
7
+ * Rule: Ensure user-supplied SVG content is validated or sanitized
8
+ * to prevent script injection and other attacks.
9
+ */
10
+
11
+ const path = require('path');
12
+
13
+ class S008Router {
14
+ constructor() {
15
+ this.analyzers = new Map();
16
+ this.ruleId = 'S008';
17
+ }
18
+
19
+ getAnalyzer(language) {
20
+ const normalizedLang = this.normalizeLanguage(language);
21
+
22
+ if (!this.analyzers.has(normalizedLang)) {
23
+ try {
24
+ const analyzerPath = path.join(__dirname, normalizedLang, 'analyzer.js');
25
+ const AnalyzerClass = require(analyzerPath);
26
+ this.analyzers.set(normalizedLang, new AnalyzerClass());
27
+ } catch (error) {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ return this.analyzers.get(normalizedLang);
33
+ }
34
+
35
+ normalizeLanguage(language) {
36
+ if (typeof language !== 'string') {
37
+ return 'typescript';
38
+ }
39
+ const languageMap = {
40
+ 'typescript': 'typescript',
41
+ 'javascript': 'typescript',
42
+ 'ts': 'typescript',
43
+ 'js': 'typescript',
44
+ 'dart': 'dart'
45
+ };
46
+ return languageMap[language.toLowerCase()] || language.toLowerCase();
47
+ }
48
+
49
+ supportsLanguage(language) {
50
+ if (typeof language !== 'string') return false;
51
+ const supported = ['typescript', 'javascript', 'ts', 'js', 'dart'];
52
+ return supported.includes(language.toLowerCase());
53
+ }
54
+
55
+ getSupportedLanguages() {
56
+ return ['typescript', 'javascript', 'dart'];
57
+ }
58
+
59
+ async analyze(files, language, options = {}) {
60
+ const analyzer = this.getAnalyzer(language);
61
+ if (!analyzer) return [];
62
+ if (typeof analyzer.analyze === 'function') {
63
+ return analyzer.analyze(files, language, options);
64
+ }
65
+ return [];
66
+ }
67
+
68
+ async initialize(semanticEngineOrLanguage = null, semanticEngine = null) {
69
+ let engine = semanticEngine;
70
+ let lang = null;
71
+
72
+ if (typeof semanticEngineOrLanguage === 'string') {
73
+ lang = semanticEngineOrLanguage;
74
+ } else if (semanticEngineOrLanguage && typeof semanticEngineOrLanguage === 'object') {
75
+ engine = semanticEngineOrLanguage;
76
+ }
77
+
78
+ if (lang) {
79
+ const analyzer = this.getAnalyzer(lang);
80
+ if (analyzer && typeof analyzer.initialize === 'function') {
81
+ await analyzer.initialize(engine);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ module.exports = new S008Router();
@@ -0,0 +1,216 @@
1
+ /**
2
+ * S008 TypeScript Analyzer - SVG Content Validation
3
+ *
4
+ * Detects unsafe SVG handling in TypeScript/JavaScript code.
5
+ *
6
+ * Rule: Ensure user-supplied SVG content is validated or sanitized
7
+ * to prevent script injection and other attacks.
8
+ *
9
+ * Detects:
10
+ * - SVG content rendered without sanitization
11
+ * - dangerouslySetInnerHTML with SVG
12
+ * - Direct DOM insertion of SVG content
13
+ * - Missing DOMPurify or similar sanitization
14
+ */
15
+
16
+ const fs = require('fs');
17
+
18
+ class TypeScriptS008Analyzer {
19
+ constructor(config = {}) {
20
+ this.ruleId = 'S008';
21
+ this.language = 'typescript';
22
+ this.config = {
23
+ checkDangerouslySetInnerHTML: config.checkDangerouslySetInnerHTML !== false,
24
+ checkDirectInsertion: config.checkDirectInsertion !== false,
25
+ };
26
+ }
27
+
28
+ getMetadata() {
29
+ return {
30
+ ruleId: 'S008',
31
+ name: 'SVG Content Validation',
32
+ language: 'typescript',
33
+ description: 'Ensure user-supplied SVG content is sanitized before rendering'
34
+ };
35
+ }
36
+
37
+ getConfig() {
38
+ return this.config;
39
+ }
40
+
41
+ async analyze(files, language, options = {}) {
42
+ const violations = [];
43
+
44
+ for (const filePath of files) {
45
+ if (!/\.(ts|tsx|js|jsx)$/i.test(filePath)) continue;
46
+
47
+ try {
48
+ const content = fs.readFileSync(filePath, 'utf-8');
49
+ const fileViolations = this.analyzeContent(content, filePath);
50
+ violations.push(...fileViolations);
51
+ } catch (error) {
52
+ // Skip files that can't be read
53
+ }
54
+ }
55
+
56
+ return violations;
57
+ }
58
+
59
+ analyzeContent(content, filePath) {
60
+ const violations = [];
61
+ const lines = content.split('\n');
62
+
63
+ // Skip test files
64
+ if (/\.(test|spec)\.(ts|js|tsx|jsx)$/i.test(filePath) ||
65
+ /__(tests?|mocks?)__/i.test(filePath)) {
66
+ return violations;
67
+ }
68
+
69
+ // Check if file uses DOMPurify or similar sanitization
70
+ const hasSanitizer = /(?:DOMPurify|sanitize|xss|purify)/i.test(content);
71
+
72
+ // Track SVG-related variables
73
+ const svgVars = new Set();
74
+ const contentLower = content.toLowerCase();
75
+ const hasSvgContext = contentLower.includes('svg') ||
76
+ contentLower.includes('.svg') ||
77
+ contentLower.includes('image/svg');
78
+
79
+ // Only analyze if there's SVG context
80
+ if (!hasSvgContext) {
81
+ return violations;
82
+ }
83
+
84
+ // Patterns for detecting unsafe SVG handling
85
+ const patterns = [
86
+ {
87
+ // dangerouslySetInnerHTML with SVG content
88
+ regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(\w+)/g,
89
+ message: 'Unsafe SVG rendering: dangerouslySetInnerHTML without sanitization',
90
+ checkVar: true
91
+ },
92
+ {
93
+ // innerHTML assignment with SVG
94
+ regex: /\.innerHTML\s*=\s*(\w*[Ss]vg\w*)/g,
95
+ message: 'Unsafe SVG insertion: innerHTML assignment with SVG content'
96
+ },
97
+ {
98
+ // Direct SVG string insertion
99
+ regex: /\.innerHTML\s*=\s*['"`]<svg/g,
100
+ message: 'SVG string directly inserted via innerHTML - consider using sanitization'
101
+ },
102
+ {
103
+ // insertAdjacentHTML with SVG
104
+ regex: /insertAdjacentHTML\s*\([^)]*[Ss]vg/g,
105
+ message: 'Unsafe SVG insertion via insertAdjacentHTML'
106
+ },
107
+ {
108
+ // document.write with SVG
109
+ regex: /document\.write\s*\([^)]*[Ss]vg/g,
110
+ message: 'Unsafe SVG insertion via document.write'
111
+ },
112
+ {
113
+ // Blob URL creation with SVG without sanitization
114
+ regex: /new\s+Blob\s*\(\s*\[[^\]]*[Ss]vg/g,
115
+ message: 'SVG Blob created - ensure content is sanitized before creating Blob'
116
+ },
117
+ {
118
+ // SVG data URL without sanitization
119
+ regex: /data:image\/svg\+xml[^'"`]*\$\{/g,
120
+ message: 'Dynamic SVG data URL - ensure content is sanitized'
121
+ },
122
+ {
123
+ // User input directly used in SVG
124
+ regex: /(?:req\.(?:body|query|params)|userInput|formData)\s*\.\s*\w*[Ss]vg/g,
125
+ message: 'User-supplied SVG content used without validation'
126
+ },
127
+ {
128
+ // File upload SVG without validation
129
+ regex: /(?:multer|upload|file)\s*\([^)]*svg/gi,
130
+ message: 'SVG file upload - ensure content is validated and sanitized'
131
+ }
132
+ ];
133
+
134
+ lines.forEach((line, lineIndex) => {
135
+ // Skip comments
136
+ if (/^\s*(\/\/|\/\*|\*)/.test(line)) return;
137
+
138
+ // Skip if line has sanitization
139
+ if (/DOMPurify|sanitize|purify/i.test(line)) return;
140
+
141
+ for (const pattern of patterns) {
142
+ let match;
143
+ pattern.regex.lastIndex = 0;
144
+ while ((match = pattern.regex.exec(line)) !== null) {
145
+ // For dangerouslySetInnerHTML, check if the variable is SVG-related
146
+ if (pattern.checkVar && match[1]) {
147
+ const varName = match[1].toLowerCase();
148
+ if (!varName.includes('svg') && !svgVars.has(match[1])) {
149
+ // Check if variable is assigned SVG content elsewhere
150
+ const varAssignment = new RegExp(`${match[1]}\\s*=.*svg`, 'i');
151
+ if (!varAssignment.test(content)) {
152
+ continue;
153
+ }
154
+ }
155
+ }
156
+
157
+ // Skip if sanitizer is used nearby (within 5 lines)
158
+ let hasSanitizationNearby = false;
159
+ for (let i = Math.max(0, lineIndex - 5); i < Math.min(lines.length, lineIndex + 5); i++) {
160
+ if (/DOMPurify|sanitize|purify/i.test(lines[i])) {
161
+ hasSanitizationNearby = true;
162
+ break;
163
+ }
164
+ }
165
+ if (hasSanitizationNearby) continue;
166
+
167
+ violations.push({
168
+ ruleId: 'S008',
169
+ file: filePath,
170
+ line: lineIndex + 1,
171
+ column: match.index + 1,
172
+ severity: 'warning',
173
+ message: pattern.message,
174
+ suggestion: 'Use DOMPurify or similar library to sanitize SVG content before rendering'
175
+ });
176
+ }
177
+ }
178
+ });
179
+
180
+ // Check for SVG element creation without sanitization
181
+ if (!hasSanitizer) {
182
+ lines.forEach((line, lineIndex) => {
183
+ // Skip comments
184
+ if (/^\s*(\/\/|\/\*|\*)/.test(line)) return;
185
+
186
+ // Check for creating SVG element and setting content
187
+ if (/createElementNS\s*\([^)]*svg/i.test(line) ||
188
+ /createElement\s*\(\s*['"`]svg['"`]/i.test(line)) {
189
+ // Look ahead for innerHTML or textContent assignment
190
+ for (let i = lineIndex; i < Math.min(lines.length, lineIndex + 10); i++) {
191
+ if (/\.innerHTML\s*=/.test(lines[i]) && !/sanitize|purify/i.test(lines[i])) {
192
+ violations.push({
193
+ ruleId: 'S008',
194
+ file: filePath,
195
+ line: i + 1,
196
+ column: 1,
197
+ severity: 'warning',
198
+ message: 'SVG element content set without sanitization',
199
+ suggestion: 'Sanitize content before setting innerHTML on SVG elements'
200
+ });
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ });
206
+ }
207
+
208
+ return violations;
209
+ }
210
+
211
+ supportsLanguage(language) {
212
+ return ['typescript', 'javascript', 'ts', 'js'].includes(language.toLowerCase());
213
+ }
214
+ }
215
+
216
+ module.exports = TypeScriptS008Analyzer;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * S018 Dart Analyzer - No Sensitive Data in Browser Storage
3
+ *
4
+ * This is a JS wrapper that delegates to DartAnalyzer binary.
5
+ * Actual implementation: dart_analyzer/lib/rules/security/S018_no_sensitive_browser_storage.dart
6
+ *
7
+ * Rule: Do not store sensitive data in browser storage (SharedPreferences, etc.)
8
+ */
9
+
10
+ class DartS018Analyzer {
11
+ constructor() {
12
+ this.ruleId = 'S018';
13
+ this.language = 'dart';
14
+ }
15
+
16
+ getMetadata() {
17
+ return {
18
+ ruleId: 'S018',
19
+ name: 'No Sensitive Data in Browser Storage',
20
+ language: 'dart',
21
+ delegateTo: 'dart_analyzer',
22
+ description: 'Prevent sensitive data from being stored in client-side storage'
23
+ };
24
+ }
25
+
26
+ getConfig() {
27
+ return {
28
+ checkSharedPreferences: true,
29
+ checkSecureStorage: true,
30
+ severity: 'error'
31
+ };
32
+ }
33
+
34
+ async analyze(files, language, options) {
35
+ // Delegated to DartAnalyzer binary via heuristic-engine.js
36
+ return [];
37
+ }
38
+
39
+ supportsLanguage(language) {
40
+ return language === 'dart';
41
+ }
42
+ }
43
+
44
+ module.exports = DartS018Analyzer;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * S018 Rule Router - No Sensitive Data in Browser Storage
3
+ *
4
+ * Routes analysis to the appropriate language-specific analyzer.
5
+ * Supports: TypeScript, JavaScript, Dart
6
+ *
7
+ * Rule: Do not store sensitive data in browser storage (localStorage, sessionStorage, IndexedDB)
8
+ */
9
+
10
+ const path = require('path');
11
+
12
+ class S018Router {
13
+ constructor() {
14
+ this.analyzers = new Map();
15
+ this.ruleId = 'S018';
16
+ }
17
+
18
+ getAnalyzer(language) {
19
+ const normalizedLang = this.normalizeLanguage(language);
20
+
21
+ if (!this.analyzers.has(normalizedLang)) {
22
+ try {
23
+ const analyzerPath = path.join(__dirname, normalizedLang, 'analyzer.js');
24
+ const AnalyzerClass = require(analyzerPath);
25
+ this.analyzers.set(normalizedLang, new AnalyzerClass());
26
+ } catch (error) {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ return this.analyzers.get(normalizedLang);
32
+ }
33
+
34
+ normalizeLanguage(language) {
35
+ if (typeof language !== 'string') {
36
+ return 'typescript';
37
+ }
38
+ const languageMap = {
39
+ 'typescript': 'typescript',
40
+ 'javascript': 'typescript',
41
+ 'ts': 'typescript',
42
+ 'js': 'typescript',
43
+ 'dart': 'dart'
44
+ };
45
+ return languageMap[language.toLowerCase()] || language.toLowerCase();
46
+ }
47
+
48
+ supportsLanguage(language) {
49
+ if (typeof language !== 'string') return false;
50
+ const supported = ['typescript', 'javascript', 'ts', 'js', 'dart'];
51
+ return supported.includes(language.toLowerCase());
52
+ }
53
+
54
+ getSupportedLanguages() {
55
+ return ['typescript', 'javascript', 'dart'];
56
+ }
57
+
58
+ async analyze(files, language, options = {}) {
59
+ const analyzer = this.getAnalyzer(language);
60
+ if (!analyzer) return [];
61
+ if (typeof analyzer.analyze === 'function') {
62
+ return analyzer.analyze(files, language, options);
63
+ }
64
+ return [];
65
+ }
66
+
67
+ async initialize(semanticEngineOrLanguage = null, semanticEngine = null) {
68
+ let engine = semanticEngine;
69
+ let lang = null;
70
+
71
+ if (typeof semanticEngineOrLanguage === 'string') {
72
+ lang = semanticEngineOrLanguage;
73
+ } else if (semanticEngineOrLanguage && typeof semanticEngineOrLanguage === 'object') {
74
+ engine = semanticEngineOrLanguage;
75
+ }
76
+
77
+ if (lang) {
78
+ const analyzer = this.getAnalyzer(lang);
79
+ if (analyzer && typeof analyzer.initialize === 'function') {
80
+ await analyzer.initialize(engine);
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ module.exports = new S018Router();
@@ -0,0 +1,193 @@
1
+ /**
2
+ * S018 TypeScript Analyzer - No Sensitive Data in Browser Storage
3
+ *
4
+ * Detects sensitive data stored in browser storage mechanisms.
5
+ *
6
+ * Rule: Do not store sensitive data in localStorage, sessionStorage, or IndexedDB.
7
+ * Exception: Session tokens may be stored with proper safeguards.
8
+ *
9
+ * Detects:
10
+ * - Passwords/credentials in browser storage
11
+ * - PII (personal identifiable information) in storage
12
+ * - API keys or secrets in localStorage/sessionStorage
13
+ * - Financial data in client-side storage
14
+ */
15
+
16
+ const fs = require('fs');
17
+
18
+ class TypeScriptS018Analyzer {
19
+ constructor(config = {}) {
20
+ this.ruleId = 'S018';
21
+ this.language = 'typescript';
22
+ this.config = {
23
+ checkLocalStorage: config.checkLocalStorage !== false,
24
+ checkSessionStorage: config.checkSessionStorage !== false,
25
+ checkIndexedDB: config.checkIndexedDB !== false,
26
+ };
27
+ }
28
+
29
+ getMetadata() {
30
+ return {
31
+ ruleId: 'S018',
32
+ name: 'No Sensitive Data in Browser Storage',
33
+ language: 'typescript',
34
+ description: 'Prevent sensitive data from being stored in browser storage'
35
+ };
36
+ }
37
+
38
+ getConfig() {
39
+ return this.config;
40
+ }
41
+
42
+ async analyze(files, language, options = {}) {
43
+ const violations = [];
44
+
45
+ for (const filePath of files) {
46
+ if (!/\.(ts|tsx|js|jsx)$/i.test(filePath)) continue;
47
+
48
+ try {
49
+ const content = fs.readFileSync(filePath, 'utf-8');
50
+ const fileViolations = this.analyzeContent(content, filePath);
51
+ violations.push(...fileViolations);
52
+ } catch (error) {
53
+ // Skip files that can't be read
54
+ }
55
+ }
56
+
57
+ return violations;
58
+ }
59
+
60
+ analyzeContent(content, filePath) {
61
+ const violations = [];
62
+ const lines = content.split('\n');
63
+
64
+ // Skip test files
65
+ if (/\.(test|spec)\.(ts|js|tsx|jsx)$/i.test(filePath) ||
66
+ /__(tests?|mocks?)__/i.test(filePath)) {
67
+ return violations;
68
+ }
69
+
70
+ // Sensitive data patterns
71
+ const sensitivePatterns = [
72
+ 'password', 'passwd', 'pwd',
73
+ 'secret', 'apikey', 'api_key', 'api-key',
74
+ 'private_key', 'privatekey', 'private-key',
75
+ 'credit_card', 'creditcard', 'credit-card',
76
+ 'card_number', 'cardnumber', 'cvv', 'cvc',
77
+ 'ssn', 'social_security', 'socialSecurity',
78
+ 'bank_account', 'bankaccount', 'bank-account',
79
+ 'encryption_key', 'encryptionkey',
80
+ 'auth_token', 'authtoken', // different from session token
81
+ 'refresh_token', 'refreshtoken',
82
+ ];
83
+
84
+ // Allowed patterns (session tokens are acceptable)
85
+ const allowedPatterns = [
86
+ 'session_token', 'sessiontoken', 'session-token',
87
+ 'access_token', 'accesstoken', 'access-token',
88
+ 'jwt', 'bearer',
89
+ 'theme', 'language', 'locale', 'preference',
90
+ 'cart', 'wishlist'
91
+ ];
92
+
93
+ // Storage methods to check
94
+ const storagePatterns = [
95
+ {
96
+ regex: /localStorage\.setItem\s*\(\s*['"`]([^'"`]+)['"`]\s*,/g,
97
+ storage: 'localStorage'
98
+ },
99
+ {
100
+ regex: /sessionStorage\.setItem\s*\(\s*['"`]([^'"`]+)['"`]\s*,/g,
101
+ storage: 'sessionStorage'
102
+ },
103
+ {
104
+ regex: /localStorage\s*\[\s*['"`]([^'"`]+)['"`]\s*\]\s*=/g,
105
+ storage: 'localStorage'
106
+ },
107
+ {
108
+ regex: /sessionStorage\s*\[\s*['"`]([^'"`]+)['"`]\s*\]\s*=/g,
109
+ storage: 'sessionStorage'
110
+ },
111
+ {
112
+ regex: /indexedDB\.open\s*\(\s*['"`]([^'"`]+)['"`]/g,
113
+ storage: 'IndexedDB'
114
+ },
115
+ {
116
+ regex: /\.put\s*\(\s*\{[^}]*(?:password|secret|apiKey|creditCard)[^}]*\}/gi,
117
+ storage: 'IndexedDB',
118
+ directMatch: true
119
+ }
120
+ ];
121
+
122
+ lines.forEach((line, lineIndex) => {
123
+ // Skip comments
124
+ if (/^\s*(\/\/|\/\*|\*)/.test(line)) return;
125
+
126
+ for (const pattern of storagePatterns) {
127
+ let match;
128
+ pattern.regex.lastIndex = 0;
129
+
130
+ while ((match = pattern.regex.exec(line)) !== null) {
131
+ const key = match[1] ? match[1].toLowerCase() : '';
132
+
133
+ // Check if key matches sensitive patterns
134
+ let isSensitive = false;
135
+ let matchedPattern = '';
136
+
137
+ if (pattern.directMatch) {
138
+ isSensitive = true;
139
+ matchedPattern = 'sensitive data object';
140
+ } else {
141
+ for (const sensitive of sensitivePatterns) {
142
+ if (key.includes(sensitive.toLowerCase())) {
143
+ // Check if it's actually allowed
144
+ const isAllowed = allowedPatterns.some(allowed =>
145
+ key.includes(allowed.toLowerCase())
146
+ );
147
+ if (!isAllowed) {
148
+ isSensitive = true;
149
+ matchedPattern = sensitive;
150
+ break;
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ if (isSensitive) {
157
+ violations.push({
158
+ ruleId: 'S018',
159
+ file: filePath,
160
+ line: lineIndex + 1,
161
+ column: match.index + 1,
162
+ severity: 'error',
163
+ message: `Sensitive data "${matchedPattern}" stored in ${pattern.storage} - use server-side session instead`,
164
+ suggestion: 'Store sensitive data on the server, not in browser storage. Use HttpOnly cookies for session management.'
165
+ });
166
+ }
167
+ }
168
+ }
169
+
170
+ // Check for storing entire objects that might contain sensitive data
171
+ if (/localStorage\.setItem.*JSON\.stringify.*(?:user|credentials|auth)/i.test(line) ||
172
+ /sessionStorage\.setItem.*JSON\.stringify.*(?:user|credentials|auth)/i.test(line)) {
173
+ violations.push({
174
+ ruleId: 'S018',
175
+ file: filePath,
176
+ line: lineIndex + 1,
177
+ column: 1,
178
+ severity: 'warning',
179
+ message: 'Storing user/auth object in browser storage may expose sensitive data',
180
+ suggestion: 'Ensure the object does not contain passwords, tokens, or PII before storing'
181
+ });
182
+ }
183
+ });
184
+
185
+ return violations;
186
+ }
187
+
188
+ supportsLanguage(language) {
189
+ return ['typescript', 'javascript', 'ts', 'js'].includes(language.toLowerCase());
190
+ }
191
+ }
192
+
193
+ module.exports = TypeScriptS018Analyzer;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * S021 Dart Analyzer - Referrer Policy
3
+ *
4
+ * This is a JS wrapper that delegates to DartAnalyzer binary.
5
+ * Actual implementation: dart_analyzer/lib/rules/security/S021_referrer_policy.dart
6
+ *
7
+ * Rule: Set Referrer-Policy to prevent sensitive data leakage
8
+ */
9
+
10
+ class DartS021Analyzer {
11
+ constructor() {
12
+ this.ruleId = 'S021';
13
+ this.language = 'dart';
14
+ }
15
+
16
+ getMetadata() {
17
+ return {
18
+ ruleId: 'S021',
19
+ name: 'Referrer Policy',
20
+ language: 'dart',
21
+ delegateTo: 'dart_analyzer',
22
+ description: 'Set Referrer-Policy to prevent sensitive data leakage'
23
+ };
24
+ }
25
+
26
+ getConfig() {
27
+ return {
28
+ checkHeaders: true,
29
+ checkExternalLinks: true,
30
+ severity: 'warning'
31
+ };
32
+ }
33
+
34
+ async analyze(files, language, options) {
35
+ // Delegated to DartAnalyzer binary via heuristic-engine.js
36
+ return [];
37
+ }
38
+
39
+ supportsLanguage(language) {
40
+ return language === 'dart';
41
+ }
42
+ }
43
+
44
+ module.exports = DartS021Analyzer;