@sun-asterisk/sunlint 1.3.0 → 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 (73) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/CONTRIBUTING.md +1179 -54
  3. package/README.md +3 -4
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/large-project.json +143 -0
  7. package/config/presets/all.json +0 -1
  8. package/config/release.json +70 -0
  9. package/config/rule-analysis-strategies.js +23 -4
  10. package/config/rules/S027-categories.json +122 -0
  11. package/config/rules/enhanced-rules-registry.json +136 -75
  12. package/config/rules/rules-registry-generated.json +2 -2
  13. package/config/rules/rules-registry.json +13 -1
  14. package/core/cli-action-handler.js +24 -30
  15. package/core/cli-program.js +11 -3
  16. package/core/config-merger.js +29 -2
  17. package/core/enhanced-rules-registry.js +3 -3
  18. package/core/semantic-engine.js +117 -19
  19. package/core/unified-rule-registry.js +1 -1
  20. package/docs/COMMAND-EXAMPLES.md +134 -0
  21. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  22. package/engines/heuristic-engine.js +71 -13
  23. package/integrations/eslint/plugin/index.js +0 -2
  24. package/origin-rules/common-en.md +8 -8
  25. package/package.json +1 -1
  26. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  27. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  28. package/rules/common/C033_separate_service_repository/README.md +78 -0
  29. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  30. package/rules/common/C033_separate_service_repository/config.json +50 -0
  31. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  32. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  33. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  34. package/rules/common/C035_error_logging_context/analyzer.js +230 -0
  35. package/rules/common/C035_error_logging_context/config.json +54 -0
  36. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  37. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  38. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  39. package/rules/common/C040_centralized_validation/config.json +46 -0
  40. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  41. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  42. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  43. package/rules/common/C076_explicit_function_types/README.md +30 -0
  44. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  45. package/rules/common/C076_explicit_function_types/config.json +15 -0
  46. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  47. package/rules/index.js +1 -0
  48. package/rules/parser/rule-parser.js +13 -2
  49. package/rules/security/S005_no_origin_auth/README.md +226 -0
  50. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  51. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  52. package/rules/security/S005_no_origin_auth/config.json +85 -0
  53. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  54. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  55. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  56. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  57. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  58. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  59. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  60. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  61. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  62. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  63. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  64. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  65. package/scripts/prepare-release.sh +1 -1
  66. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  67. package/docs/FUTURE_PACKAGES.md +0 -83
  68. package/docs/HEURISTIC_VS_AI.md +0 -113
  69. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  70. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  71. package/docs/RELEASE_GUIDE.md +0 -230
  72. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  73. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
@@ -0,0 +1,153 @@
1
+ {
2
+ "S027": {
3
+ "categories": [
4
+ {
5
+ "name": "AWS Credentials",
6
+ "severity": "critical",
7
+ "description": "AWS access keys, secret keys, and session tokens",
8
+ "patterns": [
9
+ "AKIA[0-9A-Z]{16}",
10
+ "(aws[-_]?)?(secret[-_]?access[-_]?key|access[-_]?key[-_]?id|awssecretaccesskey|awsaccesskeyid)\\s*[=:]\\s*[\"']?[A-Za-z0-9\\/+=]{20,40}[\"']?",
11
+ "(aws[-_]?)?session[-_]?token\\s*[=:]\\s*[\"']?[A-Za-z0-9\\/+=]{100,}[\"']?"
12
+ ],
13
+ "exclude_patterns": [
14
+ "(test|mock|fake|example|demo)[-_]?aws",
15
+ "AWS_REGION|AWS_DEFAULT_REGION"
16
+ ]
17
+ },
18
+ {
19
+ "name": "JWT & Authentication Tokens",
20
+ "severity": "critical",
21
+ "description": "JWT tokens and authentication credentials",
22
+ "patterns": [
23
+ "eyJ[A-Za-z0-9\\-_=]+\\.[A-Za-z0-9\\-_=]+\\.?[A-Za-z0-9\\-_.+/=]*",
24
+ "(jwt|bearer|auth|authtoken|jwttoken)[-_]?(token|secret)?\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{20,}[\"']?",
25
+ "authorization\\s*[=:]\\s*[\"']?(bearer|basic)\\s+[a-zA-Z0-9\\-_=]{10,}[\"']?"
26
+ ]
27
+ },
28
+ {
29
+ "name": "API Keys & Secrets",
30
+ "severity": "high",
31
+ "description": "Generic API keys and secret tokens",
32
+ "patterns": [
33
+ "(api[-_]?key|apikey|secret[-_]?key|secretkey|access[-_]?token|accesstoken)\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{16,}[\"']?",
34
+ "(client[-_]?secret|clientsecret|app[-_]?secret|appsecret)\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{20,}[\"']?",
35
+ "(private[-_]?key|privatekey|encryption[-_]?key|encryptionkey)\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{20,}[\"']?",
36
+ "(password|secret)\\s*[=:]\\s*[\"'][a-zA-Z0-9\\-_=]{6,}[\"']"
37
+ ],
38
+ "exclude_patterns": [
39
+ "(display|row|sort|primary|foreign)[-_]?key",
40
+ "key(value|path|name|code|id|index)",
41
+ "^key$",
42
+ "(test|mock|demo|example).*password",
43
+ "password.*(test|mock|demo|example|123|dummy)",
44
+ "wrongpassword|correctpassword|testpassword"
45
+ ]
46
+ },
47
+ {
48
+ "name": "Database Credentials",
49
+ "severity": "high",
50
+ "description": "Database connection strings and passwords",
51
+ "patterns": [
52
+ "(mongodb|mysql|postgres|redis):\\/\\/[^\\/\\s'\"]+:[^\\/\\s'\"]+@[^\\/\\s'\"]+",
53
+ "(db|database|dbpassword|databasepassword)[-_]?(password|pass|pwd|secret)?\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{6,}[\"']?",
54
+ "connection[-_]?string\\s*[=:]\\s*[\"']?[^\"'\\s]{20,}[\"']?"
55
+ ]
56
+ },
57
+ {
58
+ "name": "Third-party Service Keys",
59
+ "severity": "high",
60
+ "description": "GitHub, Slack, Stripe and other service tokens",
61
+ "patterns": [
62
+ "gh[pousr]_[A-Za-z0-9_]{36}",
63
+ "xox[baprs]-[A-Za-z0-9-]+",
64
+ "sk_live_[A-Za-z0-9]{24,}",
65
+ "(github|slack|stripe|paypal)[-_]?(token|key|secret)[\\s:=]+[\"']?[a-zA-Z0-9\\-_=]{16,}[\"']?"
66
+ ]
67
+ },
68
+ {
69
+ "name": "Suspicious Variable Names",
70
+ "severity": "medium",
71
+ "description": "Variables with sensitive naming patterns",
72
+ "patterns": [
73
+ "(client|app|service)[-_]?(id|key|token|secret)[\"']?\\s*[:=]\\s*[\"'][A-Za-z0-9\\-_=]{12,}[\"']?",
74
+ "(oauth|openid)[-_]?(client[-_]?id|secret)[\\s:=]+[\"']?[a-zA-Z0-9\\-_=]{10,}[\"']?"
75
+ ],
76
+ "exclude_patterns": [
77
+ "(send|verify|update|register|reset).*password",
78
+ "password.*(reset|verify|update|first|time)"
79
+ ]
80
+ },
81
+ {
82
+ "name": "Base64 Encoded Secrets",
83
+ "severity": "medium",
84
+ "description": "Potentially encoded sensitive data",
85
+ "patterns": [
86
+ "(?:^|[\\s=:'\"])([A-Za-z0-9+\\/]{64,}={0,2})(?:[\\s'\";}]|$)"
87
+ ],
88
+ "exclude_patterns": [
89
+ "^[a-zA-Z0-9+\\/]*$",
90
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
91
+ "(test|demo|example|sample|mock)",
92
+ "^\\/[a-zA-Z0-9\\/\\-_]+$",
93
+ "^[a-zA-Z0-9\\/\\-_\\.]+$",
94
+ "[a-zA-Z]+[a-zA-Z\\/\\-_]{20,}",
95
+ "[a-zA-Z]+Slice\\/",
96
+ "[a-zA-Z]+Company\\/",
97
+ "[a-zA-Z]+Management\\/",
98
+ "[a-zA-Z]+Component",
99
+ "[a-zA-Z]+Setting",
100
+ "[a-zA-Z]+Match",
101
+ "Selector$",
102
+ "Controller$",
103
+ "Service$",
104
+ "Api$",
105
+ "slice\\/",
106
+ "Component\\/",
107
+ "management\\/",
108
+ "company\\/",
109
+ "import.*from",
110
+ "require\\("
111
+ ]
112
+ },
113
+ {
114
+ "name": "Environment Variables",
115
+ "severity": "low",
116
+ "description": "Public environment variables that might leak info",
117
+ "patterns": [
118
+ "NEXT_PUBLIC_[A-Z0-9_]+[\\s:=]+[\"'][^\"']+[\"']",
119
+ "react_app_[A-Z0-9_]+[\\s:=]+[\"'][^\"']+[\"']"
120
+ ],
121
+ "exclude_patterns": [
122
+ "NODE_ENV|ENV|ENVIRONMENT|MODE|DEBUG"
123
+ ]
124
+ },
125
+ {
126
+ "name": "File Path Leaks",
127
+ "severity": "low",
128
+ "description": "Sensitive file patterns",
129
+ "patterns": [
130
+ "^\\s*['\"]?\\.env(\\.[a-zA-Z0-9_]+)?['\"]?\\s*$",
131
+ "^\\s*['\"]?(secrets?|credentials?|private[-_]?keys?)\\.(json|ya?ml|ts|js)['\"]?\\s*$",
132
+ "^\\s*['\"]?(id_rsa|id_dsa|\\.pem|\\.p12|\\.pfx)['\"]?\\s*$"
133
+ ],
134
+ "exclude_patterns": [
135
+ "process\\.env\\.",
136
+ "import.*from",
137
+ "require\\(",
138
+ "NODE_ENV|ENVIRONMENT|MODE|DEBUG"
139
+ ]
140
+ }
141
+ ],
142
+ "global_exclude_patterns": [
143
+ "(test|mock|fake|dummy|placeholder)\\.(js|ts|jsx|tsx)$",
144
+ "\\.(test|spec|mock)\\.",
145
+ "__tests__|\\/tests?\\/|\\/spec\\/",
146
+ "process\\.env\\.",
147
+ "import.*from.*['\"]",
148
+ "require\\(['\"]"
149
+ ],
150
+ "min_length": 8,
151
+ "max_length": 1000
152
+ }
153
+ }
@@ -0,0 +1,250 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class S027CategorizedAnalyzer {
5
+ constructor() {
6
+ this.ruleId = 'S027';
7
+ this.ruleName = 'No Hardcoded Secrets (Categorized)';
8
+ this.description = 'Phát hiện thông tin bảo mật theo categories với độ ưu tiên khác nhau';
9
+
10
+ // Load categories config
11
+ this.config = this.loadConfig();
12
+ this.categories = this.config.categories;
13
+ this.globalExcludePatterns = this.config.global_exclude_patterns.map(p => new RegExp(p, 'i'));
14
+ this.minLength = this.config.min_length || 8;
15
+ this.maxLength = this.config.max_length || 1000;
16
+
17
+ // Compile patterns for performance
18
+ this.compilePatterns();
19
+ }
20
+
21
+ loadConfig() {
22
+ const configPath = path.join(__dirname, 'categories.json');
23
+ try {
24
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
25
+ return config.S027;
26
+ } catch (error) {
27
+ console.error('Failed to load S027 categories config:', error.message);
28
+ return { categories: [], global_exclude_patterns: [] };
29
+ }
30
+ }
31
+
32
+ compilePatterns() {
33
+ this.categories.forEach(category => {
34
+ category.compiledPatterns = category.patterns.map(p => ({
35
+ regex: new RegExp(p, 'gm'),
36
+ original: p
37
+ }));
38
+
39
+ if (category.exclude_patterns) {
40
+ category.compiledExcludePatterns = category.exclude_patterns.map(p => new RegExp(p, 'i'));
41
+ }
42
+ });
43
+ }
44
+
45
+ async analyze(files, language, options = {}) {
46
+ const violations = [];
47
+ this.currentFilePath = '';
48
+
49
+ for (const filePath of files) {
50
+ // Skip build/dist/node_modules
51
+ if (this.shouldSkipFile(filePath)) {
52
+ continue;
53
+ }
54
+
55
+ this.currentFilePath = filePath;
56
+
57
+ try {
58
+ const content = fs.readFileSync(filePath, 'utf8');
59
+ const fileViolations = this.analyzeFile(content, filePath);
60
+ violations.push(...fileViolations);
61
+ } catch (error) {
62
+ if (options.verbose) {
63
+ console.error(`Error analyzing ${filePath}:`, error.message);
64
+ }
65
+ }
66
+ }
67
+
68
+ return violations;
69
+ }
70
+
71
+ shouldSkipFile(filePath) {
72
+ const skipPatterns = [
73
+ 'build/', 'dist/', 'node_modules/', '.git/',
74
+ 'coverage/', '.next/', '.cache/', 'tmp/',
75
+ '.lock', '.log', '.min.js', '.bundle.js'
76
+ ];
77
+
78
+ return skipPatterns.some(pattern => filePath.includes(pattern));
79
+ }
80
+
81
+ analyzeFile(content, filePath) {
82
+ const violations = [];
83
+ // Handle different line endings (Windows \r\n, Unix \n, Mac \r)
84
+ const lines = content.split(/\r?\n/);
85
+
86
+ // Check if this is a test file for context
87
+ const isTestFile = this.isTestFile(filePath);
88
+
89
+ lines.forEach((line, index) => {
90
+ const lineNumber = index + 1;
91
+ const trimmedLine = line.trim();
92
+
93
+ // Skip comments and imports
94
+ if (this.isCommentOrImport(trimmedLine)) {
95
+ return;
96
+ }
97
+
98
+ // Check global exclude patterns first
99
+ if (this.matchesGlobalExcludes(line)) {
100
+ return;
101
+ }
102
+
103
+ // Check each category
104
+ this.categories.forEach(category => {
105
+ const categoryViolations = this.checkCategory(
106
+ category, line, lineNumber, filePath, isTestFile
107
+ );
108
+ violations.push(...categoryViolations);
109
+ });
110
+ });
111
+
112
+ return violations;
113
+ }
114
+
115
+ isTestFile(filePath) {
116
+ const testPatterns = [
117
+ /\.(test|spec)\./i,
118
+ /__tests__/i,
119
+ /\/tests?\//i,
120
+ /\/spec\//i,
121
+ /setupTests/i,
122
+ /testSetup/i,
123
+ /test[-_]/i, // Matches test- or test_
124
+ /^.*\/test[^\/]*\.js$/i // Matches files starting with test
125
+ ];
126
+
127
+ return testPatterns.some(pattern => pattern.test(filePath));
128
+ }
129
+
130
+ isCommentOrImport(line) {
131
+ return line.startsWith('//') || line.startsWith('/*') ||
132
+ line.startsWith('import') || line.startsWith('export') ||
133
+ line.startsWith('*') || line.startsWith('<');
134
+ }
135
+
136
+ matchesGlobalExcludes(line) {
137
+ return this.globalExcludePatterns.some(pattern => pattern.test(line));
138
+ }
139
+
140
+ checkCategory(category, line, lineNumber, filePath, isTestFile) {
141
+ const violations = [];
142
+
143
+ category.compiledPatterns.forEach(({ regex, original }) => {
144
+ let match;
145
+
146
+ // Reset regex lastIndex for global patterns
147
+ regex.lastIndex = 0;
148
+
149
+ while ((match = regex.exec(line)) !== null) {
150
+ const matchedText = match[0];
151
+ const column = match.index + 1;
152
+
153
+ // Check length constraints
154
+ if (matchedText.length < this.minLength || matchedText.length > this.maxLength) {
155
+ continue;
156
+ }
157
+
158
+ // Check category-specific excludes
159
+ if (category.compiledExcludePatterns &&
160
+ category.compiledExcludePatterns.some(pattern => pattern.test(matchedText))) {
161
+ continue;
162
+ }
163
+
164
+ // Be more lenient in test files for lower severity categories
165
+ // But still report critical/high severity issues even in test files
166
+ if (isTestFile && category.severity === 'low') {
167
+ continue;
168
+ }
169
+
170
+ violations.push({
171
+ file: filePath,
172
+ line: lineNumber,
173
+ column: column,
174
+ message: `[${category.name}] Potential ${category.severity} security risk: '${matchedText}'. ${category.description}`,
175
+ severity: this.mapSeverity(category.severity),
176
+ ruleId: this.ruleId,
177
+ category: category.name,
178
+ categoryDescription: category.description,
179
+ matchedPattern: original,
180
+ matchedText: matchedText
181
+ });
182
+ }
183
+ });
184
+
185
+ return violations;
186
+ }
187
+
188
+ mapSeverity(categorySeverity) {
189
+ const severityMap = {
190
+ 'critical': 'error',
191
+ 'high': 'warning',
192
+ 'medium': 'warning',
193
+ 'low': 'info'
194
+ };
195
+
196
+ return severityMap[categorySeverity] || 'warning';
197
+ }
198
+
199
+ // Method for getting category statistics
200
+ getCategoryStats(violations) {
201
+ const stats = {};
202
+
203
+ violations.forEach(violation => {
204
+ const category = violation.category;
205
+ if (!stats[category]) {
206
+ stats[category] = {
207
+ count: 0,
208
+ severity: violation.severity,
209
+ files: new Set()
210
+ };
211
+ }
212
+ stats[category].count++;
213
+ stats[category].files.add(violation.file);
214
+ });
215
+
216
+ // Convert Set to array for JSON serialization
217
+ Object.keys(stats).forEach(category => {
218
+ stats[category].files = Array.from(stats[category].files);
219
+ stats[category].fileCount = stats[category].files.length;
220
+ });
221
+
222
+ return stats;
223
+ }
224
+
225
+ // Method for filtering by category
226
+ filterByCategory(violations, categoryNames) {
227
+ if (!categoryNames || categoryNames.length === 0) {
228
+ return violations;
229
+ }
230
+
231
+ return violations.filter(violation =>
232
+ categoryNames.includes(violation.category)
233
+ );
234
+ }
235
+
236
+ // Method for filtering by severity
237
+ filterBySeverity(violations, minSeverity = 'info') {
238
+ const severityOrder = ['info', 'warning', 'error'];
239
+ const minIndex = severityOrder.indexOf(minSeverity);
240
+
241
+ if (minIndex === -1) return violations;
242
+
243
+ return violations.filter(violation => {
244
+ const violationIndex = severityOrder.indexOf(violation.severity);
245
+ return violationIndex >= minIndex;
246
+ });
247
+ }
248
+ }
249
+
250
+ module.exports = S027CategorizedAnalyzer;
@@ -135,7 +135,7 @@ sunlint --rule=C006 --input=src --format=summary
135
135
 
136
136
  # TypeScript Analysis
137
137
  --typescript # Enable TypeScript analysis
138
- --typescript-engine <type> # Engine: eslint, sunlint, hybrid
138
+ --typescript-engine <type> # Engine: eslint, heuristic, hybrid
139
139
 
140
140
  # Output Control
141
141
  --format <format> # Output: eslint, json, summary, table