@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.
- package/cli.js +33 -0
- package/config/rules/enhanced-rules-registry.json +354 -98
- package/config/rules/rules-registry-generated.json +197 -171
- package/core/architecture-integration.js +115 -17
- package/core/cli-action-handler.js +101 -27
- package/core/cli-program.js +5 -0
- package/core/github-annotate-service.js +62 -0
- package/core/impact-integration.js +31 -16
- package/core/init-command.js +227 -0
- package/core/output-service.js +53 -5
- package/core/summary-report-service.js +46 -0
- package/core/unified-rule-registry.js +2 -1
- package/engines/eslint-engine.js +6 -0
- package/engines/impact/core/detectors/database-detector.js +1 -1
- package/engines/impact/core/detectors/endpoint-detector.js +1 -1
- package/engines/impact/core/report-generator.js +235 -73
- package/origin-rules/security-en.md +470 -282
- package/package.json +1 -1
- package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
- package/rules/security/S001_backend_auth_communications/index.js +87 -0
- package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
- package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
- package/rules/security/S002_os_command_injection/index.js +87 -0
- package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
- package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
- package/rules/security/S008_svg_content_validation/index.js +87 -0
- package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
- package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
- package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
- package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
- package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
- package/rules/security/S021_referrer_policy/index.js +86 -0
- package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
- package/rules/security/S023_no_json_injection/config.json +133 -44
- package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
- package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
- package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
- package/rules/security/S026_tls_all_connections/config.json +30 -0
- package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
- package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
- package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
- package/rules/security/S035_separate_app_hostnames/config.json +28 -0
- package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
- package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
- package/rules/security/S039_tls_certificate_validation/config.json +29 -0
- package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
- package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
- package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
- package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
- package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
- package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
- package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
- package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
- package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
- package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
- package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
- package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
- package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
- package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
- package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
- package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
- package/rules/security/S053_generic_error_messages/config.json +28 -0
- package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
- package/rules/security/S053_generic_error_messages/index.js +86 -0
- package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
- package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
- package/rules/security/S059_disable_debug_mode/config.json +28 -0
- package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
- package/rules/security/S059_disable_debug_mode/index.js +86 -0
- package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
- package/rules/security/S060_password_minimum_length/config.json +28 -0
- package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
- package/rules/security/S060_password_minimum_length/index.js +86 -0
- package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
- package/rules/security/S026_json_schema_validation/config.json +0 -27
- package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
- package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
- package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
- package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
- package/rules/security/S035_path_session_cookies/config.json +0 -99
- package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
- package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
- package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
- package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
- package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
- package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
- package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
- package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
- package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
- /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
- /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/typescript/README.md +0 -0
package/package.json
CHANGED
|
@@ -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;
|