@sun-asterisk/sunlint 1.3.36 → 1.3.37

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 (103) hide show
  1. package/cli.js +33 -0
  2. package/config/rules/enhanced-rules-registry.json +354 -98
  3. package/config/rules/rules-registry-generated.json +197 -171
  4. package/core/architecture-integration.js +115 -17
  5. package/core/cli-action-handler.js +101 -27
  6. package/core/cli-program.js +5 -0
  7. package/core/github-annotate-service.js +62 -0
  8. package/core/impact-integration.js +31 -16
  9. package/core/init-command.js +227 -0
  10. package/core/output-service.js +53 -5
  11. package/core/summary-report-service.js +46 -0
  12. package/core/unified-rule-registry.js +2 -1
  13. package/engines/eslint-engine.js +6 -0
  14. package/engines/impact/core/detectors/database-detector.js +1 -1
  15. package/engines/impact/core/detectors/endpoint-detector.js +1 -1
  16. package/engines/impact/core/report-generator.js +235 -73
  17. package/origin-rules/security-en.md +470 -282
  18. package/package.json +1 -1
  19. package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
  20. package/rules/security/S001_backend_auth_communications/index.js +87 -0
  21. package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
  22. package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
  23. package/rules/security/S002_os_command_injection/index.js +87 -0
  24. package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
  25. package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
  26. package/rules/security/S008_svg_content_validation/index.js +87 -0
  27. package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
  28. package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
  29. package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
  30. package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
  31. package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
  32. package/rules/security/S021_referrer_policy/index.js +86 -0
  33. package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
  34. package/rules/security/S023_no_json_injection/config.json +133 -44
  35. package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
  36. package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
  37. package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
  38. package/rules/security/S026_tls_all_connections/config.json +30 -0
  39. package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
  40. package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
  41. package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
  42. package/rules/security/S035_separate_app_hostnames/config.json +28 -0
  43. package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
  44. package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
  45. package/rules/security/S039_tls_certificate_validation/config.json +29 -0
  46. package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
  47. package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
  48. package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
  49. package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
  50. package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
  51. package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
  52. package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
  53. package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
  54. package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
  55. package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
  56. package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
  57. package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
  58. package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
  59. package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
  60. package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
  61. package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
  62. package/rules/security/S053_generic_error_messages/config.json +28 -0
  63. package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
  64. package/rules/security/S053_generic_error_messages/index.js +86 -0
  65. package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
  66. package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
  67. package/rules/security/S059_disable_debug_mode/config.json +28 -0
  68. package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
  69. package/rules/security/S059_disable_debug_mode/index.js +86 -0
  70. package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
  71. package/rules/security/S060_password_minimum_length/config.json +28 -0
  72. package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
  73. package/rules/security/S060_password_minimum_length/index.js +86 -0
  74. package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
  75. package/rules/security/S026_json_schema_validation/config.json +0 -27
  76. package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
  77. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  78. package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
  79. package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
  80. package/rules/security/S035_path_session_cookies/config.json +0 -99
  81. package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
  82. package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
  83. package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
  84. package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
  85. package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
  86. package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
  87. package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
  88. package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
  89. package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
  90. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
  91. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
  92. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
  93. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
  94. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
  95. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
  96. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
  97. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
  98. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
  99. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
  100. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
  101. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
  102. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
  103. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/typescript/README.md +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.3.36",
3
+ "version": "1.3.37",
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": {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * S001 Dart Analyzer - Backend Authentication Communications
3
+ *
4
+ * This is a JS wrapper that delegates to DartAnalyzer binary.
5
+ * Actual implementation: dart_analyzer/lib/rules/security/S001_backend_auth_communications.dart
6
+ *
7
+ * Rule: Ensure backend-to-backend communications use secure, short-lived credentials
8
+ */
9
+
10
+ class DartS001Analyzer {
11
+ constructor() {
12
+ this.ruleId = 'S001';
13
+ this.language = 'dart';
14
+ }
15
+
16
+ getMetadata() {
17
+ return {
18
+ ruleId: 'S001',
19
+ name: 'Backend Authentication Communications',
20
+ language: 'dart',
21
+ delegateTo: 'dart_analyzer',
22
+ description: 'Ensure backend communications use secure, short-lived credentials'
23
+ };
24
+ }
25
+
26
+ getConfig() {
27
+ return {
28
+ checkStaticCredentials: true,
29
+ checkHardcodedHeaders: true,
30
+ severity: 'error'
31
+ };
32
+ }
33
+
34
+ async analyze(files, language, options) {
35
+ // Delegated to DartAnalyzer binary via heuristic-engine.js
36
+ return [];
37
+ }
38
+
39
+ supportsLanguage(language) {
40
+ return language === 'dart';
41
+ }
42
+ }
43
+
44
+ module.exports = DartS001Analyzer;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * S001 Rule Router - Backend Authentication Communications
3
+ *
4
+ * Routes analysis to the appropriate language-specific analyzer.
5
+ * Supports: TypeScript, JavaScript, Dart
6
+ *
7
+ * Rule: Ensure backend-to-backend communications are authenticated using secure,
8
+ * short-lived credentials instead of static secrets.
9
+ */
10
+
11
+ const path = require('path');
12
+
13
+ class S001Router {
14
+ constructor() {
15
+ this.analyzers = new Map();
16
+ this.ruleId = 'S001';
17
+ }
18
+
19
+ getAnalyzer(language) {
20
+ const normalizedLang = this.normalizeLanguage(language);
21
+
22
+ if (!this.analyzers.has(normalizedLang)) {
23
+ try {
24
+ const analyzerPath = path.join(__dirname, normalizedLang, 'analyzer.js');
25
+ const AnalyzerClass = require(analyzerPath);
26
+ this.analyzers.set(normalizedLang, new AnalyzerClass());
27
+ } catch (error) {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ return this.analyzers.get(normalizedLang);
33
+ }
34
+
35
+ normalizeLanguage(language) {
36
+ if (typeof language !== 'string') {
37
+ return 'typescript';
38
+ }
39
+ const languageMap = {
40
+ 'typescript': 'typescript',
41
+ 'javascript': 'typescript',
42
+ 'ts': 'typescript',
43
+ 'js': 'typescript',
44
+ 'dart': 'dart'
45
+ };
46
+ return languageMap[language.toLowerCase()] || language.toLowerCase();
47
+ }
48
+
49
+ supportsLanguage(language) {
50
+ if (typeof language !== 'string') return false;
51
+ const supported = ['typescript', 'javascript', 'ts', 'js', 'dart'];
52
+ return supported.includes(language.toLowerCase());
53
+ }
54
+
55
+ getSupportedLanguages() {
56
+ return ['typescript', 'javascript', 'dart'];
57
+ }
58
+
59
+ async analyze(files, language, options = {}) {
60
+ const analyzer = this.getAnalyzer(language);
61
+ if (!analyzer) return [];
62
+ if (typeof analyzer.analyze === 'function') {
63
+ return analyzer.analyze(files, language, options);
64
+ }
65
+ return [];
66
+ }
67
+
68
+ async initialize(semanticEngineOrLanguage = null, semanticEngine = null) {
69
+ let engine = semanticEngine;
70
+ let lang = null;
71
+
72
+ if (typeof semanticEngineOrLanguage === 'string') {
73
+ lang = semanticEngineOrLanguage;
74
+ } else if (semanticEngineOrLanguage && typeof semanticEngineOrLanguage === 'object') {
75
+ engine = semanticEngineOrLanguage;
76
+ }
77
+
78
+ if (lang) {
79
+ const analyzer = this.getAnalyzer(lang);
80
+ if (analyzer && typeof analyzer.initialize === 'function') {
81
+ await analyzer.initialize(engine);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ module.exports = new S001Router();
@@ -0,0 +1,164 @@
1
+ /**
2
+ * S001 TypeScript Analyzer - Backend Authentication Communications
3
+ *
4
+ * Detects insecure backend authentication patterns in TypeScript/JavaScript code.
5
+ *
6
+ * Rule: Ensure backend-to-backend communications use secure, short-lived credentials
7
+ * instead of static secrets.
8
+ *
9
+ * Detects:
10
+ * - Static API keys/passwords in service calls
11
+ * - Hardcoded credentials in HTTP headers
12
+ * - Long-lived tokens without expiry
13
+ * - Shared secrets in configuration
14
+ */
15
+
16
+ const fs = require('fs');
17
+
18
+ class TypeScriptS001Analyzer {
19
+ constructor(config = {}) {
20
+ this.ruleId = 'S001';
21
+ this.language = 'typescript';
22
+ this.config = {
23
+ checkStaticCredentials: config.checkStaticCredentials !== false,
24
+ checkHardcodedHeaders: config.checkHardcodedHeaders !== false,
25
+ };
26
+ }
27
+
28
+ getMetadata() {
29
+ return {
30
+ ruleId: 'S001',
31
+ name: 'Backend Authentication Communications',
32
+ language: 'typescript',
33
+ description: 'Ensure backend communications use secure, short-lived credentials'
34
+ };
35
+ }
36
+
37
+ getConfig() {
38
+ return this.config;
39
+ }
40
+
41
+ async analyze(files, language, options = {}) {
42
+ const violations = [];
43
+
44
+ for (const filePath of files) {
45
+ if (!/\.(ts|tsx|js|jsx)$/i.test(filePath)) continue;
46
+
47
+ try {
48
+ const content = fs.readFileSync(filePath, 'utf-8');
49
+ const fileViolations = this.analyzeContent(content, filePath);
50
+ violations.push(...fileViolations);
51
+ } catch (error) {
52
+ // Skip files that can't be read
53
+ }
54
+ }
55
+
56
+ return violations;
57
+ }
58
+
59
+ analyzeContent(content, filePath) {
60
+ const violations = [];
61
+ const lines = content.split('\n');
62
+
63
+ // Skip test files
64
+ if (/\.(test|spec)\.(ts|js|tsx|jsx)$/i.test(filePath) ||
65
+ /__(tests?|mocks?)__/i.test(filePath)) {
66
+ return violations;
67
+ }
68
+
69
+ // Patterns for detecting insecure backend auth
70
+ const patterns = [
71
+ {
72
+ // Static API key in headers
73
+ regex: /headers\s*[=:]\s*\{[^}]*['"]?(Authorization|X-API-Key|Api-Key|X-Auth-Token)['"]?\s*[=:]\s*['"`][^'"`]{10,}['"`]/gi,
74
+ message: 'Static credential in HTTP headers - use environment variables or secrets manager'
75
+ },
76
+ {
77
+ // Hardcoded Bearer token
78
+ regex: /['"`]Bearer\s+[A-Za-z0-9._-]{20,}['"`]/gi,
79
+ message: 'Hardcoded Bearer token detected - use dynamic token generation'
80
+ },
81
+ {
82
+ // Static password in connection strings - must be actual password value assignment
83
+ // Match: password: 'secret123', password = 'mypass', db_password: 'admin'
84
+ // Skip: RESET_PASSWORD = '/path', resetPassword: 'service-name', password_field: 'name'
85
+ // Must not start with / (URL path) and must contain alphanumeric (not just identifiers)
86
+ regex: /(?<![a-zA-Z_])password\s*[=:]\s*['"`](?![\/])[^'"`]{8,}['"`]/gi,
87
+ message: 'Static password in connection - use secrets manager or environment variables',
88
+ excludePatterns: [
89
+ /process\.env/i,
90
+ /config\./i,
91
+ /getenv/i,
92
+ /Password\s*:/i,
93
+ /PASSWORD\s*=\s*['"`]\/[^'"`]*['"`]/i, // URL paths like '/reset-password'
94
+ /password\s*[=:]\s*['"`][a-zA-Z]+-[a-zA-Z]+(-[a-zA-Z]+)*['"`]/i, // service names like 'reset-password-activity'
95
+ /const\s+\w*PASSWORD\w*\s*=/i, // const RESET_PASSWORD =
96
+ /enum/i // enum definitions
97
+ ]
98
+ },
99
+ {
100
+ // API key assignment with literal value (must look like actual API key)
101
+ regex: /(?:apiKey|api_key|apiToken|api_token)\s*[=:]\s*['"`](?=[A-Za-z0-9._-]*[0-9])[A-Za-z0-9._-]{20,}['"`]/gi,
102
+ message: 'Hardcoded API key - use environment variables or secrets manager'
103
+ },
104
+ {
105
+ // Basic auth with hardcoded credentials (Base64 encoded)
106
+ regex: /['"`]Basic\s+[A-Za-z0-9+/]{20,}={0,2}['"`]/gi,
107
+ message: 'Hardcoded Basic authentication - credentials should be dynamically retrieved'
108
+ },
109
+ {
110
+ // Service account with static credentials
111
+ regex: /service[_-]?account.*(?<![a-zA-Z])password\s*[=:]\s*['"`][^'"`]{8,}['"`]/gi,
112
+ message: 'Static service account credentials - use certificate-based auth or short-lived tokens'
113
+ },
114
+ {
115
+ // Shared secret patterns
116
+ regex: /shared[_-]?secret\s*[=:]\s*['"`][^'"`]{8,}['"`]/gi,
117
+ message: 'Shared secret detected - use individual service accounts with short-lived tokens'
118
+ },
119
+ {
120
+ // Database connection string with password
121
+ regex: /(?:mongodb|postgres|mysql|redis|amqp):\/\/[^:]+:[^@]{4,}@/gi,
122
+ message: 'Hardcoded credentials in connection string - use secrets manager'
123
+ }
124
+ ];
125
+
126
+ lines.forEach((line, lineIndex) => {
127
+ // Skip comments
128
+ if (/^\s*(\/\/|\/\*|\*)/.test(line)) return;
129
+
130
+ // Skip if line contains environment variable access
131
+ if (/process\.env|getenv|Environment\.|config\./i.test(line)) return;
132
+
133
+ for (const pattern of patterns) {
134
+ let match;
135
+ pattern.regex.lastIndex = 0;
136
+ while ((match = pattern.regex.exec(line)) !== null) {
137
+ // Check exclude patterns
138
+ if (pattern.excludePatterns) {
139
+ const shouldExclude = pattern.excludePatterns.some(ep => ep.test(line));
140
+ if (shouldExclude) continue;
141
+ }
142
+
143
+ violations.push({
144
+ ruleId: 'S001',
145
+ file: filePath,
146
+ line: lineIndex + 1,
147
+ column: match.index + 1,
148
+ severity: 'error',
149
+ message: pattern.message,
150
+ suggestion: 'Use short-lived tokens, certificate-based auth (mTLS), or secrets manager'
151
+ });
152
+ }
153
+ }
154
+ });
155
+
156
+ return violations;
157
+ }
158
+
159
+ supportsLanguage(language) {
160
+ return ['typescript', 'javascript', 'ts', 'js'].includes(language.toLowerCase());
161
+ }
162
+ }
163
+
164
+ module.exports = TypeScriptS001Analyzer;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * S002 Dart Analyzer - OS Command Injection Protection
3
+ *
4
+ * This is a JS wrapper that delegates to DartAnalyzer binary.
5
+ * Actual implementation: dart_analyzer/lib/rules/security/S002_os_command_injection.dart
6
+ *
7
+ * Rule: Prevent OS command injection by using parameterized commands
8
+ */
9
+
10
+ class DartS002Analyzer {
11
+ constructor() {
12
+ this.ruleId = 'S002';
13
+ this.language = 'dart';
14
+ }
15
+
16
+ getMetadata() {
17
+ return {
18
+ ruleId: 'S002',
19
+ name: 'OS Command Injection Protection',
20
+ language: 'dart',
21
+ delegateTo: 'dart_analyzer',
22
+ description: 'Prevent OS command injection by using parameterized commands'
23
+ };
24
+ }
25
+
26
+ getConfig() {
27
+ return {
28
+ checkProcessRun: true,
29
+ checkShellExecution: true,
30
+ severity: 'error'
31
+ };
32
+ }
33
+
34
+ async analyze(files, language, options) {
35
+ // Delegated to DartAnalyzer binary via heuristic-engine.js
36
+ return [];
37
+ }
38
+
39
+ supportsLanguage(language) {
40
+ return language === 'dart';
41
+ }
42
+ }
43
+
44
+ module.exports = DartS002Analyzer;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * S002 Rule Router - OS Command Injection Protection
3
+ *
4
+ * Routes analysis to the appropriate language-specific analyzer.
5
+ * Supports: TypeScript, JavaScript, Dart
6
+ *
7
+ * Rule: Prevent OS command injection attacks by ensuring all operating system
8
+ * calls use parameterized queries or proper output encoding.
9
+ */
10
+
11
+ const path = require('path');
12
+
13
+ class S002Router {
14
+ constructor() {
15
+ this.analyzers = new Map();
16
+ this.ruleId = 'S002';
17
+ }
18
+
19
+ getAnalyzer(language) {
20
+ const normalizedLang = this.normalizeLanguage(language);
21
+
22
+ if (!this.analyzers.has(normalizedLang)) {
23
+ try {
24
+ const analyzerPath = path.join(__dirname, normalizedLang, 'analyzer.js');
25
+ const AnalyzerClass = require(analyzerPath);
26
+ this.analyzers.set(normalizedLang, new AnalyzerClass());
27
+ } catch (error) {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ return this.analyzers.get(normalizedLang);
33
+ }
34
+
35
+ normalizeLanguage(language) {
36
+ if (typeof language !== 'string') {
37
+ return 'typescript';
38
+ }
39
+ const languageMap = {
40
+ 'typescript': 'typescript',
41
+ 'javascript': 'typescript',
42
+ 'ts': 'typescript',
43
+ 'js': 'typescript',
44
+ 'dart': 'dart'
45
+ };
46
+ return languageMap[language.toLowerCase()] || language.toLowerCase();
47
+ }
48
+
49
+ supportsLanguage(language) {
50
+ if (typeof language !== 'string') return false;
51
+ const supported = ['typescript', 'javascript', 'ts', 'js', 'dart'];
52
+ return supported.includes(language.toLowerCase());
53
+ }
54
+
55
+ getSupportedLanguages() {
56
+ return ['typescript', 'javascript', 'dart'];
57
+ }
58
+
59
+ async analyze(files, language, options = {}) {
60
+ const analyzer = this.getAnalyzer(language);
61
+ if (!analyzer) return [];
62
+ if (typeof analyzer.analyze === 'function') {
63
+ return analyzer.analyze(files, language, options);
64
+ }
65
+ return [];
66
+ }
67
+
68
+ async initialize(semanticEngineOrLanguage = null, semanticEngine = null) {
69
+ let engine = semanticEngine;
70
+ let lang = null;
71
+
72
+ if (typeof semanticEngineOrLanguage === 'string') {
73
+ lang = semanticEngineOrLanguage;
74
+ } else if (semanticEngineOrLanguage && typeof semanticEngineOrLanguage === 'object') {
75
+ engine = semanticEngineOrLanguage;
76
+ }
77
+
78
+ if (lang) {
79
+ const analyzer = this.getAnalyzer(lang);
80
+ if (analyzer && typeof analyzer.initialize === 'function') {
81
+ await analyzer.initialize(engine);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ module.exports = new S002Router();
@@ -0,0 +1,194 @@
1
+ /**
2
+ * S002 TypeScript Analyzer - OS Command Injection Protection
3
+ *
4
+ * Detects OS command injection vulnerabilities in TypeScript/JavaScript code.
5
+ *
6
+ * Rule: Prevent OS command injection by ensuring operating system calls use
7
+ * parameterized queries or proper output encoding.
8
+ *
9
+ * Detects:
10
+ * - exec() with string concatenation
11
+ * - spawn/execFile with shell: true and user input
12
+ * - Template literals in shell commands
13
+ * - child_process with untrusted data
14
+ */
15
+
16
+ const fs = require('fs');
17
+
18
+ class TypeScriptS002Analyzer {
19
+ constructor(config = {}) {
20
+ this.ruleId = 'S002';
21
+ this.language = 'typescript';
22
+ this.config = {
23
+ checkExec: config.checkExec !== false,
24
+ checkSpawn: config.checkSpawn !== false,
25
+ checkEval: config.checkEval !== false,
26
+ };
27
+ }
28
+
29
+ getMetadata() {
30
+ return {
31
+ ruleId: 'S002',
32
+ name: 'OS Command Injection Protection',
33
+ language: 'typescript',
34
+ description: 'Prevent OS command injection by using parameterized commands'
35
+ };
36
+ }
37
+
38
+ getConfig() {
39
+ return this.config;
40
+ }
41
+
42
+ async analyze(files, language, options = {}) {
43
+ const violations = [];
44
+
45
+ for (const filePath of files) {
46
+ if (!/\.(ts|tsx|js|jsx)$/i.test(filePath)) continue;
47
+
48
+ try {
49
+ const content = fs.readFileSync(filePath, 'utf-8');
50
+ const fileViolations = this.analyzeContent(content, filePath);
51
+ violations.push(...fileViolations);
52
+ } catch (error) {
53
+ // Skip files that can't be read
54
+ }
55
+ }
56
+
57
+ return violations;
58
+ }
59
+
60
+ analyzeContent(content, filePath) {
61
+ const violations = [];
62
+ const lines = content.split('\n');
63
+
64
+ // Skip test files
65
+ if (/\.(test|spec)\.(ts|js|tsx|jsx)$/i.test(filePath) ||
66
+ /__(tests?|mocks?)__/i.test(filePath)) {
67
+ return violations;
68
+ }
69
+
70
+ // Track user input variables
71
+ const userInputVars = new Set();
72
+
73
+ // First pass: identify user input variables
74
+ lines.forEach((line) => {
75
+ const inputPatterns = [
76
+ /const\s+(\w+)\s*=\s*req\.(query|body|params)/,
77
+ /let\s+(\w+)\s*=\s*req\.(query|body|params)/,
78
+ /const\s+\{\s*([^}]+)\s*\}\s*=\s*req\.(query|body|params)/,
79
+ ];
80
+
81
+ for (const pattern of inputPatterns) {
82
+ const match = line.match(pattern);
83
+ if (match) {
84
+ if (match[1].includes(',')) {
85
+ match[1].split(',').forEach(v => userInputVars.add(v.trim()));
86
+ } else {
87
+ userInputVars.add(match[1]);
88
+ }
89
+ }
90
+ }
91
+ });
92
+
93
+ // Patterns for detecting OS command injection
94
+ const patterns = [
95
+ {
96
+ // exec() with template literal or string concatenation
97
+ regex: /exec\s*\(\s*`[^`]*\$\{/g,
98
+ message: 'OS command injection risk: exec() with template literal containing variables'
99
+ },
100
+ {
101
+ // exec() with string concatenation
102
+ regex: /exec\s*\([^)]*\+\s*\w+/g,
103
+ message: 'OS command injection risk: exec() with string concatenation'
104
+ },
105
+ {
106
+ // execSync with template literal
107
+ regex: /execSync\s*\(\s*`[^`]*\$\{/g,
108
+ message: 'OS command injection risk: execSync() with template literal'
109
+ },
110
+ {
111
+ // spawn with shell: true
112
+ regex: /spawn\s*\([^)]+,\s*\{[^}]*shell\s*:\s*true/g,
113
+ message: 'OS command injection risk: spawn() with shell: true - use argument array instead'
114
+ },
115
+ {
116
+ // spawnSync with shell: true
117
+ regex: /spawnSync\s*\([^)]+,\s*\{[^}]*shell\s*:\s*true/g,
118
+ message: 'OS command injection risk: spawnSync() with shell: true'
119
+ },
120
+ {
121
+ // child_process.exec with user input patterns
122
+ regex: /child_process\.exec\s*\(\s*`[^`]*\$\{/g,
123
+ message: 'OS command injection risk: child_process.exec with interpolated values'
124
+ },
125
+ {
126
+ // shelljs exec
127
+ regex: /shell\.exec\s*\(\s*`[^`]*\$\{/g,
128
+ message: 'OS command injection risk: shelljs exec with template literal'
129
+ },
130
+ {
131
+ // execa with shell option
132
+ regex: /execa\s*\([^)]+,\s*\{[^}]*shell\s*:\s*true/g,
133
+ message: 'OS command injection risk: execa with shell: true'
134
+ },
135
+ {
136
+ // Direct system call patterns
137
+ regex: /\bsystem\s*\(\s*`[^`]*\$\{/g,
138
+ message: 'OS command injection risk: system() with template literal'
139
+ },
140
+ {
141
+ // eval with user input
142
+ regex: /eval\s*\(\s*(req\.(query|body|params)|userInput|\w+Input)/g,
143
+ message: 'Code injection risk: eval() with potentially untrusted input'
144
+ }
145
+ ];
146
+
147
+ lines.forEach((line, lineIndex) => {
148
+ // Skip comments
149
+ if (/^\s*(\/\/|\/\*|\*)/.test(line)) return;
150
+
151
+ for (const pattern of patterns) {
152
+ let match;
153
+ pattern.regex.lastIndex = 0;
154
+ while ((match = pattern.regex.exec(line)) !== null) {
155
+ violations.push({
156
+ ruleId: 'S002',
157
+ file: filePath,
158
+ line: lineIndex + 1,
159
+ column: match.index + 1,
160
+ severity: 'error',
161
+ message: pattern.message,
162
+ suggestion: 'Use execFile/spawn with argument arrays instead of shell commands with string interpolation'
163
+ });
164
+ }
165
+ }
166
+
167
+ // Check for user input variables in exec calls
168
+ if (/exec\s*\(/.test(line) || /spawn\s*\(/.test(line)) {
169
+ for (const userVar of userInputVars) {
170
+ const varPattern = new RegExp(`(exec|spawn)\\s*\\([^)]*\\b${userVar}\\b`, 'g');
171
+ if (varPattern.test(line)) {
172
+ violations.push({
173
+ ruleId: 'S002',
174
+ file: filePath,
175
+ line: lineIndex + 1,
176
+ column: 1,
177
+ severity: 'error',
178
+ message: `OS command injection risk: user input "${userVar}" used in shell command`,
179
+ suggestion: 'Validate and sanitize user input before using in commands, or use allowlists'
180
+ });
181
+ }
182
+ }
183
+ }
184
+ });
185
+
186
+ return violations;
187
+ }
188
+
189
+ supportsLanguage(language) {
190
+ return ['typescript', 'javascript', 'ts', 'js'].includes(language.toLowerCase());
191
+ }
192
+ }
193
+
194
+ module.exports = TypeScriptS002Analyzer;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * S008 Dart Analyzer - SVG Content Validation
3
+ *
4
+ * This is a JS wrapper that delegates to DartAnalyzer binary.
5
+ * Actual implementation: dart_analyzer/lib/rules/security/S008_svg_content_sanitization.dart
6
+ *
7
+ * Rule: Ensure user-supplied SVG content is sanitized before rendering
8
+ */
9
+
10
+ class DartS008Analyzer {
11
+ constructor() {
12
+ this.ruleId = 'S008';
13
+ this.language = 'dart';
14
+ }
15
+
16
+ getMetadata() {
17
+ return {
18
+ ruleId: 'S008',
19
+ name: 'SVG Content Validation',
20
+ language: 'dart',
21
+ delegateTo: 'dart_analyzer',
22
+ description: 'Ensure user-supplied SVG content is sanitized before rendering'
23
+ };
24
+ }
25
+
26
+ getConfig() {
27
+ return {
28
+ checkSvgRendering: true,
29
+ checkScriptTags: true,
30
+ severity: 'warning'
31
+ };
32
+ }
33
+
34
+ async analyze(files, language, options) {
35
+ // Delegated to DartAnalyzer binary via heuristic-engine.js
36
+ return [];
37
+ }
38
+
39
+ supportsLanguage(language) {
40
+ return language === 'dart';
41
+ }
42
+ }
43
+
44
+ module.exports = DartS008Analyzer;