@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.3.26",
3
+ "version": "1.3.28",
4
4
  "description": "☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -12,31 +12,83 @@ const { CommentDetector } = require('../../utils/rule-helpers');
12
12
 
13
13
  class C003NoVagueAbbreviations {
14
14
  constructor(options = {}) {
15
+ // Organize abbreviations by framework/category for better maintainability
16
+ const abbreviationsByCategory = {
17
+ // Core technical & web standards
18
+ core: [
19
+ 'id', 'url', 'uri', 'api', 'ui', 'db', 'fs', 'os', 'io', 'ai', 'ml',
20
+ 'dom', 'xhr', 'spa', 'pwa', 'seo', 'cdn', 'ssl', 'tls'
21
+ ],
22
+
23
+ // File formats & protocols
24
+ formats: [
25
+ 'json', 'xml', 'html', 'css', 'pdf', 'csv', 'tsv',
26
+ 'png', 'jpg', 'gif', 'svg', 'mp4', 'mp3', 'zip', 'tar'
27
+ ],
28
+
29
+ // Network protocols
30
+ protocols: [
31
+ 'http', 'https', 'ftp', 'smtp', 'tcp', 'udp', 'sql'
32
+ ],
33
+
34
+ // Programming languages
35
+ languages: [
36
+ 'js', 'ts', 'py', 'rb', 'go', 'rs', 'kt', 'cs', 'vb', 'sh'
37
+ ],
38
+
39
+ // Frontend frameworks & tools
40
+ frontend: [
41
+ 'jsx', 'tsx', 'vue', 'scss', 'less'
42
+ ],
43
+
44
+ // Testing abbreviations
45
+ testing: [
46
+ 'qa', 'ci', 'cd', 'pr', 'it', 'ut', 'e2e',
47
+ // Test variants from user feedback
48
+ 'qa1', 'qa2', 'ci1', 'ci2', 'it2', 'tsx2'
49
+ ],
50
+
51
+ // Database & ORM
52
+ database: [
53
+ 'orm', 'ddl', 'dml', 'etl', 'olap', 'oltp', 'sql'
54
+ ],
55
+
56
+ // TypeORM specific
57
+ typeorm: [
58
+ 'qb' // QueryBuilder - common in TypeORM
59
+ ],
60
+
61
+ // Business & project management
62
+ business: [
63
+ 'kpi', 'roi', 'sla', 'poc', 'mvp', 'b2b', 'b2c', 'crm', 'erp'
64
+ ],
65
+
66
+ // Common development terms
67
+ dev: [
68
+ 'config', 'env', 'app', 'btn', 'img', 'src', 'dest',
69
+ 'req', 'res', 'ctx', 'auth', 'log', 'err', 'msg', 'key'
70
+ ],
71
+
72
+ // Math & measurements
73
+ math: [
74
+ 'min', 'max', 'len', 'num', 'str', 'ms'
75
+ ],
76
+
77
+ // Common context terms
78
+ context: [
79
+ 'value', 'result', 'response', 'request', 'data',
80
+ 'item', 'element', 'object', 'async', 'length'
81
+ ]
82
+ };
83
+
84
+ // Combine all abbreviations from categories
85
+ const allAbbreviations = Object.values(abbreviationsByCategory).flat();
86
+
15
87
  this.options = {
16
88
  allowedSingleChar: new Set(options.allowedSingleChar || [
17
89
  'i', 'j', 'k', 'x', 'y', 'z', 'n', 'm', 't', 'v', 'r', 'e', 'p', 'w', 'h'
18
90
  ]),
19
- allowedAbbreviations: new Set(options.allowedAbbreviations || [
20
- // Technical abbreviations from user feedback
21
- 'id', 'url', 'uri', 'api', 'ui', 'db', 'fs', 'os', 'io', 'ai', 'ml', 'qa', 'ci', 'cd', 'pr',
22
- 'jwt', 'uuid', 'json', 'xml', 'html', 'css', 'sql', 'http', 'https', 'ftp', 'smtp', 'tcp', 'udp',
23
- 'pdf', 'csv', 'tsv', 'png', 'jpg', 'gif', 'svg', 'mp4', 'mp3', 'zip', 'tar',
24
- 'js', 'ts', 'py', 'rb', 'go', 'rs', 'kt', 'cs', 'vb', 'sh',
25
- 'dom', 'xhr', 'spa', 'pwa', 'seo', 'cdn', 'ssl', 'tls',
26
- 'orm', 'ddl', 'dml', 'etl', 'olap', 'oltp',
27
- 'kpi', 'roi', 'sla', 'poc', 'mvp', 'b2b', 'b2c', 'crm', 'erp',
28
- 'jsx', 'tsx', 'vue', 'scss', 'less',
29
- 'it', 'ut', 'e2e',
30
- // Common development terms
31
- 'config', 'env', 'app', 'btn', 'img', 'src', 'dest', 'req', 'res', 'ctx',
32
- 'min', 'max', 'len', 'num', 'str', 'auth', 'log', 'err', 'msg', 'key',
33
- // Add the variants from user feedback cases
34
- 'qa1', 'ci1', 'tsx2', 'it2', 'qa2', 'ci2',
35
- // Common test/function context terms
36
- 'value', 'result', 'response', 'request', 'data', 'item', 'element', 'object',
37
- // Common programming terms
38
- 'async', 'length', 'ms'
39
- ]),
91
+ allowedAbbreviations: new Set(options.allowedAbbreviations || allAbbreviations),
40
92
  minLength: options.minLength || 2,
41
93
  strictMode: options.strictMode || false
42
94
  };
@@ -72,6 +72,158 @@ class C017SymbolBasedAnalyzer {
72
72
  }
73
73
  }
74
74
 
75
+ /**
76
+ * Check if the file is a Data Transfer Object (DTO)
77
+ * Uses multiple detection strategies:
78
+ * 1. Decorators (@DTO, @DataTransferObject)
79
+ * 2. Inheritance (extends BaseDTO, extends DTO)
80
+ * 3. Interface implementation (implements IDTO)
81
+ * 4. JSDoc annotations (@dto, @data-transfer-object)
82
+ * 5. File location (in dto/ or dtos/ folder)
83
+ * 6. Structural pattern analysis
84
+ * 7. Filename pattern (.dto.ts, .dto.js)
85
+ * 8. Class name pattern (ending with DTO/Dto)
86
+ */
87
+ isDataTransferObject(filePath, constructor) {
88
+ try {
89
+ const classDecl = constructor.getParent();
90
+ if (!classDecl) return false;
91
+
92
+ // Strategy 1: Check decorators
93
+ const decorators = classDecl.getDecorators?.() || [];
94
+ for (const decorator of decorators) {
95
+ const decoratorName = decorator.getName();
96
+ if (decoratorName && /^(DTO|DataTransferObject|Dto)$/i.test(decoratorName)) {
97
+ return true;
98
+ }
99
+ }
100
+
101
+ // Strategy 2: Check inheritance (extends BaseDTO, DTO, etc.)
102
+ const heritage = classDecl.getExtends?.();
103
+ if (heritage) {
104
+ const baseClassName = heritage.getText();
105
+ if (/\b(Base)?(DTO|Dto|DataTransferObject)\b/.test(baseClassName)) {
106
+ return true;
107
+ }
108
+ }
109
+
110
+ // Strategy 3: Check interface implementation
111
+ const implementations = classDecl.getImplements?.() || [];
112
+ for (const impl of implementations) {
113
+ const interfaceName = impl.getText();
114
+ if (/^I?(DTO|Dto|DataTransferObject)$/i.test(interfaceName)) {
115
+ return true;
116
+ }
117
+ }
118
+
119
+ // Strategy 4: Check JSDoc tags
120
+ const jsDocs = classDecl.getJsDocs?.() || [];
121
+ for (const jsDoc of jsDocs) {
122
+ const tags = jsDoc.getTags?.() || [];
123
+ for (const tag of tags) {
124
+ const tagName = tag.getTagName();
125
+ if (tagName && /^(dto|data-transfer-object|transfer-object)$/i.test(tagName)) {
126
+ return true;
127
+ }
128
+ }
129
+ // Also check comment text
130
+ const comment = jsDoc.getDescription?.() || '';
131
+ if (/@(dto|data-transfer-object)\b/i.test(comment)) {
132
+ return true;
133
+ }
134
+ }
135
+
136
+ // Strategy 5: Check file location (dto/ or dtos/ folder)
137
+ if (filePath.match(/[\/\\](dto|dtos|transfer-objects?)[\/\\]/i)) {
138
+ return true;
139
+ }
140
+
141
+ // Strategy 6: Structural pattern analysis
142
+ if (this.hasDataTransferObjectStructure(classDecl)) {
143
+ return true;
144
+ }
145
+
146
+ // Strategy 7: Check filename pattern
147
+ if (filePath.match(/\.dto\.(ts|js)$/i)) {
148
+ return true;
149
+ }
150
+
151
+ // Strategy 8: Check class name pattern
152
+ const className = classDecl.getName?.() || '';
153
+ if (className.match(/(DTO|Dto)$/)) {
154
+ return true;
155
+ }
156
+
157
+ } catch (error) {
158
+ // Ignore errors, continue with fallback checks
159
+ }
160
+
161
+ return false;
162
+ }
163
+
164
+ /**
165
+ * Analyze class structure to detect DTO pattern
166
+ * DTOs typically have:
167
+ * - Mostly public properties (data fields)
168
+ * - Few methods (only simple transformations)
169
+ * - Constructor that accepts plain data object
170
+ * - No injected dependencies (services, repositories)
171
+ */
172
+ hasDataTransferObjectStructure(classDecl) {
173
+ try {
174
+ const properties = classDecl.getProperties();
175
+ const methods = classDecl.getMethods();
176
+ const constructor = classDecl.getConstructors()[0];
177
+
178
+ // DTO should have at least some properties
179
+ if (properties.length === 0) {
180
+ return false;
181
+ }
182
+
183
+ // Count public properties vs total properties
184
+ const publicProperties = properties.filter(prop => {
185
+ const modifiers = prop.getModifiers().map(m => m.getText());
186
+ return !modifiers.includes('private') && !modifiers.includes('protected');
187
+ });
188
+
189
+ // DTO pattern: Most properties are public (data fields)
190
+ const publicPropertyRatio = publicProperties.length / properties.length;
191
+ if (publicPropertyRatio < 0.5) {
192
+ return false; // Too many private properties for a DTO
193
+ }
194
+
195
+ // DTO pattern: Few or no methods (excluding constructor)
196
+ if (methods.length > 3) {
197
+ return false; // Too many methods for a DTO
198
+ }
199
+
200
+ // Check constructor parameters
201
+ if (constructor) {
202
+ const params = constructor.getParameters();
203
+
204
+ // DTO pattern: Usually 1-2 parameters (data object, optional config)
205
+ if (params.length > 2) {
206
+ return false;
207
+ }
208
+
209
+ // Check if parameters are dependency injections (services, repositories)
210
+ for (const param of params) {
211
+ const paramType = param.getType().getText();
212
+ // If injecting services/repositories, not a DTO
213
+ if (/(Service|Repository|Controller|Manager|Handler|Provider)/.test(paramType)) {
214
+ return false;
215
+ }
216
+ }
217
+ }
218
+
219
+ // Passed all structural checks - likely a DTO
220
+ return true;
221
+
222
+ } catch (error) {
223
+ return false;
224
+ }
225
+ }
226
+
75
227
  /**
76
228
  * Analyze a constructor for business logic violations
77
229
  */
@@ -80,10 +232,15 @@ class C017SymbolBasedAnalyzer {
80
232
  if (!body) return;
81
233
 
82
234
  const statements = body.getStatements();
235
+ const isDTO = this.isDataTransferObject(filePath, constructor);
236
+
237
+ if (verbose && isDTO) {
238
+ console.log(` 📦 [C017] DTO class detected: ${filePath} - allowing private method calls`);
239
+ }
83
240
 
84
241
  for (const statement of statements) {
85
242
  // Check for method calls (instance methods)
86
- if (this.containsMethodCall(statement, verbose)) {
243
+ if (this.containsMethodCall(statement, verbose, isDTO, constructor)) {
87
244
  const { line, column } = this.getStatementPosition(statement);
88
245
 
89
246
  violations.push({
@@ -206,10 +363,46 @@ class C017SymbolBasedAnalyzer {
206
363
  }
207
364
  }
208
365
 
366
+ /**
367
+ * Check if a method is a private method in the class
368
+ */
369
+ isPrivateMethod(methodName, constructor) {
370
+ try {
371
+ const classDecl = constructor.getParent();
372
+ if (!classDecl) return false;
373
+
374
+ // Get all methods in the class
375
+ const methods = classDecl.getMethods();
376
+
377
+ for (const method of methods) {
378
+ const name = method.getName();
379
+ if (name === methodName) {
380
+ // Check if method has private modifier
381
+ const modifiers = method.getModifiers();
382
+ for (const modifier of modifiers) {
383
+ if (modifier.getText() === 'private') {
384
+ return true;
385
+ }
386
+ }
387
+ // In TypeScript, methods starting with # are also private
388
+ if (name.startsWith('#')) {
389
+ return true;
390
+ }
391
+ break;
392
+ }
393
+ }
394
+ } catch (error) {
395
+ // Ignore errors, default to not private
396
+ }
397
+ return false;
398
+ }
399
+
209
400
  /**
210
401
  * Check if statement contains instance method calls
402
+ * @param {boolean} isDTO - If true and method is private, allow the call (DTO pattern)
403
+ * @param {object} constructor - Constructor node to check method visibility
211
404
  */
212
- containsMethodCall(statement, verbose = false) {
405
+ containsMethodCall(statement, verbose = false, isDTO = false, constructor = null) {
213
406
  // Get all call expressions in the statement
214
407
  const callExpressions = statement.getDescendantsOfKind(SyntaxKind.CallExpression);
215
408
 
@@ -234,6 +427,17 @@ class C017SymbolBasedAnalyzer {
234
427
  if (!safePatterns.includes(methodName)) {
235
428
  // Check if it's a config service or environment variable access
236
429
  if (!this.isSafeConfigAccess(callExpr)) {
430
+ // DTO Exception: Allow private method calls in DTOs for data transformation
431
+ if (isDTO && constructor) {
432
+ const isPrivate = this.isPrivateMethod(methodName, constructor);
433
+ if (isPrivate) {
434
+ if (verbose) {
435
+ console.log(` ✅ [C017] DTO private method allowed: ${callText}`);
436
+ }
437
+ continue; // Skip this violation for DTO private methods
438
+ }
439
+ }
440
+
237
441
  if (verbose) {
238
442
  console.log(` 🔧 Method call detected: ${callText}`);
239
443
  }