@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
@@ -29,11 +29,38 @@ class S013SymbolBasedAnalyzer {
29
29
  try {
30
30
  const { SyntaxKind } = require("ts-morph");
31
31
 
32
+ // Skip test files - they use http:// for mocking
33
+ if (
34
+ filePath.includes('.test.') ||
35
+ filePath.includes('.spec.') ||
36
+ filePath.includes('__tests__/') ||
37
+ filePath.includes('__mocks__/')
38
+ ) {
39
+ return violations; // Skip test files
40
+ }
41
+
32
42
  // Check for imports/requires of 'http' module
33
43
  const importDecls = sourceFile.getImportDeclarations();
34
44
  for (const imp of importDecls) {
35
45
  const moduleName = imp.getModuleSpecifierValue();
36
46
  if (moduleName === "http") {
47
+ // Check if it's only used for typing (e.g., http.IncomingMessage)
48
+ const namedImports = imp.getNamedImports();
49
+ const namespaceImport = imp.getNamespaceImport();
50
+
51
+ // Skip if it's a namespace import used only for types
52
+ if (namespaceImport) {
53
+ const importName = namespaceImport.getText();
54
+ const sourceText = sourceFile.getFullText();
55
+ // Check if only used for typing (e.g., import * as http for http.IncomingMessage type)
56
+ const usagePattern = new RegExp(`${importName}\\.(?:IncomingMessage|ServerResponse|Server)`, 'g');
57
+ const actualUsagePattern = new RegExp(`${importName}\\.(?:createServer|get|request)\\s*\\(`, 'g');
58
+
59
+ if (usagePattern.test(sourceText) && !actualUsagePattern.test(sourceText)) {
60
+ continue; // Only used for typing, not actual HTTP calls
61
+ }
62
+ }
63
+
37
64
  const startLine = imp.getStartLineNumber();
38
65
  violations.push({
39
66
  ruleId: this.ruleId,
@@ -148,6 +175,30 @@ class S013SymbolBasedAnalyzer {
148
175
  if (kind === SyntaxKind.StringLiteral) {
149
176
  const lit = a.getLiteralValue();
150
177
  if (/^http:\/\//i.test(lit)) {
178
+ // Skip if it's a string check in utility functions (e.g., startsWith('http://'))
179
+ if (lit === 'http://' || lit === 'https://') {
180
+ const callText = call.getParent().getText();
181
+ // Check if it's used in string manipulation (startsWith, replace, includes, etc.)
182
+ if (
183
+ /\.(?:startsWith|replace|includes|indexOf|match|test)\s*\(/.test(callText) ||
184
+ /case\s+.*startsWith/.test(callText)
185
+ ) {
186
+ continue; // Skip utility functions that convert http -> https
187
+ }
188
+ }
189
+
190
+ // Skip localhost URLs used as base URL for parsing (e.g., new URL(path, 'http://localhost'))
191
+ if (lit === 'http://localhost' && args.length >= 2) {
192
+ // This is likely used as base URL for URL constructor
193
+ continue;
194
+ }
195
+
196
+ // Skip Swagger .addServer() with localhost in development
197
+ const callExprText = expr.getText();
198
+ if (/\.addServer/.test(callExprText) && /localhost/.test(lit)) {
199
+ continue; // Swagger config for local development
200
+ }
201
+
151
202
  const msgPrefix = isNewExpression
152
203
  ? "Constructor"
153
204
  : "Insecure client call";
@@ -176,6 +227,16 @@ class S013SymbolBasedAnalyzer {
176
227
  ) {
177
228
  const text = a.getText();
178
229
  if (/http:\/\//i.test(text)) {
230
+ // Skip Firebase Emulator configuration (local development only)
231
+ const callExprText = expr.getText();
232
+ if (
233
+ /connect.*Emulator/i.test(callExprText) ||
234
+ (text.includes('localhost') || text.includes('EMULATOR_HOST') || text.includes('127.0.0.1'))
235
+ ) {
236
+ // Firebase/local emulator config - safe for development
237
+ continue;
238
+ }
239
+
179
240
  violations.push({
180
241
  ruleId: this.ruleId,
181
242
  message: `Insecure URL in template literal detected - use 'https://'`,
@@ -220,6 +281,11 @@ class S013SymbolBasedAnalyzer {
220
281
  column: 1,
221
282
  });
222
283
  } else if (/^http:\/\//i.test(lit)) {
284
+ // Skip localhost URLs used as base URL for URL constructor (e.g., new URL(path, 'http://localhost'))
285
+ if (lit === 'http://localhost' && args.length >= 2) {
286
+ continue; // Used as base URL for parsing
287
+ }
288
+
223
289
  violations.push({
224
290
  ruleId: this.ruleId,
225
291
  message: `Constructor with insecure URL '${lit}' detected - use 'https://'`,
@@ -266,6 +332,27 @@ class S013SymbolBasedAnalyzer {
266
332
  if (kind === SyntaxKind.StringLiteral) {
267
333
  const value = initializer.getLiteralValue();
268
334
  if (/^http:\/\//i.test(value)) {
335
+ // Skip API documentation examples
336
+ if (value === 'http://example.com' || value === 'http://localhost') {
337
+ // Check if this is inside @ApiProperty decorator
338
+ const propertyName = prop.getName && prop.getName();
339
+ const parent = prop.getParent();
340
+ const grandParent = parent && parent.getParent();
341
+
342
+ // Check for @ApiProperty({ example: 'http://...' })
343
+ if (grandParent) {
344
+ const decorators = grandParent.getDecorators && grandParent.getDecorators();
345
+ if (decorators && decorators.some(d => d.getText().includes('ApiProperty'))) {
346
+ continue; // Skip API documentation examples
347
+ }
348
+ }
349
+
350
+ // Also check if property name is 'example'
351
+ if (propertyName === 'example') {
352
+ continue; // Skip example values
353
+ }
354
+ }
355
+
269
356
  violations.push({
270
357
  ruleId: this.ruleId,
271
358
  message: `Object property contains insecure URL '${value}' - use 'https://'`,
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * S017 Main Analyzer - Always use parameterized queries
3
- * Primary: Symbol-based analysis (when available)
4
- * Fallback: Regex-based for all other cases
3
+ * Uses symbol-based analysis only (regex-based removed)
5
4
  * Command: node cli.js --rule=S017 --input=examples/rule-test-fixtures/rules/S017_use_parameterized_queries --engine=heuristic
6
5
  */
7
6
 
8
7
  const S017SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
- const S017RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
8
 
11
9
  class S017Analyzer {
12
10
  constructor(options = {}) {
@@ -26,14 +24,7 @@ class S017Analyzer {
26
24
  this.semanticEngine = options.semanticEngine || null;
27
25
  this.verbose = options.verbose || false;
28
26
 
29
- // Configuration
30
- this.config = {
31
- useSymbolBased: true, // Primary approach
32
- fallbackToRegex: true, // Secondary approach
33
- regexBasedOnly: false, // Can be set to true for pure mode
34
- };
35
-
36
- // Initialize analyzers
27
+ // Initialize symbol analyzer only
37
28
  try {
38
29
  this.symbolAnalyzer = new S017SymbolBasedAnalyzer(this.semanticEngine);
39
30
  if (process.env.SUNLINT_DEBUG) {
@@ -43,15 +34,6 @@ class S017Analyzer {
43
34
  console.error(`🔧 [S017] Error creating symbol analyzer:`, error);
44
35
  }
45
36
 
46
- try {
47
- this.regexAnalyzer = new S017RegexBasedAnalyzer(this.semanticEngine);
48
- if (process.env.SUNLINT_DEBUG) {
49
- console.log(`🔧 [S017] Regex analyzer created successfully`);
50
- }
51
- } catch (error) {
52
- console.error(`🔧 [S017] Error creating regex analyzer:`, error);
53
- }
54
-
55
37
  if (process.env.SUNLINT_DEBUG) {
56
38
  console.log(`🔧 [S017] Constructor completed`);
57
39
  }
@@ -66,25 +48,19 @@ class S017Analyzer {
66
48
  }
67
49
  this.verbose = semanticEngine?.verbose || false;
68
50
 
69
- // Initialize both analyzers
51
+ // Initialize symbol analyzer
70
52
  if (this.symbolAnalyzer) {
71
53
  await this.symbolAnalyzer.initialize?.(semanticEngine);
72
54
  }
73
- if (this.regexAnalyzer) {
74
- await this.regexAnalyzer.initialize?.(semanticEngine);
75
- }
76
55
 
77
56
  // Ensure verbose flag is propagated
78
- if (this.regexAnalyzer) {
79
- this.regexAnalyzer.verbose = this.verbose;
80
- }
81
57
  if (this.symbolAnalyzer) {
82
58
  this.symbolAnalyzer.verbose = this.verbose;
83
59
  }
84
60
 
85
61
  if (this.verbose) {
86
62
  console.log(
87
- `🔧 [S017 Hybrid] Analyzer initialized - verbose: ${this.verbose}`
63
+ `🔧 [S017] Analyzer initialized - verbose: ${this.verbose}`
88
64
  );
89
65
  }
90
66
  }
@@ -147,15 +123,11 @@ class S017Analyzer {
147
123
  // Create a Set to track unique violations and prevent duplicates
148
124
  const violationMap = new Map();
149
125
 
150
- // 1. Try Symbol-based analysis first (primary)
151
- if (
152
- this.config.useSymbolBased &&
153
- this.semanticEngine?.project &&
154
- this.semanticEngine?.initialized
155
- ) {
126
+ // Symbol-based analysis only
127
+ if (this.semanticEngine?.project && this.semanticEngine?.initialized) {
156
128
  try {
157
129
  if (process.env.SUNLINT_DEBUG) {
158
- console.log(`🔧 [S017] Trying symbol-based analysis...`);
130
+ console.log(`🔧 [S017] Running symbol-based analysis...`);
159
131
  }
160
132
  const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
161
133
  if (sourceFile) {
@@ -197,12 +169,10 @@ class S017Analyzer {
197
169
  }
198
170
  } catch (error) {
199
171
  console.warn(`⚠️ [S017] Symbol analysis failed: ${error.message}`);
200
- // Continue to fallback
201
172
  }
202
173
  } else {
203
174
  if (process.env.SUNLINT_DEBUG) {
204
175
  console.log(`🔄 [S017] Symbol analysis conditions check:`);
205
- console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
206
176
  console.log(` - semanticEngine: ${!!this.semanticEngine}`);
207
177
  console.log(
208
178
  ` - semanticEngine.project: ${!!this.semanticEngine?.project}`
@@ -210,51 +180,14 @@ class S017Analyzer {
210
180
  console.log(
211
181
  ` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`
212
182
  );
213
- console.log(
214
- `🔄 [S017] Symbol analysis unavailable, using regex fallback`
215
- );
183
+ console.log(`🔄 [S017] Symbol analysis unavailable`);
216
184
  }
217
185
  }
218
186
 
219
- // 2. Fallback to regex-based analysis (only if symbol-based failed or unavailable)
220
- if (this.config.fallbackToRegex) {
221
- try {
222
- if (process.env.SUNLINT_DEBUG) {
223
- console.log(`🔧 [S017] Trying regex-based analysis...`);
224
- }
225
-
226
- // Read file content for regex analyzer
227
- const fs = require("fs");
228
- const fileContent = fs.readFileSync(filePath, "utf8");
229
-
230
- const violations = await this.regexAnalyzer.analyzeFile(
231
- filePath,
232
- fileContent,
233
- options
234
- );
235
-
236
- // Add violations to map to deduplicate
237
- violations.forEach((v) => {
238
- const key = `${v.line}:${v.column}:${v.message}`;
239
- if (!violationMap.has(key)) {
240
- v.analysisStrategy = "regex-fallback";
241
- violationMap.set(key, v);
242
- }
243
- });
244
-
245
- if (process.env.SUNLINT_DEBUG) {
246
- console.log(
247
- `🔄 [S017] Regex-based analysis: ${violations.length} violations`
248
- );
249
- }
250
- return Array.from(violationMap.values()); // Return deduplicated violations
251
- } catch (error) {
252
- console.error(`⚠ [S017] Regex analysis failed: ${error.message}`);
253
- }
187
+ if (process.env.SUNLINT_DEBUG) {
188
+ console.log(`🔧 [S017] Analysis completed: ${violationMap.size} violations`);
254
189
  }
255
-
256
- console.log(`🔧 [S017] No analysis methods succeeded, returning empty`);
257
- return [];
190
+ return Array.from(violationMap.values());
258
191
  }
259
192
 
260
193
  /**