@sun-asterisk/sunlint 1.2.2 → 1.3.1

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 (124) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/CONTRIBUTING.md +1654 -66
  3. package/README.md +19 -6
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/engines/engines-enhanced.json +86 -0
  7. package/config/engines/semantic-config.json +114 -0
  8. package/config/eslint-rule-mapping.json +50 -38
  9. package/config/large-project.json +143 -0
  10. package/config/presets/all.json +0 -1
  11. package/config/release.json +70 -0
  12. package/config/rule-analysis-strategies.js +23 -4
  13. package/config/rules/S027-categories.json +122 -0
  14. package/config/rules/enhanced-rules-registry.json +2564 -0
  15. package/config/rules/rules-registry-generated.json +785 -837
  16. package/config/rules/rules-registry.json +13 -1
  17. package/core/adapters/sunlint-rule-adapter.js +25 -30
  18. package/core/analysis-orchestrator.js +42 -2
  19. package/core/categories.js +52 -0
  20. package/core/category-constants.js +39 -0
  21. package/core/cli-action-handler.js +53 -32
  22. package/core/cli-program.js +11 -3
  23. package/core/config-manager.js +111 -0
  24. package/core/config-merger.js +88 -0
  25. package/core/constants/categories.js +168 -0
  26. package/core/constants/defaults.js +165 -0
  27. package/core/constants/engines.js +185 -0
  28. package/core/constants/index.js +30 -0
  29. package/core/constants/rules.js +215 -0
  30. package/core/enhanced-rules-registry.js +3 -3
  31. package/core/file-targeting-service.js +128 -7
  32. package/core/interfaces/rule-plugin.interface.js +207 -0
  33. package/core/plugin-manager.js +448 -0
  34. package/core/rule-selection-service.js +42 -15
  35. package/core/semantic-engine.js +658 -0
  36. package/core/semantic-rule-base.js +433 -0
  37. package/core/unified-rule-registry.js +484 -0
  38. package/docs/COMMAND-EXAMPLES.md +134 -0
  39. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  40. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  41. package/engines/core/base-engine.js +249 -0
  42. package/engines/engine-factory.js +275 -0
  43. package/engines/eslint-engine.js +171 -19
  44. package/engines/heuristic-engine.js +569 -78
  45. package/integrations/eslint/plugin/index.js +26 -28
  46. package/origin-rules/common-en.md +8 -8
  47. package/package.json +10 -6
  48. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  49. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  50. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  51. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  52. package/rules/common/C033_separate_service_repository/README.md +78 -0
  53. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  54. package/rules/common/C033_separate_service_repository/config.json +50 -0
  55. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  56. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  57. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  58. package/rules/common/C035_error_logging_context/analyzer.js +230 -0
  59. package/rules/common/C035_error_logging_context/config.json +54 -0
  60. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  61. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  62. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  63. package/rules/common/C040_centralized_validation/config.json +46 -0
  64. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  65. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  66. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  67. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  68. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  69. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  70. package/rules/common/C076_explicit_function_types/README.md +30 -0
  71. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  72. package/rules/common/C076_explicit_function_types/config.json +15 -0
  73. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  74. package/rules/index.js +8 -0
  75. package/rules/parser/rule-parser.js +13 -2
  76. package/rules/security/S005_no_origin_auth/README.md +226 -0
  77. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  78. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  79. package/rules/security/S005_no_origin_auth/config.json +85 -0
  80. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  81. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  82. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  83. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  84. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  85. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  86. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  87. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  88. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  89. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  90. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  91. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  92. package/scripts/category-manager.js +150 -0
  93. package/scripts/generate-rules-registry.js +88 -0
  94. package/scripts/migrate-rule-registry.js +157 -0
  95. package/scripts/prepare-release.sh +1 -1
  96. package/scripts/validate-system.js +48 -0
  97. package/.sunlint.json +0 -35
  98. package/config/README.md +0 -88
  99. package/config/engines/eslint-rule-mapping.json +0 -74
  100. package/config/schemas/sunlint-schema.json +0 -0
  101. package/config/testing/test-s005-working.ts +0 -22
  102. package/core/multi-rule-runner.js +0 -0
  103. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  104. package/docs/FUTURE_PACKAGES.md +0 -83
  105. package/docs/HEURISTIC_VS_AI.md +0 -113
  106. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  107. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  108. package/docs/RELEASE_GUIDE.md +0 -230
  109. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  110. package/engines/tree-sitter-parser.js +0 -0
  111. package/engines/universal-ast-engine.js +0 -0
  112. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  113. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  114. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  115. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  116. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  117. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  118. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  119. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  120. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  121. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  122. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  123. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  124. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -0,0 +1,406 @@
1
+ /**
2
+ * AST-based analyzer for S005 - No Origin Header Authentication
3
+ * Detects usage of Origin header for authentication/access control through AST analysis
4
+ */
5
+
6
+ const babel = require('@babel/parser');
7
+ const traverse = require('@babel/traverse').default;
8
+
9
+ class S005ASTAnalyzer {
10
+ constructor() {
11
+ this.ruleId = 'S005';
12
+ this.ruleName = 'No Origin Header Authentication';
13
+ this.description = 'Do not use Origin header for authentication or access control';
14
+ }
15
+
16
+ async analyze(files, language, options = {}) {
17
+ const violations = [];
18
+
19
+ for (const filePath of files) {
20
+ try {
21
+ const content = require('fs').readFileSync(filePath, 'utf8');
22
+ const fileViolations = await this.analyzeFile(content, filePath, options);
23
+ violations.push(...fileViolations);
24
+ } catch (error) {
25
+ if (options.verbose) {
26
+ console.warn(`⚠️ S005 AST analysis failed for ${filePath}: ${error.message}`);
27
+ }
28
+ }
29
+ }
30
+
31
+ return violations;
32
+ }
33
+
34
+ async analyzeFile(content, filePath, options = {}) {
35
+ const violations = [];
36
+
37
+ try {
38
+ // Parse with TypeScript/JavaScript support
39
+ const ast = babel.parse(content, {
40
+ sourceType: 'module',
41
+ allowImportExportEverywhere: true,
42
+ allowReturnOutsideFunction: true,
43
+ plugins: [
44
+ 'typescript',
45
+ 'jsx',
46
+ 'objectRestSpread',
47
+ 'functionBind',
48
+ 'exportDefaultFrom',
49
+ 'decorators-legacy',
50
+ 'classProperties',
51
+ 'asyncGenerators',
52
+ 'dynamicImport'
53
+ ]
54
+ });
55
+
56
+ // Traverse AST to find Origin header usage in authentication contexts
57
+ traverse(ast, {
58
+ MemberExpression: (path) => {
59
+ this.checkOriginHeaderAccess(path, violations, filePath);
60
+ },
61
+ CallExpression: (path) => {
62
+ this.checkOriginHeaderMethods(path, violations, filePath);
63
+ },
64
+ IfStatement: (path) => {
65
+ this.checkConditionalOriginAuth(path, violations, filePath);
66
+ },
67
+ AssignmentExpression: (path) => {
68
+ this.checkOriginAssignment(path, violations, filePath);
69
+ }
70
+ });
71
+
72
+ } catch (parseError) {
73
+ if (options.verbose) {
74
+ console.warn(`⚠️ S005 parse failed for ${filePath}: ${parseError.message}`);
75
+ }
76
+ // Fall back to regex analysis if AST parsing fails
77
+ return this.analyzeWithRegex(content, filePath, options);
78
+ }
79
+
80
+ return violations;
81
+ }
82
+
83
+ checkOriginHeaderAccess(path, violations, filePath) {
84
+ const node = path.node;
85
+
86
+ // Check for req.headers.origin, headers.origin, req.headers['origin']
87
+ if (this.isOriginHeaderAccess(node)) {
88
+ // Check if this is in an authentication context
89
+ if (this.isInAuthenticationContext(path)) {
90
+ violations.push({
91
+ ruleId: this.ruleId,
92
+ severity: 'error',
93
+ message: 'Origin header should not be used for authentication. Origin can be spoofed and is not secure for access control.',
94
+ line: node.loc ? node.loc.start.line : 1,
95
+ column: node.loc ? node.loc.start.column + 1 : 1,
96
+ filePath: filePath,
97
+ type: 'origin_header_access'
98
+ });
99
+ }
100
+ }
101
+ }
102
+
103
+ checkOriginHeaderMethods(path, violations, filePath) {
104
+ const node = path.node;
105
+
106
+ // Check for req.get('origin'), req.header('origin'), getHeader('origin')
107
+ if (this.isOriginHeaderMethod(node)) {
108
+ if (this.isInAuthenticationContext(path)) {
109
+ violations.push({
110
+ ruleId: this.ruleId,
111
+ severity: 'error',
112
+ message: 'Origin header retrieval methods should not be used for authentication purposes.',
113
+ line: node.loc ? node.loc.start.line : 1,
114
+ column: node.loc ? node.loc.start.column + 1 : 1,
115
+ filePath: filePath,
116
+ type: 'origin_header_method'
117
+ });
118
+ }
119
+ }
120
+
121
+ // Check for CORS configuration with origin-based auth
122
+ if (this.isCORSOriginAuth(node)) {
123
+ violations.push({
124
+ ruleId: this.ruleId,
125
+ severity: 'warning',
126
+ message: 'CORS origin configuration should not replace proper authentication mechanisms.',
127
+ line: node.loc ? node.loc.start.line : 1,
128
+ column: node.loc ? node.loc.start.column + 1 : 1,
129
+ filePath: filePath,
130
+ type: 'cors_origin_auth'
131
+ });
132
+ }
133
+ }
134
+
135
+ checkConditionalOriginAuth(path, violations, filePath) {
136
+ const node = path.node;
137
+
138
+ // Check if condition involves origin header and authentication
139
+ if (this.hasOriginInCondition(node.test) && this.hasAuthInBlock(node.consequent)) {
140
+ violations.push({
141
+ ruleId: this.ruleId,
142
+ severity: 'error',
143
+ message: 'Conditional authentication based on Origin header is insecure. Use proper authentication tokens.',
144
+ line: node.loc ? node.loc.start.line : 1,
145
+ column: node.loc ? node.loc.start.column + 1 : 1,
146
+ filePath: filePath,
147
+ type: 'conditional_origin_auth'
148
+ });
149
+ }
150
+ }
151
+
152
+ checkOriginAssignment(path, violations, filePath) {
153
+ const node = path.node;
154
+
155
+ // Check for assignments involving origin header in auth context
156
+ if (this.isOriginRelatedAssignment(node) && this.isInAuthenticationContext(path)) {
157
+ violations.push({
158
+ ruleId: this.ruleId,
159
+ severity: 'warning',
160
+ message: 'Origin header values should not be assigned for authentication purposes.',
161
+ line: node.loc ? node.loc.start.line : 1,
162
+ column: node.loc ? node.loc.start.column + 1 : 1,
163
+ filePath: filePath,
164
+ type: 'origin_assignment_auth'
165
+ });
166
+ }
167
+ }
168
+
169
+ isOriginHeaderAccess(node) {
170
+ // req.headers.origin
171
+ if (node.type === 'MemberExpression' &&
172
+ node.object && node.object.type === 'MemberExpression' &&
173
+ node.object.property && node.object.property.name === 'headers' &&
174
+ node.property && (node.property.name === 'origin' ||
175
+ (node.property.type === 'StringLiteral' && node.property.value === 'origin'))) {
176
+ return true;
177
+ }
178
+
179
+ // headers.origin or headers['origin']
180
+ if (node.type === 'MemberExpression' &&
181
+ node.object && node.object.name === 'headers' &&
182
+ node.property && (node.property.name === 'origin' ||
183
+ (node.property.type === 'StringLiteral' && node.property.value === 'origin'))) {
184
+ return true;
185
+ }
186
+
187
+ return false;
188
+ }
189
+
190
+ isOriginHeaderMethod(node) {
191
+ if (node.type !== 'CallExpression' || !node.callee) return false;
192
+
193
+ const callee = node.callee;
194
+
195
+ // req.get('origin'), req.header('origin')
196
+ if (callee.type === 'MemberExpression' &&
197
+ callee.property && (callee.property.name === 'get' || callee.property.name === 'header') &&
198
+ node.arguments && node.arguments.length > 0 &&
199
+ node.arguments[0].type === 'StringLiteral' &&
200
+ node.arguments[0].value.toLowerCase() === 'origin') {
201
+ return true;
202
+ }
203
+
204
+ // getHeader('origin')
205
+ if (callee.type === 'Identifier' && callee.name === 'getHeader' &&
206
+ node.arguments && node.arguments.length > 0 &&
207
+ node.arguments[0].type === 'StringLiteral' &&
208
+ node.arguments[0].value.toLowerCase() === 'origin') {
209
+ return true;
210
+ }
211
+
212
+ return false;
213
+ }
214
+
215
+ isCORSOriginAuth(node) {
216
+ if (node.type !== 'CallExpression' || !node.callee) return false;
217
+
218
+ // Check for CORS configuration calls
219
+ const callee = node.callee;
220
+ if (callee.type === 'Identifier' && callee.name === 'cors' ||
221
+ (callee.type === 'MemberExpression' && callee.property && callee.property.name === 'cors')) {
222
+
223
+ // Check if arguments contain auth-related configuration
224
+ if (node.arguments && node.arguments.length > 0) {
225
+ const config = node.arguments[0];
226
+ if (config.type === 'ObjectExpression') {
227
+ return config.properties.some(prop =>
228
+ this.isPropertyWithAuthKeyword(prop) && this.hasOriginReference(prop)
229
+ );
230
+ }
231
+ }
232
+ }
233
+
234
+ return false;
235
+ }
236
+
237
+ hasOriginInCondition(testNode) {
238
+ if (!testNode) return false;
239
+
240
+ // Recursively check for origin references in condition
241
+ if (testNode.type === 'MemberExpression') {
242
+ return this.isOriginHeaderAccess(testNode);
243
+ }
244
+
245
+ if (testNode.type === 'CallExpression') {
246
+ return this.isOriginHeaderMethod(testNode);
247
+ }
248
+
249
+ if (testNode.type === 'BinaryExpression') {
250
+ return this.hasOriginInCondition(testNode.left) || this.hasOriginInCondition(testNode.right);
251
+ }
252
+
253
+ if (testNode.type === 'LogicalExpression') {
254
+ return this.hasOriginInCondition(testNode.left) || this.hasOriginInCondition(testNode.right);
255
+ }
256
+
257
+ return false;
258
+ }
259
+
260
+ hasAuthInBlock(blockNode) {
261
+ if (!blockNode) return false;
262
+
263
+ const authKeywords = ['auth', 'login', 'token', 'session', 'user', 'permission', 'access'];
264
+
265
+ // Simple check for auth-related identifiers in the block
266
+ let hasAuth = false;
267
+
268
+ try {
269
+ traverse(blockNode, {
270
+ Identifier: (path) => {
271
+ if (path.node && path.node.name && authKeywords.some(keyword =>
272
+ path.node.name.toLowerCase().includes(keyword))) {
273
+ hasAuth = true;
274
+ path.stop();
275
+ }
276
+ },
277
+ StringLiteral: (path) => {
278
+ if (path.node && path.node.value && authKeywords.some(keyword =>
279
+ path.node.value.toLowerCase().includes(keyword))) {
280
+ hasAuth = true;
281
+ path.stop();
282
+ }
283
+ }
284
+ }, this);
285
+ } catch (error) {
286
+ // Ignore traverse errors, return false
287
+ return false;
288
+ }
289
+
290
+ return hasAuth;
291
+ }
292
+
293
+ isInAuthenticationContext(path) {
294
+ // Check parent nodes for authentication context
295
+ let currentPath = path;
296
+ let depth = 0;
297
+ const maxDepth = 10;
298
+
299
+ while (currentPath && depth < maxDepth) {
300
+ const node = currentPath.node;
301
+
302
+ // Check function names
303
+ if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' ||
304
+ node.type === 'ArrowFunctionExpression') {
305
+ if (this.hasAuthInName(node.id?.name)) {
306
+ return true;
307
+ }
308
+ }
309
+
310
+ // Check variable declarations
311
+ if (node.type === 'VariableDeclarator' && this.hasAuthInName(node.id?.name)) {
312
+ return true;
313
+ }
314
+
315
+ // Check object property names
316
+ if (node.type === 'ObjectProperty' && this.hasAuthInName(node.key?.name || node.key?.value)) {
317
+ return true;
318
+ }
319
+
320
+ currentPath = currentPath.parent;
321
+ depth++;
322
+ }
323
+
324
+ return false;
325
+ }
326
+
327
+ hasAuthInName(name) {
328
+ if (!name) return false;
329
+
330
+ const authKeywords = [
331
+ 'auth', 'login', 'logout', 'authenticate', 'authorize',
332
+ 'permission', 'access', 'token', 'session', 'user',
333
+ 'verify', 'validate', 'check', 'guard', 'protect',
334
+ 'middleware', 'passport', 'jwt', 'bearer'
335
+ ];
336
+
337
+ const lowerName = name.toLowerCase();
338
+ return authKeywords.some(keyword => lowerName.includes(keyword));
339
+ }
340
+
341
+ isOriginRelatedAssignment(node) {
342
+ if (node.type !== 'AssignmentExpression') return false;
343
+
344
+ // Check if right side involves origin header
345
+ return this.hasOriginReference(node.right);
346
+ }
347
+
348
+ hasOriginReference(node) {
349
+ if (!node) return false;
350
+
351
+ if (node.type === 'MemberExpression') {
352
+ return this.isOriginHeaderAccess(node);
353
+ }
354
+
355
+ if (node.type === 'CallExpression') {
356
+ return this.isOriginHeaderMethod(node);
357
+ }
358
+
359
+ if (node.type === 'Identifier' && node.name.toLowerCase().includes('origin')) {
360
+ return true;
361
+ }
362
+
363
+ if (node.type === 'StringLiteral' && node.value.toLowerCase().includes('origin')) {
364
+ return true;
365
+ }
366
+
367
+ return false;
368
+ }
369
+
370
+ isPropertyWithAuthKeyword(prop) {
371
+ if (!prop || prop.type !== 'ObjectProperty') return false;
372
+
373
+ const key = prop.key?.name || prop.key?.value;
374
+ if (!key) return false;
375
+
376
+ return this.hasAuthInName(key);
377
+ }
378
+
379
+ // Fallback regex analysis
380
+ analyzeWithRegex(content, filePath, options = {}) {
381
+ const violations = [];
382
+ const lines = content.split('\n');
383
+
384
+ lines.forEach((line, index) => {
385
+ const lineNumber = index + 1;
386
+
387
+ // Basic regex check for origin header in auth context
388
+ const originAuthPattern = /(?:req\.headers\.origin|req\.get\s*\(\s*['"`]origin['"`]\s*\)).*(?:auth|login|token|permission)/i;
389
+ if (originAuthPattern.test(line)) {
390
+ violations.push({
391
+ ruleId: this.ruleId,
392
+ severity: 'error',
393
+ message: 'Origin header should not be used for authentication (detected via regex fallback).',
394
+ line: lineNumber,
395
+ column: line.search(/origin/i) + 1,
396
+ filePath: filePath,
397
+ type: 'origin_auth_regex'
398
+ });
399
+ }
400
+ });
401
+
402
+ return violations;
403
+ }
404
+ }
405
+
406
+ module.exports = S005ASTAnalyzer;
@@ -0,0 +1,85 @@
1
+ {
2
+ "ruleId": "S005",
3
+ "name": "No Origin Header Authentication",
4
+ "description": "Do not use Origin header for authentication or access control",
5
+ "category": "security",
6
+ "severity": "error",
7
+ "languages": ["typescript", "javascript"],
8
+ "version": "1.0.0",
9
+ "status": "stable",
10
+ "tags": ["security", "authentication", "headers", "origin", "access-control"],
11
+
12
+ "patterns": {
13
+ "vulnerable": [
14
+ "req.headers.origin in authentication context",
15
+ "req.get('origin') for access control",
16
+ "Origin-based conditional authentication",
17
+ "CORS origin configuration mixed with auth",
18
+ "Express middleware using origin for security"
19
+ ],
20
+ "secure": [
21
+ "JWT token authentication",
22
+ "Session-based authentication",
23
+ "API key authentication",
24
+ "OAuth 2.0 flows",
25
+ "Proper CORS configuration without auth reliance"
26
+ ]
27
+ },
28
+
29
+ "configuration": {
30
+ "checkAuthContext": true,
31
+ "checkMiddleware": true,
32
+ "checkConditionals": true,
33
+ "checkCORSMixing": true,
34
+ "contextDepth": 3,
35
+ "ignoreComments": true
36
+ },
37
+
38
+ "examples": {
39
+ "violations": [
40
+ {
41
+ "code": "if (req.headers.origin === 'trusted.com') { req.authenticated = true; }",
42
+ "reason": "Using Origin header for authentication is insecure"
43
+ },
44
+ {
45
+ "code": "const authMiddleware = (req, res, next) => { if (req.get('origin') === 'admin.com') next(); }",
46
+ "reason": "Middleware should not rely on Origin header for access control"
47
+ }
48
+ ],
49
+ "valid": [
50
+ {
51
+ "code": "const token = req.headers.authorization; jwt.verify(token, secret, callback);",
52
+ "reason": "Proper JWT token authentication"
53
+ },
54
+ {
55
+ "code": "console.log('Request from:', req.headers.origin);",
56
+ "reason": "Using Origin header for logging only, not authentication"
57
+ }
58
+ ]
59
+ },
60
+
61
+ "remediation": {
62
+ "recommendations": [
63
+ "Use JWT tokens for authentication",
64
+ "Implement session-based authentication",
65
+ "Use API keys for service authentication",
66
+ "Implement OAuth 2.0 for third-party authentication",
67
+ "Use proper CORS configuration without relying on it for authentication"
68
+ ],
69
+ "resources": [
70
+ "https://owasp.org/www-community/vulnerabilities/CORS_OriginHeaderScrutiny",
71
+ "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin",
72
+ "https://auth0.com/docs/secure/tokens/json-web-tokens"
73
+ ]
74
+ },
75
+
76
+ "performance": {
77
+ "complexity": "O(n)",
78
+ "accuracy": {
79
+ "ast": 95,
80
+ "regex": 85
81
+ },
82
+ "falsePositiveRate": "< 5%",
83
+ "coverage": "High for TypeScript/JavaScript"
84
+ }
85
+ }
@@ -0,0 +1,139 @@
1
+ # S006 - No Plaintext Recovery/Activation Codes
2
+
3
+ ## Overview
4
+ Quy tắc này phát hiện việc gửi mã khôi phục (recovery codes), mã kích hoạt (activation codes), hoặc mã xác thực (verification codes) dưới dạng plaintext, có thể dẫn đến các lỗ hổng bảo mật nghiêm trọng.
5
+
6
+ ## OWASP Classification
7
+ - **Category**: A02:2021 - Cryptographic Failures
8
+ - **CWE**: CWE-319 - Cleartext Transmission of Sensitive Information
9
+ - **Severity**: Error
10
+ - **Impact**: High (Account takeover, unauthorized access)
11
+
12
+ ## Vấn đề
13
+ Khi gửi các mã nhạy cảm như recovery codes, activation codes, hoặc OTP codes dưới dạng plaintext:
14
+
15
+ 1. **Nguy cơ bị chặn bắt**: Mã có thể bị đánh cắp qua các kênh không an toàn
16
+ 2. **Account takeover**: Kẻ tấn công có thể sử dụng mã để chiếm quyền điều khiển tài khoản
17
+ 3. **Lưu trữ không an toàn**: Mã có thể bị lưu trong logs hoặc cache
18
+ 4. **Man-in-the-middle attacks**: Mã có thể bị chặn bắt trong quá trình truyền tải
19
+
20
+ ## Các trường hợp vi phạm
21
+
22
+ ### 1. Gửi mã qua email không mã hóa
23
+ ```javascript
24
+ // ❌ Vi phạm - gửi activation code dạng plaintext
25
+ const activationCode = generateCode();
26
+ await sendEmail(user.email, \`Your activation code is: \${activationCode}\`);
27
+
28
+ // ❌ Vi phạm - trả về recovery code trong API response
29
+ app.post('/forgot-password', (req, res) => {
30
+ const recoveryCode = generateRecoveryCode();
31
+ res.json({
32
+ message: 'Password reset initiated',
33
+ recoveryCode: recoveryCode // Nguy hiểm!
34
+ });
35
+ });
36
+ ```
37
+
38
+ ### 2. Lưu mã trong logs
39
+ ```javascript
40
+ // ❌ Vi phạm - log OTP code
41
+ const otpCode = generateOTP();
42
+ console.log(\`Generated OTP for user \${userId}: \${otpCode}\`);
43
+ logger.info(\`Sending verification code: \${verificationCode}\`);
44
+ ```
45
+
46
+ ### 3. Hiển thị mã trong response body
47
+ ```javascript
48
+ // ❌ Vi phạm - trả về mã trong response
49
+ const resetCode = await generateResetCode(userId);
50
+ return {
51
+ success: true,
52
+ resetCode: resetCode,
53
+ message: 'Check your email'
54
+ };
55
+ ```
56
+
57
+ ## Giải pháp an toàn
58
+
59
+ ### 1. Mã hóa mã trước khi gửi
60
+ ```javascript
61
+ // ✅ An toàn - hash mã trước khi lưu trữ
62
+ const activationCode = generateCode();
63
+ const hashedCode = await bcrypt.hash(activationCode, 10);
64
+ await saveToDatabase({ userId, hashedCode });
65
+
66
+ // Chỉ gửi mã qua kênh an toàn
67
+ await sendSecureEmail(user.email, activationCode);
68
+ ```
69
+
70
+ ### 2. Sử dụng token thay vì mã trực tiếp
71
+ ```javascript
72
+ // ✅ An toàn - sử dụng secure token
73
+ const resetToken = jwt.sign({ userId, purpose: 'reset' }, SECRET_KEY, { expiresIn: '15m' });
74
+ const resetLink = \`https://app.com/reset?token=\${resetToken}\`;
75
+ await sendEmail(user.email, \`Click here to reset: \${resetLink}\`);
76
+ ```
77
+
78
+ ### 3. Chỉ thông báo thành công, không trả về mã
79
+ ```javascript
80
+ // ✅ An toàn - không expose mã
81
+ app.post('/send-otp', async (req, res) => {
82
+ const otp = generateOTP();
83
+ await sendSMSOTP(user.phone, otp);
84
+
85
+ res.json({
86
+ success: true,
87
+ message: 'OTP sent to your phone'
88
+ // Không trả về OTP
89
+ });
90
+ });
91
+ ```
92
+
93
+ ### 4. Logging an toàn
94
+ ```javascript
95
+ // ✅ An toàn - log mà không expose mã
96
+ const verificationCode = generateCode();
97
+ logger.info(\`Verification code sent to user \${userId}\`);
98
+ // Không log mã thực tế
99
+ ```
100
+
101
+ ## Phương pháp phát hiện
102
+
103
+ Rule này sử dụng heuristic analysis để phát hiện:
104
+
105
+ 1. **Pattern matching**: Tìm kiếm các từ khóa như `recovery`, `activation`, `reset`, `otp`, `verification` kết hợp với `send`, `email`, `response`
106
+ 2. **Variable analysis**: Phân tích tên biến có chứa từ khóa nhạy cảm
107
+ 3. **Context analysis**: Kiểm tra ngữ cảnh truyền tải (HTTP response, email, SMS)
108
+ 4. **String literal analysis**: Phát hiện mã được hardcode trong chuỗi
109
+
110
+ ## Cấu hình
111
+
112
+ ```json
113
+ {
114
+ "S006": {
115
+ "enabled": true,
116
+ "severity": "error",
117
+ "excludePatterns": [
118
+ "test/**",
119
+ "**/*.test.js",
120
+ "**/*.spec.js"
121
+ ]
122
+ }
123
+ }
124
+ ```
125
+
126
+ ## Best Practices
127
+
128
+ 1. **Luôn mã hóa**: Mã hóa tất cả mã nhạy cảm trước khi truyền tải
129
+ 2. **Sử dụng HTTPS**: Đảm bảo tất cả API endpoints sử dụng HTTPS
130
+ 3. **Time-limited tokens**: Sử dụng tokens có thời hạn thay vì mã tĩnh
131
+ 4. **Secure channels**: Sử dụng các kênh truyền tải an toàn (encrypted email, secure SMS)
132
+ 5. **No logging**: Không bao giờ log mã nhạy cảm
133
+ 6. **Hash storage**: Luôn hash mã trước khi lưu vào database
134
+
135
+ ## Tài liệu tham khảo
136
+
137
+ - [OWASP A02:2021 - Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/)
138
+ - [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html)
139
+ - [NIST Guidelines for Password Recovery](https://pages.nist.gov/800-63-3/sp800-63b.html)