@sun-asterisk/sunlint 1.3.26 → 1.3.28

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 (69) hide show
  1. package/config/rules/enhanced-rules-registry.json +101 -17
  2. package/config/rules/rules-registry-generated.json +22 -22
  3. package/origin-rules/security-en.md +351 -338
  4. package/package.json +1 -1
  5. package/rules/common/C003_no_vague_abbreviations/analyzer.js +73 -21
  6. package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +206 -2
  7. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +553 -58
  8. package/rules/common/C029_catch_block_logging/analyzer.js +47 -12
  9. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +35 -15
  10. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +9 -5
  11. package/rules/security/S003_open_redirect_protection/README.md +371 -0
  12. package/rules/security/S003_open_redirect_protection/analyzer.js +135 -0
  13. package/rules/security/S003_open_redirect_protection/config.json +58 -0
  14. package/rules/security/S003_open_redirect_protection/symbol-based-analyzer.js +884 -0
  15. package/rules/security/S004_sensitive_data_logging/analyzer.js +135 -0
  16. package/rules/security/S004_sensitive_data_logging/config.json +62 -0
  17. package/rules/security/S004_sensitive_data_logging/symbol-based-analyzer.js +592 -0
  18. package/rules/security/S005_no_origin_auth/analyzer.js +97 -148
  19. package/rules/security/S005_no_origin_auth/config.json +28 -67
  20. package/rules/security/S005_no_origin_auth/symbol-based-analyzer.js +708 -0
  21. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +170 -31
  22. package/rules/security/S010_no_insecure_encryption/analyzer.js +8 -2
  23. package/rules/security/S012_hardcoded_secrets/analyzer.js +149 -0
  24. package/rules/security/S012_hardcoded_secrets/config.json +75 -0
  25. package/rules/security/S012_hardcoded_secrets/symbol-based-analyzer.js +1204 -0
  26. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +87 -0
  27. package/rules/security/S017_use_parameterized_queries/analyzer.js +11 -78
  28. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +1146 -1
  29. package/rules/security/S019_smtp_injection_protection/analyzer.js +120 -0
  30. package/rules/security/S019_smtp_injection_protection/config.json +35 -0
  31. package/rules/security/S019_smtp_injection_protection/symbol-based-analyzer.js +687 -0
  32. package/rules/security/S020_no_eval_dynamic_code/analyzer.js +55 -130
  33. package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +4 -19
  34. package/rules/security/S022_escape_output_context/README.md +254 -0
  35. package/rules/security/S022_escape_output_context/analyzer.js +510 -0
  36. package/rules/security/S022_escape_output_context/config.json +229 -0
  37. package/rules/security/S023_no_json_injection/analyzer.js +15 -0
  38. package/rules/security/S023_no_json_injection/ast-analyzer.js +18 -3
  39. package/rules/security/S023_no_json_injection/config.json +133 -0
  40. package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +41 -0
  41. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +67 -8
  42. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +29 -6
  43. package/rules/security/S029_csrf_protection/config.json +127 -0
  44. package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +160 -28
  45. package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +81 -19
  46. package/rules/security/S031_secure_session_cookies/analyzer.js +20 -2
  47. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +100 -0
  48. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +8 -1
  49. package/rules/security/S032_httponly_session_cookies/analyzer.js +2 -2
  50. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +115 -0
  51. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +39 -10
  52. package/rules/security/S036_lfi_rfi_protection/analyzer.js +224 -0
  53. package/rules/security/S036_lfi_rfi_protection/config.json +20 -0
  54. package/rules/security/S040_session_fixation_protection/analyzer.js +153 -0
  55. package/rules/security/S040_session_fixation_protection/config.json +20 -0
  56. package/rules/security/S042_require_re_authentication_for_long_lived/README.md +83 -0
  57. package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +153 -0
  58. package/rules/security/S042_require_re_authentication_for_long_lived/config.json +41 -0
  59. package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +1139 -0
  60. package/rules/security/S043_password_changes_invalidate_all_sessions/README.md +107 -0
  61. package/rules/security/S043_password_changes_invalidate_all_sessions/analyzer.js +153 -0
  62. package/rules/security/S043_password_changes_invalidate_all_sessions/config.json +41 -0
  63. package/rules/security/S043_password_changes_invalidate_all_sessions/symbol-based-analyzer.js +541 -0
  64. package/docs/COMMAND-EXAMPLES.md +0 -390
  65. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +0 -151
  66. package/docs/FOLDER_STRUCTURE.md +0 -59
  67. package/docs/SIMPLIFIED_USAGE_GUIDE.md +0 -208
  68. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +0 -541
  69. package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +0 -307
@@ -0,0 +1,153 @@
1
+ const fs = require('fs');
2
+
3
+ class S040Analyzer {
4
+ constructor() {
5
+ this.ruleId = 'S040';
6
+ this.ruleName = 'Session Fixation Protection';
7
+ }
8
+
9
+ async analyze(files, language, options = {}) {
10
+ const violations = [];
11
+
12
+ for (const filePath of files) {
13
+ // Skip test files
14
+ if (this.isTestFile(filePath)) {
15
+ continue;
16
+ }
17
+
18
+ try {
19
+ const content = fs.readFileSync(filePath, 'utf8');
20
+ const fileViolations = this.analyzeFile(content, filePath);
21
+ violations.push(...fileViolations);
22
+ } catch (error) {
23
+ if (options.verbose) {
24
+ console.warn(`S040: Error analyzing ${filePath}:`, error.message);
25
+ }
26
+ }
27
+ }
28
+
29
+ return violations;
30
+ }
31
+
32
+ isTestFile(filePath) {
33
+ const testPatterns = [
34
+ /\.test\.(ts|tsx|js|jsx|php|py)$/,
35
+ /\.spec\.(ts|tsx|js|jsx|php|py)$/,
36
+ /__tests__\//,
37
+ /__mocks__\//,
38
+ /\/tests?\//,
39
+ ];
40
+ return testPatterns.some(p => p.test(filePath));
41
+ }
42
+
43
+ analyzeFile(content, filePath) {
44
+ const violations = [];
45
+
46
+ // Find login/authentication functions
47
+ const loginFunctions = this.findLoginFunctions(content);
48
+
49
+ loginFunctions.forEach(({ startLine, endLine, functionContent, functionName }) => {
50
+ // Check if session is regenerated
51
+ if (!this.hasSessionRegeneration(functionContent)) {
52
+ violations.push({
53
+ file: filePath,
54
+ line: startLine,
55
+ column: 1,
56
+ message: `Login function '${functionName}' does not regenerate session - vulnerable to Session Fixation. Use session.regenerate(), req.session.regenerate(), or create new JWT token.`,
57
+ severity: 2, // error
58
+ ruleId: this.ruleId,
59
+ });
60
+ }
61
+ });
62
+
63
+ return violations;
64
+ }
65
+
66
+ findLoginFunctions(content) {
67
+ const functions = [];
68
+ const lines = content.split(/\r?\n/);
69
+
70
+ // Patterns that indicate login/authentication functions
71
+ const loginPatterns = [
72
+ /(?:async\s+)?(?:function\s+)?(\w*login\w*|authenticate\w*|signin\w*)\s*\(/gi,
73
+ /(?:const|let|var)\s+(\w*login\w*|authenticate\w*|signin\w*)\s*=\s*(?:async\s+)?\(/gi,
74
+ /@Post\s*\(\s*['"`]\/?(login|signin|authenticate)/gi,
75
+ /router\.\w+\s*\(\s*['"`]\/?(login|signin|authenticate)/gi,
76
+ ];
77
+
78
+ let currentFunction = null;
79
+ let braceCount = 0;
80
+
81
+ lines.forEach((line, index) => {
82
+ // Check if this line starts a login function
83
+ if (!currentFunction) {
84
+ loginPatterns.forEach(pattern => {
85
+ const match = line.match(pattern);
86
+ if (match) {
87
+ currentFunction = {
88
+ startLine: index + 1,
89
+ functionName: match[1] || 'anonymous',
90
+ functionContent: '',
91
+ };
92
+ braceCount = 0;
93
+ }
94
+ });
95
+ }
96
+
97
+ if (currentFunction) {
98
+ currentFunction.functionContent += line + '\n';
99
+
100
+ // Count braces to find function end
101
+ braceCount += (line.match(/\{/g) || []).length;
102
+ braceCount -= (line.match(/\}/g) || []).length;
103
+
104
+ if (braceCount === 0 && currentFunction.functionContent.includes('{')) {
105
+ currentFunction.endLine = index + 1;
106
+ functions.push(currentFunction);
107
+ currentFunction = null;
108
+ }
109
+ }
110
+ });
111
+
112
+ return functions;
113
+ }
114
+
115
+ hasSessionRegeneration(functionContent) {
116
+ // Patterns that indicate session regeneration
117
+ const regenerationPatterns = [
118
+ /session\.regenerate\s*\(/,
119
+ /req\.session\.regenerate\s*\(/,
120
+ /request\.session\.regenerate\s*\(/,
121
+ /sessionStore\.regenerate\s*\(/,
122
+
123
+ // Express-session
124
+ /session\.destroy.*session\.save/s,
125
+ /req\.session\.destroy.*new\s+session/s,
126
+
127
+ // JWT token creation (new token after login)
128
+ /jwt\.sign\s*\(/,
129
+ /generateToken\s*\(/,
130
+ /createToken\s*\(/,
131
+ /signToken\s*\(/,
132
+
133
+ // PHP session regeneration
134
+ /session_regenerate_id\s*\(/,
135
+ /session_destroy.*session_start/s,
136
+
137
+ // Python Flask/Django
138
+ /session\.regenerate\s*\(/,
139
+ /session\.clear\s*\(.*session\[/s,
140
+ /login_user.*renew=True/,
141
+
142
+ // Passport.js (implicitly regenerates)
143
+ /passport\.authenticate/,
144
+ /req\.login\s*\(/,
145
+ ];
146
+
147
+ return regenerationPatterns.some(pattern => pattern.test(functionContent));
148
+ }
149
+
150
+ cleanup() {}
151
+ }
152
+
153
+ module.exports = S040Analyzer;
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "S040",
3
+ "name": "Session Fixation Protection - Regenerate Session on Login",
4
+ "category": "security",
5
+ "description": "S040 - Prevent Session Fixation attacks by regenerating session tokens after successful authentication.",
6
+ "severity": "error",
7
+ "enabled": true,
8
+ "patterns": {
9
+ "include": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx", "**/*.php", "**/*.py"],
10
+ "exclude": [
11
+ "**/*.test.*",
12
+ "**/*.spec.*",
13
+ "__tests__/**",
14
+ "__mocks__/**",
15
+ "**/node_modules/**",
16
+ "**/dist/**",
17
+ "**/build/**"
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,83 @@
1
+ ## Rule C042
2
+ **Title:** Require re-authentication for long-lived sessions or sensitive actions
3
+
4
+ ---
5
+
6
+ ### Objective
7
+ Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.
8
+
9
+ ### Details
10
+ - **Persistent Login or "Remember Me":**
11
+ - Require re-login after X hours (e.g., 12h, 24h)
12
+ - Re-authenticate after inactivity (e.g., 30 mins)
13
+ - Require password or 2FA for sensitive actions (password change, payments)
14
+ - **For JWT:**
15
+ - Use short-lived tokens with secure refresh logic
16
+ - **Best Practice:**
17
+ - Do not flag as a violation if re-authentication logic exists in downstream service/common methods even when the immediate controller/service does not show it directly.
18
+
19
+ ### Applies to
20
+ - All programming languages
21
+ - Critical for backend/auth REST API development
22
+
23
+ ### Detection
24
+ - Detect session configs with excessive lifetime (e.g., session maxAge > 24h, JWT expiresIn > 1h for access tokens)
25
+ - Detect missing re-authentication check on sensitive actions (by function name or endpoint path)
26
+ - Should NOT flag data objects (e.g., session DB records with expiresAt)
27
+ - Should follow method call chains to detect delegated re-authentication (service/deep method etc.)
28
+
29
+ ### Tools/Validation
30
+ - Manual code review
31
+ - Static analysis (for JWT expiry, session policy)
32
+ - Security functional tests
33
+ - SonarQube (custom rule integration)
34
+
35
+ ---
36
+
37
+ ### Example: Clean vs Violation
38
+
39
+ #### Clean (no violation)
40
+ ```typescript
41
+ async changePassword(userId: string, currentPassword: string, newPassword: string) {
42
+ // Re-authentication logic via password verification
43
+ const user = await this.userRepository.findById(userId);
44
+ if (!await bcrypt.compare(currentPassword, user.passwordHash)) {
45
+ throw new UnauthorizedException();
46
+ }
47
+ // ...
48
+ }
49
+ ```
50
+
51
+ #### Violation (should be flagged)
52
+ ```typescript
53
+ async changePassword(userId: string, newPassword: string) {
54
+ // No password verification
55
+ await this.userRepository.updatePassword(userId, newPassword);
56
+ }
57
+ ```
58
+
59
+ #### Violation (configuration)
60
+ ```js
61
+ app.use(session({ cookie: { maxAge: 90 * 24 * 60 * 60 * 1000 } })); // 90 days
62
+ ```
63
+
64
+ ---
65
+
66
+ ### Resources
67
+ - [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
68
+ - [JWT Best Practices](https://auth0.com/blog/jwt-best-practices/)
69
+
70
+ ### Severity
71
+ Medium
72
+
73
+ ### Status
74
+ Activated
75
+
76
+ ### Version
77
+ 1.0
78
+
79
+ ---
80
+
81
+ ### Labels
82
+ - Security Custom Rules
83
+ - documentation
@@ -0,0 +1,153 @@
1
+ /**
2
+ * S042 Require re-authentication for long-lived sessions or sensitive actions
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Purpose: Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.
6
+ * Command: node cli.js --rule=S042 --input=examples/rule-test-fixtures/rules/S042_require_re_authentication_for_long_lived --engine=heuristic --verbose
7
+ */
8
+
9
+ const S042SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
10
+
11
+ class S042Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S042] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S042] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S042";
23
+ this.ruleName = "Require re-authentication for long-lived sessions or sensitive actions";
24
+ this.description =
25
+ "Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.";
26
+ this.semanticEngine = options.semanticEngine || null;
27
+ this.verbose = options.verbose || false;
28
+
29
+ this.config = {
30
+ useSymbolBased: true,
31
+ fallbackToRegex: false,
32
+ regexBasedOnly: false,
33
+ fallbackToSymbol: true, // Allow symbol analysis even without semantic engine
34
+ };
35
+
36
+ try {
37
+ this.symbolAnalyzer = new S042SymbolBasedAnalyzer(this.semanticEngine);
38
+ if (process.env.SUNLINT_DEBUG)
39
+ console.log(`🔧 [S042] Symbol analyzer created successfully`);
40
+ } catch (error) {
41
+ console.error(`🔧 [S042] Error creating symbol analyzer:`, error);
42
+ }
43
+ }
44
+
45
+ async initialize(semanticEngine = null) {
46
+ if (semanticEngine) {
47
+ this.semanticEngine = semanticEngine;
48
+ }
49
+ this.verbose = semanticEngine?.verbose || false;
50
+
51
+ // Initialize both analyzers
52
+ await this.symbolAnalyzer.initialize(semanticEngine);
53
+
54
+ // Ensure verbose flag is propagated
55
+ this.symbolAnalyzer.verbose = this.verbose;
56
+
57
+ if (this.verbose) {
58
+ console.log(`🔧 [S042 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
59
+ }
60
+ }
61
+
62
+ analyzeSingle(filePath, options = {}) {
63
+ if (process.env.SUNLINT_DEBUG)
64
+ console.log(`🔍 [S042] analyzeSingle() called for: ${filePath}`);
65
+ return this.analyze([filePath], "typescript", options);
66
+ }
67
+
68
+ async analyze(files, language, options = {}) {
69
+ if (process.env.SUNLINT_DEBUG) {
70
+ console.log(`🔧 [S042] analyze() method called with ${files.length} files, language: ${language}`);
71
+ }
72
+
73
+ const violations = [];
74
+
75
+ for (const filePath of files) {
76
+ try {
77
+ if (process.env.SUNLINT_DEBUG) {
78
+ console.log(`🔧 [S042] Processing file: ${filePath}`);
79
+ }
80
+
81
+ const fileViolations = await this.analyzeFile(filePath, options);
82
+ violations.push(...fileViolations);
83
+
84
+ if (process.env.SUNLINT_DEBUG) {
85
+ console.log(`🔧 [S042] File ${filePath}: Found ${fileViolations.length} violations`);
86
+ }
87
+ } catch (error) {
88
+ console.warn(`❌ [S042] Analysis failed for ${filePath}:`, error.message);
89
+ }
90
+ }
91
+
92
+ if (process.env.SUNLINT_DEBUG) {
93
+ console.log(`🔧 [S042] Total violations found: ${violations.length}`);
94
+ }
95
+
96
+ return violations;
97
+ }
98
+
99
+ async analyzeFile(filePath, options = {}) {
100
+ if (process.env.SUNLINT_DEBUG) {
101
+ console.log(`🔧 [S042] analyzeFile() called for: ${filePath}`);
102
+ }
103
+
104
+ // 1. Try Symbol-based analysis first (primary)
105
+ if (this.config.useSymbolBased &&
106
+ this.semanticEngine?.project &&
107
+ this.semanticEngine?.initialized) {
108
+ try {
109
+ if (process.env.SUNLINT_DEBUG) {
110
+ console.log(`🔧 [S042] Trying symbol-based analysis...`);
111
+ }
112
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
113
+ if (sourceFile) {
114
+ if (process.env.SUNLINT_DEBUG) {
115
+ console.log(`🔧 [S042] Source file found, analyzing with symbol-based...`);
116
+ }
117
+ const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
118
+
119
+ // Mark violations with analysis strategy
120
+ violations.forEach(v => v.analysisStrategy = 'symbol-based');
121
+
122
+ if (process.env.SUNLINT_DEBUG) {
123
+ console.log(`✅ [S042] Symbol-based analysis: ${violations.length} violations`);
124
+ }
125
+ return violations; // Return even if 0 violations - symbol analysis completed successfully
126
+ } else {
127
+ if (process.env.SUNLINT_DEBUG) {
128
+ console.log(`⚠️ [S042] Source file not found in project`);
129
+ }
130
+ }
131
+ } catch (error) {
132
+ console.warn(`⚠️ [S042] Symbol analysis failed: ${error.message}`);
133
+ // Continue to fallback
134
+ }
135
+ } else {
136
+ if (process.env.SUNLINT_DEBUG) {
137
+ console.log(`🔄 [S042] Symbol analysis conditions check:`);
138
+ console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
139
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
140
+ console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
141
+ console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
142
+ console.log(`🔄 [S042] Symbol analysis unavailable, using regex fallback`);
143
+ }
144
+ }
145
+
146
+ if (options?.verbose) {
147
+ console.log(`🔧 [S042] No analysis methods succeeded, returning empty`);
148
+ }
149
+ return [];
150
+ }
151
+ }
152
+
153
+ module.exports = S042Analyzer;
@@ -0,0 +1,41 @@
1
+ {
2
+ "id": "S042",
3
+ "name": "Require re-authentication for long-lived sessions or sensitive actions",
4
+ "category": "security",
5
+ "description": "S042 - Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.",
6
+ "severity": "error",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"],
15
+ "exclude": [
16
+ "**/*.test.js",
17
+ "**/*.test.ts",
18
+ "**/*.spec.js",
19
+ "**/*.spec.ts",
20
+ "**/node_modules/**",
21
+ "**/dist/**",
22
+ "**/build/**"
23
+ ]
24
+ },
25
+ "analysis": {
26
+ "approach": "symbol-based-primary",
27
+ "fallback": "regex-based",
28
+ "depth": 1,
29
+ "timeout": 4000
30
+ },
31
+ "validation": {
32
+ "httpIndicators": [
33
+ "http://",
34
+ "require('http')",
35
+ "require(\"http\")",
36
+ "http.createServer"
37
+ ],
38
+ "httpsModules": ["https", "tls"],
39
+ "frameworks": ["express", "nextjs", "nuxtjs", "nestjs"]
40
+ }
41
+ }