@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.
- package/config/rules/enhanced-rules-registry.json +101 -17
- package/config/rules/rules-registry-generated.json +22 -22
- package/origin-rules/security-en.md +351 -338
- package/package.json +1 -1
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +73 -21
- package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +206 -2
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +553 -58
- package/rules/common/C029_catch_block_logging/analyzer.js +47 -12
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +35 -15
- package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +9 -5
- package/rules/security/S003_open_redirect_protection/README.md +371 -0
- package/rules/security/S003_open_redirect_protection/analyzer.js +135 -0
- package/rules/security/S003_open_redirect_protection/config.json +58 -0
- package/rules/security/S003_open_redirect_protection/symbol-based-analyzer.js +884 -0
- package/rules/security/S004_sensitive_data_logging/analyzer.js +135 -0
- package/rules/security/S004_sensitive_data_logging/config.json +62 -0
- package/rules/security/S004_sensitive_data_logging/symbol-based-analyzer.js +592 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +97 -148
- package/rules/security/S005_no_origin_auth/config.json +28 -67
- package/rules/security/S005_no_origin_auth/symbol-based-analyzer.js +708 -0
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +170 -31
- package/rules/security/S010_no_insecure_encryption/analyzer.js +8 -2
- package/rules/security/S012_hardcoded_secrets/analyzer.js +149 -0
- package/rules/security/S012_hardcoded_secrets/config.json +75 -0
- package/rules/security/S012_hardcoded_secrets/symbol-based-analyzer.js +1204 -0
- package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +87 -0
- package/rules/security/S017_use_parameterized_queries/analyzer.js +11 -78
- package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +1146 -1
- package/rules/security/S019_smtp_injection_protection/analyzer.js +120 -0
- package/rules/security/S019_smtp_injection_protection/config.json +35 -0
- package/rules/security/S019_smtp_injection_protection/symbol-based-analyzer.js +687 -0
- package/rules/security/S020_no_eval_dynamic_code/analyzer.js +55 -130
- package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +4 -19
- package/rules/security/S022_escape_output_context/README.md +254 -0
- package/rules/security/S022_escape_output_context/analyzer.js +510 -0
- package/rules/security/S022_escape_output_context/config.json +229 -0
- package/rules/security/S023_no_json_injection/analyzer.js +15 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +18 -3
- package/rules/security/S023_no_json_injection/config.json +133 -0
- package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +41 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +67 -8
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +29 -6
- package/rules/security/S029_csrf_protection/config.json +127 -0
- package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +160 -28
- package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +81 -19
- package/rules/security/S031_secure_session_cookies/analyzer.js +20 -2
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +100 -0
- package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +8 -1
- package/rules/security/S032_httponly_session_cookies/analyzer.js +2 -2
- package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +115 -0
- package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +39 -10
- package/rules/security/S036_lfi_rfi_protection/analyzer.js +224 -0
- package/rules/security/S036_lfi_rfi_protection/config.json +20 -0
- package/rules/security/S040_session_fixation_protection/analyzer.js +153 -0
- package/rules/security/S040_session_fixation_protection/config.json +20 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/README.md +83 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +153 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/config.json +41 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +1139 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/README.md +107 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/analyzer.js +153 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/config.json +41 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/symbol-based-analyzer.js +541 -0
- package/docs/COMMAND-EXAMPLES.md +0 -390
- package/docs/FILE_LIMITS_COMPLETION_REPORT.md +0 -151
- package/docs/FOLDER_STRUCTURE.md +0 -59
- package/docs/SIMPLIFIED_USAGE_GUIDE.md +0 -208
- package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +0 -541
- package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +0 -307
|
@@ -1,183 +1,132 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* S005 - No Origin Header Authentication
|
|
3
|
+
*
|
|
4
|
+
* Main analyzer using symbol-based analysis to detect use of Origin header
|
|
5
|
+
* for authentication or authorization decisions.
|
|
6
|
+
*
|
|
7
|
+
* Based on:
|
|
8
|
+
* - OWASP A07:2021 - Identification and Authentication Failures
|
|
9
|
+
* - CWE-290: Authentication Bypass by Spoofing
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
const
|
|
12
|
+
const S005SymbolBasedAnalyzer = require("./symbol-based-analyzer");
|
|
8
13
|
|
|
9
14
|
class S005Analyzer {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.ruleId =
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.ruleId = "S005";
|
|
17
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
18
|
+
this.verbose = options.verbose || false;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
this.symbolAnalyzer = new S005SymbolBasedAnalyzer(this.semanticEngine);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.warn(`⚠ [S005] Failed to create symbol analyzer: ${e.message}`);
|
|
24
|
+
}
|
|
15
25
|
}
|
|
16
26
|
|
|
17
|
-
async
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.log(`🔍 Running S005 analysis on ${files.length} files...`);
|
|
27
|
+
async initialize(semanticEngine) {
|
|
28
|
+
this.semanticEngine = semanticEngine;
|
|
29
|
+
if (this.symbolAnalyzer && this.symbolAnalyzer.initialize) {
|
|
30
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
22
31
|
}
|
|
32
|
+
}
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
analyzeSingle(filePath, options = {}) {
|
|
35
|
+
return this.analyze([filePath], "typescript", options);
|
|
36
|
+
}
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
async analyze(files, language, options = {}) {
|
|
39
|
+
const violations = [];
|
|
29
40
|
for (const filePath of files) {
|
|
30
41
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const filteredRegexViolations = regexViolations.filter(regexViolation =>
|
|
36
|
-
!astViolations.some(astViolation =>
|
|
37
|
-
astViolation.line === regexViolation.line &&
|
|
38
|
-
astViolation.filePath === regexViolation.filePath
|
|
39
|
-
)
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
violations.push(...filteredRegexViolations);
|
|
43
|
-
} catch (error) {
|
|
44
|
-
if (options.verbose) {
|
|
45
|
-
console.warn(`⚠️ S005 regex analysis failed for ${filePath}: ${error.message}`);
|
|
46
|
-
}
|
|
42
|
+
const vs = await this.analyzeFile(filePath, options);
|
|
43
|
+
violations.push(...vs);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.warn(`⚠ [S005] Analysis error for ${filePath}: ${e.message}`);
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
|
-
|
|
50
|
-
if (options.verbose && violations.length > 0) {
|
|
51
|
-
console.log(`📊 S005 found ${violations.length} violations`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
48
|
return violations;
|
|
55
49
|
}
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const lines = content.split('\n');
|
|
60
|
-
|
|
61
|
-
lines.forEach((line, index) => {
|
|
62
|
-
const lineNumber = index + 1;
|
|
63
|
-
|
|
64
|
-
// Pattern 1: Direct origin header access for authentication
|
|
65
|
-
// req.headers.origin, req.get('origin'), req.header('origin')
|
|
66
|
-
const originHeaderPattern = /(?:req\.headers\.origin|req\.get\s*\(\s*['"`]origin['"`]\s*\)|req\.header\s*\(\s*['"`]origin['"`]\s*\)|headers\[['"`]origin['"`]\])/i;
|
|
67
|
-
if (originHeaderPattern.test(line)) {
|
|
68
|
-
// Check if this line is used for authentication/authorization
|
|
69
|
-
const authContextPattern = /(?:auth|login|verify|check|validate|permission|access|allow|deny|secure|token|session)/i;
|
|
70
|
-
if (authContextPattern.test(line) || this.isInAuthContext(lines, index)) {
|
|
71
|
-
violations.push({
|
|
72
|
-
ruleId: this.ruleId,
|
|
73
|
-
severity: 'error',
|
|
74
|
-
message: 'Origin header should not be used for authentication. Origin can be spoofed and is not secure for access control.',
|
|
75
|
-
line: lineNumber,
|
|
76
|
-
column: line.search(originHeaderPattern) + 1,
|
|
77
|
-
filePath: filePath,
|
|
78
|
-
type: 'origin_header_auth'
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
51
|
+
async analyzeFile(filePath, options = {}) {
|
|
52
|
+
const violationMap = new Map();
|
|
82
53
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
violations.push({
|
|
87
|
-
ruleId: this.ruleId,
|
|
88
|
-
severity: 'warning',
|
|
89
|
-
message: 'CORS origin validation should not replace proper authentication mechanisms.',
|
|
90
|
-
line: lineNumber,
|
|
91
|
-
column: line.search(corsOriginAuthPattern) + 1,
|
|
92
|
-
filePath: filePath,
|
|
93
|
-
type: 'cors_origin_auth'
|
|
94
|
-
});
|
|
95
|
-
}
|
|
54
|
+
if (!this.symbolAnalyzer) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
96
57
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
ruleId: this.ruleId,
|
|
102
|
-
severity: 'error',
|
|
103
|
-
message: 'Conditional authentication based on Origin header is insecure. Use proper authentication tokens.',
|
|
104
|
-
line: lineNumber,
|
|
105
|
-
column: line.search(/origin/i) + 1,
|
|
106
|
-
filePath: filePath,
|
|
107
|
-
type: 'conditional_origin_auth'
|
|
108
|
-
});
|
|
109
|
-
}
|
|
58
|
+
// Skip test files, build directories, and node_modules
|
|
59
|
+
if (this.shouldSkipFile(filePath)) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
110
62
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
ruleId: this.ruleId,
|
|
116
|
-
severity: 'error',
|
|
117
|
-
message: 'Authentication middleware should not rely on Origin header. Use proper authentication mechanisms.',
|
|
118
|
-
line: lineNumber,
|
|
119
|
-
column: line.search(/origin/i) + 1,
|
|
120
|
-
filePath: filePath,
|
|
121
|
-
type: 'middleware_origin_auth'
|
|
122
|
-
});
|
|
63
|
+
try {
|
|
64
|
+
let sourceFile = null;
|
|
65
|
+
if (this.semanticEngine?.project) {
|
|
66
|
+
sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
123
67
|
}
|
|
124
68
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
69
|
+
if (!sourceFile) {
|
|
70
|
+
// Create temporary ts-morph source file
|
|
71
|
+
const fs = require("fs");
|
|
72
|
+
const path = require("path");
|
|
73
|
+
const { Project } = require("ts-morph");
|
|
74
|
+
if (!fs.existsSync(filePath)) {
|
|
75
|
+
throw new Error(`File not found: ${filePath}`);
|
|
76
|
+
}
|
|
77
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
78
|
+
const tmp = new Project({
|
|
79
|
+
useInMemoryFileSystem: true,
|
|
80
|
+
compilerOptions: { allowJs: true },
|
|
136
81
|
});
|
|
82
|
+
sourceFile = tmp.createSourceFile(path.basename(filePath), content);
|
|
137
83
|
}
|
|
138
84
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
column: line.search(/origin/i) + 1,
|
|
148
|
-
filePath: filePath,
|
|
149
|
-
type: 'express_origin_auth'
|
|
85
|
+
if (sourceFile) {
|
|
86
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
87
|
+
sourceFile,
|
|
88
|
+
filePath
|
|
89
|
+
);
|
|
90
|
+
symbolViolations.forEach((v) => {
|
|
91
|
+
const key = `${v.line}:${v.column}:${v.message}`;
|
|
92
|
+
if (!violationMap.has(key)) violationMap.set(key, v);
|
|
150
93
|
});
|
|
151
94
|
}
|
|
152
|
-
})
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.warn(`⚠ [S005] Symbol analysis failed: ${e.message}`);
|
|
97
|
+
}
|
|
153
98
|
|
|
154
|
-
return
|
|
99
|
+
return Array.from(violationMap.values()).map((v) => ({
|
|
100
|
+
...v,
|
|
101
|
+
filePath,
|
|
102
|
+
file: filePath,
|
|
103
|
+
}));
|
|
155
104
|
}
|
|
156
105
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
106
|
+
shouldSkipFile(filePath) {
|
|
107
|
+
const skipPatterns = [
|
|
108
|
+
"test/",
|
|
109
|
+
"tests/",
|
|
110
|
+
"__tests__/",
|
|
111
|
+
".test.",
|
|
112
|
+
".spec.",
|
|
113
|
+
"node_modules/",
|
|
114
|
+
"build/",
|
|
115
|
+
"dist/",
|
|
116
|
+
".next/",
|
|
117
|
+
"coverage/",
|
|
118
|
+
"vendor/",
|
|
119
|
+
"mocks/",
|
|
120
|
+
".mock.",
|
|
171
121
|
];
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
122
|
+
|
|
123
|
+
return skipPatterns.some((pattern) => filePath.includes(pattern));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
cleanup() {
|
|
127
|
+
if (this.symbolAnalyzer?.cleanup) {
|
|
128
|
+
this.symbolAnalyzer.cleanup();
|
|
178
129
|
}
|
|
179
|
-
|
|
180
|
-
return false;
|
|
181
130
|
}
|
|
182
131
|
}
|
|
183
132
|
|
|
@@ -1,85 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"ruleId": "S005",
|
|
3
3
|
"name": "No Origin Header Authentication",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Prevent using Origin header for authentication or authorization decisions",
|
|
5
5
|
"category": "security",
|
|
6
6
|
"severity": "error",
|
|
7
7
|
"languages": ["typescript", "javascript"],
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
8
|
+
"tags": ["security", "owasp", "authentication", "authorization", "spoofing", "headers"],
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"fixable": false,
|
|
11
|
+
"engine": "heuristic",
|
|
12
|
+
"metadata": {
|
|
13
|
+
"owaspCategory": "A07:2021 - Identification and Authentication Failures",
|
|
14
|
+
"cweId": "CWE-290",
|
|
15
|
+
"description": "Origin header can be easily spoofed by attackers and should not be used for authentication or authorization decisions. Use verified tokens, sessions, or cryptographic signatures instead.",
|
|
16
|
+
"impact": "High - Authentication bypass, unauthorized access",
|
|
17
|
+
"likelihood": "Medium",
|
|
18
|
+
"remediation": "Use secure authentication methods: JWT tokens, session cookies, API keys with cryptographic signatures. Origin header should only be used for CORS/CSRF protection, not for access control."
|
|
19
|
+
},
|
|
12
20
|
"patterns": {
|
|
13
21
|
"vulnerable": [
|
|
14
|
-
"req.headers.origin
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"Express middleware using origin for security"
|
|
22
|
+
"if (req.headers.origin === 'trusted.com') { authenticate() }",
|
|
23
|
+
"const isAuthorized = allowedOrigins.includes(origin)",
|
|
24
|
+
"if (origin.includes('admin')) { grantAccess() }",
|
|
25
|
+
"switch(origin) { case 'internal': allow() }"
|
|
19
26
|
],
|
|
20
27
|
"secure": [
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"Proper CORS configuration without auth reliance"
|
|
28
|
+
"Use for CORS: res.setHeader('Access-Control-Allow-Origin', origin)",
|
|
29
|
+
"Use for CSRF: if (allowedOrigins.includes(origin)) { /* CSRF check */ }",
|
|
30
|
+
"Use verified tokens: const user = await verifyJWT(req.headers.authorization)",
|
|
31
|
+
"Use sessions: const user = await getSessionUser(req.session.id)"
|
|
26
32
|
]
|
|
27
33
|
},
|
|
28
|
-
|
|
29
|
-
"configuration": {
|
|
30
|
-
"checkAuthContext": true,
|
|
31
|
-
"checkMiddleware": true,
|
|
32
|
-
"checkConditionals": true,
|
|
33
|
-
"checkCORSMixing": true,
|
|
34
|
-
"contextDepth": 3,
|
|
35
|
-
"ignoreComments": true
|
|
36
|
-
},
|
|
37
|
-
|
|
38
34
|
"examples": {
|
|
39
35
|
"violations": [
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"code": "const authMiddleware = (req, res, next) => { if (req.get('origin') === 'admin.com') next(); }",
|
|
46
|
-
"reason": "Middleware should not rely on Origin header for access control"
|
|
47
|
-
}
|
|
48
|
-
],
|
|
49
|
-
"valid": [
|
|
50
|
-
{
|
|
51
|
-
"code": "const token = req.headers.authorization; jwt.verify(token, secret, callback);",
|
|
52
|
-
"reason": "Proper JWT token authentication"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"code": "console.log('Request from:', req.headers.origin);",
|
|
56
|
-
"reason": "Using Origin header for logging only, not authentication"
|
|
57
|
-
}
|
|
58
|
-
]
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
"remediation": {
|
|
62
|
-
"recommendations": [
|
|
63
|
-
"Use JWT tokens for authentication",
|
|
64
|
-
"Implement session-based authentication",
|
|
65
|
-
"Use API keys for service authentication",
|
|
66
|
-
"Implement OAuth 2.0 for third-party authentication",
|
|
67
|
-
"Use proper CORS configuration without relying on it for authentication"
|
|
36
|
+
"if (req.headers.origin === 'admin.example.com') { req.user = adminUser; }",
|
|
37
|
+
"const hasAccess = trustedOrigins.includes(req.get('origin'))",
|
|
38
|
+
"if (origin.endsWith('.internal.com')) { bypassAuth() }"
|
|
68
39
|
],
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
40
|
+
"fixes": [
|
|
41
|
+
"const user = await verifyToken(req.headers.authorization)",
|
|
42
|
+
"const session = await validateSession(req.cookies.sessionId)",
|
|
43
|
+
"const apiKey = await verifyApiKey(req.headers['x-api-key'])"
|
|
73
44
|
]
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
"performance": {
|
|
77
|
-
"complexity": "O(n)",
|
|
78
|
-
"accuracy": {
|
|
79
|
-
"ast": 95,
|
|
80
|
-
"regex": 85
|
|
81
|
-
},
|
|
82
|
-
"falsePositiveRate": "< 5%",
|
|
83
|
-
"coverage": "High for TypeScript/JavaScript"
|
|
84
45
|
}
|
|
85
46
|
}
|