@sun-asterisk/sunlint 1.3.7 → 1.3.8
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/CHANGELOG.md +38 -0
- package/config/defaults/default.json +2 -1
- package/config/rule-analysis-strategies.js +20 -0
- package/config/rules/enhanced-rules-registry.json +190 -35
- package/core/file-targeting-service.js +83 -7
- package/package.json +1 -1
- package/rules/common/C065_one_behavior_per_test/analyzer.js +851 -0
- package/rules/common/C065_one_behavior_per_test/config.json +95 -0
- package/rules/security/S037_cache_headers/README.md +128 -0
- package/rules/security/S037_cache_headers/analyzer.js +263 -0
- package/rules/security/S037_cache_headers/config.json +50 -0
- package/rules/security/S037_cache_headers/regex-based-analyzer.js +463 -0
- package/rules/security/S037_cache_headers/symbol-based-analyzer.js +546 -0
- package/rules/security/S038_no_version_headers/README.md +234 -0
- package/rules/security/S038_no_version_headers/analyzer.js +262 -0
- package/rules/security/S038_no_version_headers/config.json +49 -0
- package/rules/security/S038_no_version_headers/regex-based-analyzer.js +339 -0
- package/rules/security/S038_no_version_headers/symbol-based-analyzer.js +375 -0
- package/rules/security/S039_no_session_tokens_in_url/README.md +198 -0
- package/rules/security/S039_no_session_tokens_in_url/analyzer.js +262 -0
- package/rules/security/S039_no_session_tokens_in_url/config.json +92 -0
- package/rules/security/S039_no_session_tokens_in_url/regex-based-analyzer.js +337 -0
- package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +436 -0
- package/rules/security/S049_short_validity_tokens/analyzer.js +175 -0
- package/rules/security/S049_short_validity_tokens/config.json +124 -0
- package/rules/security/S049_short_validity_tokens/regex-based-analyzer.js +295 -0
- package/rules/security/S049_short_validity_tokens/symbol-based-analyzer.js +389 -0
- package/rules/security/S051_password_length_policy/analyzer.js +410 -0
- package/rules/security/S051_password_length_policy/config.json +83 -0
- package/rules/security/S052_weak_otp_entropy/analyzer.js +403 -0
- package/rules/security/S052_weak_otp_entropy/config.json +57 -0
- package/rules/security/S054_no_default_accounts/README.md +129 -0
- package/rules/security/S054_no_default_accounts/analyzer.js +792 -0
- package/rules/security/S054_no_default_accounts/config.json +101 -0
- package/rules/security/S056_log_injection_protection/analyzer.js +242 -0
- package/rules/security/S056_log_injection_protection/config.json +148 -0
- package/rules/security/S056_log_injection_protection/regex-based-analyzer.js +120 -0
- package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +287 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# S039 - Do Not Pass Session Tokens via URL Parameters
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Rule S039 detects when session tokens, authentication tokens, JWT tokens, or other sensitive authentication data are passed as URL parameters instead of secure headers or request body. URL parameters are logged in web server logs, browser history, and can be exposed in referrer headers.
|
|
6
|
+
|
|
7
|
+
## Framework Support
|
|
8
|
+
|
|
9
|
+
This rule supports multiple web frameworks:
|
|
10
|
+
|
|
11
|
+
- **Express.js**: Parameter access via `req.query`, `req.params`
|
|
12
|
+
- **Next.js**: URL parameter access via `searchParams.get()`, `new URL().searchParams`
|
|
13
|
+
- **NestJS**: Parameter decorators `@Query()`, `@Param()`
|
|
14
|
+
- **Nuxt.js**: Server route parameter handling
|
|
15
|
+
- **Client-side**: Browser URL parameter access via `location.search`, `URLSearchParams`
|
|
16
|
+
|
|
17
|
+
## Security Impact
|
|
18
|
+
|
|
19
|
+
Passing session tokens via URL parameters creates several security vulnerabilities:
|
|
20
|
+
|
|
21
|
+
1. **Web Server Logs**: URL parameters are logged in web server access logs
|
|
22
|
+
2. **Browser History**: URLs with parameters are stored in browser history
|
|
23
|
+
3. **Referrer Headers**: URLs may be exposed when navigating to external sites
|
|
24
|
+
4. **Shared URLs**: Users may inadvertently share URLs containing tokens
|
|
25
|
+
5. **Cache Poisoning**: URLs with tokens may be cached by proxies or CDNs
|
|
26
|
+
6. **Analytics Tracking**: URL parameters are often sent to analytics services
|
|
27
|
+
|
|
28
|
+
## Rule Details
|
|
29
|
+
|
|
30
|
+
### Detected Token Parameter Names
|
|
31
|
+
|
|
32
|
+
The rule detects the following session token parameter patterns:
|
|
33
|
+
|
|
34
|
+
| Parameter Name | Variants |
|
|
35
|
+
| -------------- | --------------------------------------------------- |
|
|
36
|
+
| Session ID | `sessionId`, `session_id`, `session-id` |
|
|
37
|
+
| Session Token | `sessionToken`, `session_token`, `session-token` |
|
|
38
|
+
| Auth Token | `authToken`, `auth_token`, `auth-token` |
|
|
39
|
+
| Authorization | `authorization`, `bearer` |
|
|
40
|
+
| JWT | `jwt`, `jwtToken`, `jwt_token`, `jwt-token` |
|
|
41
|
+
| Access Token | `accessToken`, `access_token`, `access-token` |
|
|
42
|
+
| Refresh Token | `refreshToken`, `refresh_token`, `refresh-token` |
|
|
43
|
+
| API Key | `apiKey`, `api_key`, `api-key` |
|
|
44
|
+
| CSRF Token | `csrfToken`, `csrf_token`, `csrf-token` |
|
|
45
|
+
| XSRF Token | `xsrfToken`, `xsrf_token`, `xsrf-token` |
|
|
46
|
+
| Generic Token | `token`, `apiToken`, `api_token`, `api-token` |
|
|
47
|
+
| Session Key | `sid`, `sessionkey`, `session_key`, `session-key` |
|
|
48
|
+
| User Token | `userToken`, `user_token`, `user-token` |
|
|
49
|
+
| Auth Key | `authKey`, `auth_key`, `auth-key` |
|
|
50
|
+
| Security Token | `securityToken`, `security_token`, `security-token` |
|
|
51
|
+
|
|
52
|
+
### Detected Access Patterns
|
|
53
|
+
|
|
54
|
+
✅ **Secure Alternatives:**
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Use Authorization header
|
|
58
|
+
app.get("/api/user", (req, res) => {
|
|
59
|
+
const token = req.headers.authorization?.replace("Bearer ", "");
|
|
60
|
+
// Process token securely
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Use request body for POST requests
|
|
64
|
+
app.post("/api/authenticate", (req, res) => {
|
|
65
|
+
const { sessionToken } = req.body;
|
|
66
|
+
// Process token securely
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Use secure cookies
|
|
70
|
+
app.get("/api/profile", (req, res) => {
|
|
71
|
+
const sessionId = req.cookies.sessionId;
|
|
72
|
+
// Process session ID securely
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
❌ **Violation Examples:**
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Express.js violations
|
|
80
|
+
app.get('/api/user', (req, res) => {
|
|
81
|
+
const sessionToken = req.query.sessionToken; // ❌ Token in URL
|
|
82
|
+
const authKey = req.params.authKey; // ❌ Token in URL
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Object destructuring violations
|
|
86
|
+
app.get('/api/data', (req, res) => {
|
|
87
|
+
const { jwt, apiKey } = req.query; // ❌ Multiple tokens in URL
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// NestJS violations
|
|
91
|
+
@Get('user')
|
|
92
|
+
getUserData(@Query('sessionToken') token: string) { // ❌ Token in URL
|
|
93
|
+
// ...
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@Get('profile/:authToken')
|
|
97
|
+
getProfile(@Param('authToken') token: string) { // ❌ Token in URL path
|
|
98
|
+
// ...
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Next.js violations
|
|
102
|
+
export async function GET(request: Request) {
|
|
103
|
+
const url = new URL(request.url);
|
|
104
|
+
const jwt = url.searchParams.get('jwt'); // ❌ Token in URL
|
|
105
|
+
// ...
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Client-side violations
|
|
109
|
+
const params = new URLSearchParams(location.search);
|
|
110
|
+
const sessionId = params.get('sessionId'); // ❌ Token in URL
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Framework-Specific Detection
|
|
114
|
+
|
|
115
|
+
#### Express.js
|
|
116
|
+
|
|
117
|
+
- `req.query.tokenName`
|
|
118
|
+
- `req.params.tokenName`
|
|
119
|
+
- Object destructuring: `{ tokenName } = req.query`
|
|
120
|
+
|
|
121
|
+
#### NestJS
|
|
122
|
+
|
|
123
|
+
- `@Query('tokenName')`
|
|
124
|
+
- `@Param('tokenName')`
|
|
125
|
+
|
|
126
|
+
#### Next.js
|
|
127
|
+
|
|
128
|
+
- `searchParams.get('tokenName')`
|
|
129
|
+
- `new URL().searchParams.get('tokenName')`
|
|
130
|
+
- `URLSearchParams().get('tokenName')`
|
|
131
|
+
|
|
132
|
+
#### Client-side JavaScript
|
|
133
|
+
|
|
134
|
+
- `location.search` with subsequent parameter parsing
|
|
135
|
+
- `URLSearchParams(location.search).get('tokenName')`
|
|
136
|
+
- `window.location.search` parameter access
|
|
137
|
+
|
|
138
|
+
## Best Practices
|
|
139
|
+
|
|
140
|
+
### ✅ Secure Token Transmission
|
|
141
|
+
|
|
142
|
+
1. **Authorization Headers**:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Send token in Authorization header
|
|
146
|
+
fetch("/api/data", {
|
|
147
|
+
headers: {
|
|
148
|
+
Authorization: `Bearer ${token}`,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
2. **Request Body**:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Send token in request body for POST requests
|
|
157
|
+
fetch("/api/authenticate", {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: { "Content-Type": "application/json" },
|
|
160
|
+
body: JSON.stringify({ sessionToken: token }),
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
3. **Secure Cookies**:
|
|
165
|
+
```typescript
|
|
166
|
+
// Store session in secure, HTTP-only cookies
|
|
167
|
+
res.cookie("sessionId", sessionId, {
|
|
168
|
+
httpOnly: true,
|
|
169
|
+
secure: true,
|
|
170
|
+
sameSite: "strict",
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### ❌ Avoid These Patterns
|
|
175
|
+
|
|
176
|
+
1. **URL Query Parameters**: Never pass tokens as `?token=abc123`
|
|
177
|
+
2. **URL Path Parameters**: Never embed tokens in URL paths `/api/user/abc123`
|
|
178
|
+
3. **Fragment Identifiers**: Avoid `#token=abc123` (though less logged)
|
|
179
|
+
4. **Form GET Methods**: Don't use GET forms for token submission
|
|
180
|
+
|
|
181
|
+
## Configuration
|
|
182
|
+
|
|
183
|
+
The rule can be configured in `config.json`:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"validation": {
|
|
188
|
+
"sessionTokenParams": ["custom-token", "my-auth-key"],
|
|
189
|
+
"frameworks": ["express", "nestjs", "nextjs"]
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Related Security Rules
|
|
195
|
+
|
|
196
|
+
- **S027**: No hardcoded secrets - Prevents tokens in source code
|
|
197
|
+
- **S031**: Secure session cookies - Ensures proper cookie security
|
|
198
|
+
- **S016**: No sensitive data in query strings - Broader sensitive data protection
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S039 Main Analyzer - Do not pass Session Tokens via URL parameters
|
|
3
|
+
* Primary: Symbol-based analysis (when available)
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* Command: node cli.js --rule=S039 --input=examples/rule-test-fixtures/rules/S039_no_session_tokens_in_url --engine=heuristic
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const S039SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
9
|
+
const S039RegexBasedAnalyzer = require("./regex-based-analyzer.js");
|
|
10
|
+
|
|
11
|
+
class S039Analyzer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
14
|
+
console.log(`🔧 [S039] Constructor called with options:`, !!options);
|
|
15
|
+
console.log(
|
|
16
|
+
`🔧 [S039] Options type:`,
|
|
17
|
+
typeof options,
|
|
18
|
+
Object.keys(options || {})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.ruleId = "S039";
|
|
23
|
+
this.ruleName = "Do not pass Session Tokens via URL parameters";
|
|
24
|
+
this.description =
|
|
25
|
+
"Detects when session tokens, authentication tokens, JWT tokens, or other sensitive authentication data are passed as URL parameters instead of secure headers or body. URL parameters are logged in web server logs, browser history, and can be exposed in referrer headers.";
|
|
26
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
27
|
+
this.verbose = options.verbose || false;
|
|
28
|
+
|
|
29
|
+
this.config = {
|
|
30
|
+
useSymbolBased: true,
|
|
31
|
+
fallbackToRegex: true,
|
|
32
|
+
regexBasedOnly: false,
|
|
33
|
+
prioritizeSymbolic: true, // Prefer symbol-based when available
|
|
34
|
+
fallbackToSymbol: true, // Allow symbol analysis even without semantic engine
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
this.symbolAnalyzer = new S039SymbolBasedAnalyzer(this.semanticEngine);
|
|
39
|
+
if (process.env.SUNLINT_DEBUG)
|
|
40
|
+
console.log(`🔧 [S039] Symbol analyzer created successfully`);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(`🔧 [S039] Error creating symbol analyzer:`, error);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
this.regexAnalyzer = new S039RegexBasedAnalyzer(this.semanticEngine);
|
|
47
|
+
if (process.env.SUNLINT_DEBUG)
|
|
48
|
+
console.log(`🔧 [S039] Regex analyzer created successfully`);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`🔧 [S039] Error creating regex analyzer:`, error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async initialize(semanticEngine) {
|
|
55
|
+
this.semanticEngine = semanticEngine;
|
|
56
|
+
if (process.env.SUNLINT_DEBUG)
|
|
57
|
+
console.log(`🔧 [S039] Main analyzer initializing...`);
|
|
58
|
+
|
|
59
|
+
if (this.symbolAnalyzer)
|
|
60
|
+
await this.symbolAnalyzer.initialize?.(semanticEngine);
|
|
61
|
+
if (this.regexAnalyzer)
|
|
62
|
+
await this.regexAnalyzer.initialize?.(semanticEngine);
|
|
63
|
+
if (this.regexAnalyzer) this.regexAnalyzer.cleanup?.();
|
|
64
|
+
|
|
65
|
+
if (process.env.SUNLINT_DEBUG)
|
|
66
|
+
console.log(`🔧 [S039] Main analyzer initialized successfully`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
analyzeSingle(filePath, options = {}) {
|
|
70
|
+
if (process.env.SUNLINT_DEBUG)
|
|
71
|
+
console.log(`🔍 [S039] analyzeSingle() called for: ${filePath}`);
|
|
72
|
+
return this.analyze([filePath], "typescript", options);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async analyze(files, language, options = {}) {
|
|
76
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
77
|
+
console.log(
|
|
78
|
+
`🔧 [S039] analyze() method called with ${files.length} files, language: ${language}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const violations = [];
|
|
83
|
+
for (const filePath of files) {
|
|
84
|
+
try {
|
|
85
|
+
if (process.env.SUNLINT_DEBUG)
|
|
86
|
+
console.log(`🔧 [S039] Processing file: ${filePath}`);
|
|
87
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
88
|
+
violations.push(...fileViolations);
|
|
89
|
+
if (process.env.SUNLINT_DEBUG)
|
|
90
|
+
console.log(
|
|
91
|
+
`🔧 [S039] File ${filePath}: Found ${fileViolations.length} violations`
|
|
92
|
+
);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn(
|
|
95
|
+
`⚠ [S039] Analysis failed for ${filePath}:`,
|
|
96
|
+
error.message
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (process.env.SUNLINT_DEBUG)
|
|
102
|
+
console.log(`🔧 [S039] Total violations found: ${violations.length}`);
|
|
103
|
+
return violations;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async analyzeFile(filePath, options = {}) {
|
|
107
|
+
if (process.env.SUNLINT_DEBUG)
|
|
108
|
+
console.log(`🔍 [S039] analyzeFile() called for: ${filePath}`);
|
|
109
|
+
const violationMap = new Map();
|
|
110
|
+
|
|
111
|
+
// Try symbol-based analysis first when semantic engine is available OR when explicitly enabled
|
|
112
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
113
|
+
console.log(
|
|
114
|
+
`🔧 [S039] Symbol check: useSymbolBased=${
|
|
115
|
+
this.config.useSymbolBased
|
|
116
|
+
}, semanticEngine=${!!this.semanticEngine}, project=${!!this
|
|
117
|
+
.semanticEngine?.project}, initialized=${!!this.semanticEngine
|
|
118
|
+
?.initialized}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const canUseSymbol =
|
|
123
|
+
this.config.useSymbolBased &&
|
|
124
|
+
((this.semanticEngine?.project && this.semanticEngine?.initialized) ||
|
|
125
|
+
(!this.semanticEngine && this.config.fallbackToSymbol !== false));
|
|
126
|
+
|
|
127
|
+
if (canUseSymbol) {
|
|
128
|
+
try {
|
|
129
|
+
if (process.env.SUNLINT_DEBUG)
|
|
130
|
+
console.log(`🔧 [S039] Trying symbol-based analysis...`);
|
|
131
|
+
|
|
132
|
+
let sourceFile = null;
|
|
133
|
+
if (this.semanticEngine?.project) {
|
|
134
|
+
sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
135
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
136
|
+
console.log(
|
|
137
|
+
`🔧 [S039] Checked existing semantic engine project: sourceFile=${!!sourceFile}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!sourceFile) {
|
|
143
|
+
// Create a minimal ts-morph project for this analysis
|
|
144
|
+
if (process.env.SUNLINT_DEBUG)
|
|
145
|
+
console.log(
|
|
146
|
+
`🔧 [S039] Creating temporary ts-morph project for: ${filePath}`
|
|
147
|
+
);
|
|
148
|
+
try {
|
|
149
|
+
const fs = require("fs");
|
|
150
|
+
const path = require("path");
|
|
151
|
+
const { Project } = require("ts-morph");
|
|
152
|
+
|
|
153
|
+
// Check if file exists and read content
|
|
154
|
+
if (!fs.existsSync(filePath)) {
|
|
155
|
+
throw new Error(`File not found: ${filePath}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
159
|
+
const fileName = path.basename(filePath);
|
|
160
|
+
|
|
161
|
+
const tempProject = new Project({
|
|
162
|
+
useInMemoryFileSystem: true,
|
|
163
|
+
compilerOptions: {
|
|
164
|
+
allowJs: true,
|
|
165
|
+
allowSyntheticDefaultImports: true,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Add file content to in-memory project
|
|
170
|
+
sourceFile = tempProject.createSourceFile(fileName, fileContent);
|
|
171
|
+
if (process.env.SUNLINT_DEBUG)
|
|
172
|
+
console.log(
|
|
173
|
+
`🔧 [S039] Temporary project created successfully with file: ${fileName}`
|
|
174
|
+
);
|
|
175
|
+
} catch (projectError) {
|
|
176
|
+
if (process.env.SUNLINT_DEBUG)
|
|
177
|
+
console.log(
|
|
178
|
+
`🔧 [S039] Failed to create temporary project:`,
|
|
179
|
+
projectError.message
|
|
180
|
+
);
|
|
181
|
+
throw projectError;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (sourceFile) {
|
|
186
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
187
|
+
sourceFile,
|
|
188
|
+
filePath
|
|
189
|
+
);
|
|
190
|
+
symbolViolations.forEach((v) => {
|
|
191
|
+
const key = `${v.line}:${v.column}:${v.message}`;
|
|
192
|
+
if (!violationMap.has(key)) violationMap.set(key, v);
|
|
193
|
+
});
|
|
194
|
+
if (process.env.SUNLINT_DEBUG)
|
|
195
|
+
console.log(
|
|
196
|
+
`🔧 [S039] Symbol analysis completed: ${symbolViolations.length} violations`
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// If symbol-based found violations or prioritizeSymbolic is true, skip regex
|
|
200
|
+
if (this.config.prioritizeSymbolic && symbolViolations.length >= 0) {
|
|
201
|
+
const finalViolations = Array.from(violationMap.values()).map(
|
|
202
|
+
(v) => ({
|
|
203
|
+
...v,
|
|
204
|
+
filePath,
|
|
205
|
+
file: filePath,
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
if (process.env.SUNLINT_DEBUG)
|
|
209
|
+
console.log(
|
|
210
|
+
`🔧 [S039] Symbol-based analysis prioritized: ${finalViolations.length} violations`
|
|
211
|
+
);
|
|
212
|
+
return finalViolations;
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
if (process.env.SUNLINT_DEBUG)
|
|
216
|
+
console.log(
|
|
217
|
+
`🔧 [S039] No source file found, skipping symbol analysis`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.warn(`⚠ [S039] Symbol analysis failed:`, error.message);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Fallback to regex-based analysis
|
|
226
|
+
if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
|
|
227
|
+
try {
|
|
228
|
+
if (process.env.SUNLINT_DEBUG)
|
|
229
|
+
console.log(`🔧 [S039] Trying regex-based analysis...`);
|
|
230
|
+
const regexViolations = await this.regexAnalyzer.analyze(filePath);
|
|
231
|
+
regexViolations.forEach((v) => {
|
|
232
|
+
const key = `${v.line}:${v.column}:${v.message}`;
|
|
233
|
+
if (!violationMap.has(key)) violationMap.set(key, v);
|
|
234
|
+
});
|
|
235
|
+
if (process.env.SUNLINT_DEBUG)
|
|
236
|
+
console.log(
|
|
237
|
+
`🔧 [S039] Regex analysis completed: ${regexViolations.length} violations`
|
|
238
|
+
);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.warn(`⚠ [S039] Regex analysis failed:`, error.message);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const finalViolations = Array.from(violationMap.values()).map((v) => ({
|
|
245
|
+
...v,
|
|
246
|
+
filePath,
|
|
247
|
+
file: filePath,
|
|
248
|
+
}));
|
|
249
|
+
if (process.env.SUNLINT_DEBUG)
|
|
250
|
+
console.log(
|
|
251
|
+
`🔧 [S039] File analysis completed: ${finalViolations.length} unique violations`
|
|
252
|
+
);
|
|
253
|
+
return finalViolations;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
cleanup() {
|
|
257
|
+
if (this.symbolAnalyzer?.cleanup) this.symbolAnalyzer.cleanup();
|
|
258
|
+
if (this.regexAnalyzer?.cleanup) this.regexAnalyzer.cleanup();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = S039Analyzer;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S039",
|
|
3
|
+
"name": "Do not pass Session Tokens via URL parameters",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"description": "S039 - Detects when session tokens, authentication tokens, JWT tokens, or other sensitive authentication data are passed as URL parameters instead of secure headers or request body. URL parameters are logged in web server logs, browser history, and can be exposed in referrer headers.",
|
|
6
|
+
"severity": "warning",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"semantic": {
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"priority": "high",
|
|
11
|
+
"fallback": "heuristic"
|
|
12
|
+
},
|
|
13
|
+
"patterns": {
|
|
14
|
+
"include": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"],
|
|
15
|
+
"exclude": [
|
|
16
|
+
"**/*.test.js",
|
|
17
|
+
"**/*.test.ts",
|
|
18
|
+
"**/*.spec.js",
|
|
19
|
+
"**/*.spec.ts",
|
|
20
|
+
"**/node_modules/**",
|
|
21
|
+
"**/dist/**",
|
|
22
|
+
"**/build/**"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"analysis": {
|
|
26
|
+
"approach": "symbol-based-primary",
|
|
27
|
+
"fallback": "regex-based",
|
|
28
|
+
"depth": 1,
|
|
29
|
+
"timeout": 4000
|
|
30
|
+
},
|
|
31
|
+
"validation": {
|
|
32
|
+
"sessionTokenParams": [
|
|
33
|
+
"sessionId",
|
|
34
|
+
"session_id",
|
|
35
|
+
"session-id",
|
|
36
|
+
"sessionToken",
|
|
37
|
+
"session_token",
|
|
38
|
+
"session-token",
|
|
39
|
+
"authToken",
|
|
40
|
+
"auth_token",
|
|
41
|
+
"auth-token",
|
|
42
|
+
"authorization",
|
|
43
|
+
"bearer",
|
|
44
|
+
"jwt",
|
|
45
|
+
"jwtToken",
|
|
46
|
+
"jwt_token",
|
|
47
|
+
"jwt-token",
|
|
48
|
+
"accessToken",
|
|
49
|
+
"access_token",
|
|
50
|
+
"access-token",
|
|
51
|
+
"refreshToken",
|
|
52
|
+
"refresh_token",
|
|
53
|
+
"refresh-token",
|
|
54
|
+
"apiKey",
|
|
55
|
+
"api_key",
|
|
56
|
+
"api-key",
|
|
57
|
+
"csrfToken",
|
|
58
|
+
"csrf_token",
|
|
59
|
+
"csrf-token",
|
|
60
|
+
"xsrfToken",
|
|
61
|
+
"xsrf_token",
|
|
62
|
+
"xsrf-token",
|
|
63
|
+
"token",
|
|
64
|
+
"apiToken",
|
|
65
|
+
"api_token",
|
|
66
|
+
"api-token",
|
|
67
|
+
"sid",
|
|
68
|
+
"sessionkey",
|
|
69
|
+
"session_key",
|
|
70
|
+
"session-key",
|
|
71
|
+
"userToken",
|
|
72
|
+
"user_token",
|
|
73
|
+
"user-token",
|
|
74
|
+
"authKey",
|
|
75
|
+
"auth_key",
|
|
76
|
+
"auth-key",
|
|
77
|
+
"securityToken",
|
|
78
|
+
"security_token",
|
|
79
|
+
"security-token"
|
|
80
|
+
],
|
|
81
|
+
"urlAccessPatterns": [
|
|
82
|
+
"req.query",
|
|
83
|
+
"req.params",
|
|
84
|
+
"searchParams.get",
|
|
85
|
+
"@Query",
|
|
86
|
+
"@Param",
|
|
87
|
+
"URLSearchParams",
|
|
88
|
+
"location.search"
|
|
89
|
+
],
|
|
90
|
+
"frameworks": ["express", "nestjs", "nextjs", "nuxtjs"]
|
|
91
|
+
}
|
|
92
|
+
}
|