@sun-asterisk/sunlint 1.3.16 → 1.3.17
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/rule-analysis-strategies.js +3 -3
- package/config/rules/enhanced-rules-registry.json +40 -20
- package/core/cli-action-handler.js +2 -2
- package/core/config-merger.js +28 -6
- package/core/constants/defaults.js +1 -1
- package/core/file-targeting-service.js +72 -4
- package/core/output-service.js +21 -4
- package/engines/heuristic-engine.js +5 -0
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/README.md +115 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
- package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
- package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
- package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
- package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
- package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
- package/rules/common/C008/analyzer.js +40 -0
- package/rules/common/C008/config.json +20 -0
- package/rules/common/C008/ts-morph-analyzer.js +1067 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
- package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
- package/rules/common/C033_separate_service_repository/README.md +131 -20
- package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
- package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
- package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
- package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
- package/rules/docs/C002_no_duplicate_code.md +276 -11
- package/rules/index.js +5 -1
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
- package/rules/security/S010_no_insecure_encryption/README.md +78 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
- package/rules/security/S013_tls_enforcement/README.md +51 -0
- package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
- package/rules/security/S013_tls_enforcement/config.json +41 -0
- package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
- package/rules/security/S014_tls_version_enforcement/README.md +354 -0
- package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
- package/rules/security/S014_tls_version_enforcement/config.json +56 -0
- package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
- package/rules/security/S055_content_type_validation/analyzer.js +121 -279
- package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
- package/rules/tests/C002_no_duplicate_code.test.js +111 -22
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# S014 - Enforce TLS 1.2 or 1.3 Only
|
|
2
|
+
|
|
3
|
+
## 📋 Overview
|
|
4
|
+
|
|
5
|
+
**Rule ID:** S014
|
|
6
|
+
**Category:** Security
|
|
7
|
+
**Severity:** Error
|
|
8
|
+
**Enabled:** Yes
|
|
9
|
+
|
|
10
|
+
Ensures that only secure TLS versions (1.2 or 1.3) are used in HTTPS server configurations and client connections. Detects usage of insecure SSL/TLS versions (SSLv2, SSLv3, TLS 1.0, TLS 1.1) which have known vulnerabilities.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 🎯 Detection Scope
|
|
15
|
+
|
|
16
|
+
### Insecure TLS/SSL Versions ❌
|
|
17
|
+
|
|
18
|
+
| Version | Status | Reason |
|
|
19
|
+
| ----------- | ----------- | ----------------------------------------------- |
|
|
20
|
+
| **SSLv2** | ❌ Insecure | Deprecated (RFC 6176), multiple vulnerabilities |
|
|
21
|
+
| **SSLv3** | ❌ Insecure | POODLE attack (CVE-2014-3566) |
|
|
22
|
+
| **TLS 1.0** | ❌ Insecure | BEAST attack, deprecated by PCI DSS |
|
|
23
|
+
| **TLS 1.1** | ❌ Insecure | Weak ciphers, deprecated by major browsers |
|
|
24
|
+
|
|
25
|
+
### Secure TLS Versions ✅
|
|
26
|
+
|
|
27
|
+
| Version | Status | Recommendation |
|
|
28
|
+
| ----------- | --------- | --------------------------- |
|
|
29
|
+
| **TLS 1.2** | ✅ Secure | Minimum recommended version |
|
|
30
|
+
| **TLS 1.3** | ✅ Secure | Latest, most secure version |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🔍 What This Rule Detects
|
|
35
|
+
|
|
36
|
+
### 1. HTTPS Server Configuration
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// ❌ BAD: Using TLS 1.0
|
|
40
|
+
https.createServer(
|
|
41
|
+
{
|
|
42
|
+
minVersion: "TLSv1", // Insecure!
|
|
43
|
+
cert: cert,
|
|
44
|
+
key: key,
|
|
45
|
+
},
|
|
46
|
+
app
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// ✅ GOOD: Using TLS 1.2+
|
|
50
|
+
https.createServer(
|
|
51
|
+
{
|
|
52
|
+
minVersion: "TLSv1.2", // Secure
|
|
53
|
+
cert: cert,
|
|
54
|
+
key: key,
|
|
55
|
+
},
|
|
56
|
+
app
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Secure Protocol Settings
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// ❌ BAD: Using SSLv3
|
|
64
|
+
const options = {
|
|
65
|
+
secureProtocol: "SSLv3_method", // Insecure!
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ✅ GOOD: Using TLS 1.2
|
|
69
|
+
const options = {
|
|
70
|
+
secureProtocol: "TLSv1_2_method", // Secure
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3. Client Configuration
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// ❌ BAD: axios with TLS 1.0
|
|
78
|
+
const agent = new https.Agent({
|
|
79
|
+
minVersion: "TLSv1", // Insecure!
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ✅ GOOD: axios with TLS 1.2+
|
|
83
|
+
const agent = new https.Agent({
|
|
84
|
+
minVersion: "TLSv1.2", // Secure
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 4. Framework-Specific Configuration
|
|
89
|
+
|
|
90
|
+
#### Express.js
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// ❌ BAD
|
|
94
|
+
const server = https.createServer(
|
|
95
|
+
{
|
|
96
|
+
secureProtocol: "TLSv1_method", // Insecure!
|
|
97
|
+
},
|
|
98
|
+
app
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// ✅ GOOD
|
|
102
|
+
const server = https.createServer(
|
|
103
|
+
{
|
|
104
|
+
minVersion: "TLSv1.2", // Secure
|
|
105
|
+
},
|
|
106
|
+
app
|
|
107
|
+
);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Next.js (next.config.js)
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
// ❌ BAD
|
|
114
|
+
module.exports = {
|
|
115
|
+
server: {
|
|
116
|
+
https: {
|
|
117
|
+
minVersion: "TLSv1.0", // Insecure!
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// ✅ GOOD
|
|
123
|
+
module.exports = {
|
|
124
|
+
server: {
|
|
125
|
+
https: {
|
|
126
|
+
minVersion: "TLSv1.2", // Secure
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### NestJS
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// ❌ BAD
|
|
136
|
+
const httpsOptions = {
|
|
137
|
+
secureProtocol: "SSLv3_method", // Insecure!
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ✅ GOOD
|
|
141
|
+
const httpsOptions = {
|
|
142
|
+
minVersion: "TLSv1.2", // Secure
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 🚨 Security Impact
|
|
149
|
+
|
|
150
|
+
### Critical Vulnerabilities
|
|
151
|
+
|
|
152
|
+
| Attack | Affected Versions | CVE |
|
|
153
|
+
| -------------- | ----------------- | ------------- |
|
|
154
|
+
| **POODLE** | SSLv3 | CVE-2014-3566 |
|
|
155
|
+
| **BEAST** | TLS 1.0 | CVE-2011-3389 |
|
|
156
|
+
| **CRIME** | TLS 1.0/1.1 | CVE-2012-4929 |
|
|
157
|
+
| **BREACH** | TLS 1.0/1.1 | CVE-2013-3587 |
|
|
158
|
+
| **Heartbleed** | TLS 1.0/1.1 | CVE-2014-0160 |
|
|
159
|
+
|
|
160
|
+
### Compliance Requirements
|
|
161
|
+
|
|
162
|
+
- ✅ **PCI DSS 3.2+:** Requires TLS 1.2+ (TLS 1.0/1.1 banned)
|
|
163
|
+
- ✅ **NIST SP 800-52:** Recommends TLS 1.2+ only
|
|
164
|
+
- ✅ **HIPAA:** Requires strong encryption (TLS 1.2+)
|
|
165
|
+
- ✅ **GDPR:** Requires appropriate security measures
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 📝 Examples
|
|
170
|
+
|
|
171
|
+
### Server Configuration
|
|
172
|
+
|
|
173
|
+
#### ❌ Violations
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// Violation 1: SSLv3
|
|
177
|
+
const server = https.createServer(
|
|
178
|
+
{
|
|
179
|
+
secureProtocol: "SSLv3_method",
|
|
180
|
+
},
|
|
181
|
+
app
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Violation 2: TLS 1.0
|
|
185
|
+
const options = {
|
|
186
|
+
minVersion: "TLSv1",
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Violation 3: TLS 1.1
|
|
190
|
+
https.createServer(
|
|
191
|
+
{
|
|
192
|
+
maxVersion: "TLSv1.1",
|
|
193
|
+
},
|
|
194
|
+
app
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Violation 4: String version
|
|
198
|
+
const config = {
|
|
199
|
+
protocol: "TLSv1.0",
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### ✅ Compliant Code
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Good: TLS 1.2
|
|
207
|
+
const server = https.createServer(
|
|
208
|
+
{
|
|
209
|
+
minVersion: "TLSv1.2",
|
|
210
|
+
maxVersion: "TLSv1.3",
|
|
211
|
+
},
|
|
212
|
+
app
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Good: TLS 1.3
|
|
216
|
+
const options = {
|
|
217
|
+
secureProtocol: "TLSv1_3_method",
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Good: Modern defaults
|
|
221
|
+
https.createServer(
|
|
222
|
+
{
|
|
223
|
+
minVersion: "TLSv1.2",
|
|
224
|
+
},
|
|
225
|
+
app
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Client Configuration
|
|
230
|
+
|
|
231
|
+
#### ❌ Violations
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// Violation: axios with TLS 1.0
|
|
235
|
+
const httpsAgent = new https.Agent({
|
|
236
|
+
minVersion: "TLSv1",
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
axios.get("https://api.example.com", {
|
|
240
|
+
httpsAgent,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Violation: fetch with insecure agent
|
|
244
|
+
const agent = new https.Agent({
|
|
245
|
+
secureProtocol: "TLSv1_method",
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### ✅ Compliant Code
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Good: axios with TLS 1.2+
|
|
253
|
+
const httpsAgent = new https.Agent({
|
|
254
|
+
minVersion: "TLSv1.2",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
axios.get("https://api.example.com", {
|
|
258
|
+
httpsAgent,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Good: Modern TLS
|
|
262
|
+
const agent = new https.Agent({
|
|
263
|
+
minVersion: "TLSv1.2",
|
|
264
|
+
maxVersion: "TLSv1.3",
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 🔧 How to Fix
|
|
271
|
+
|
|
272
|
+
### 1. Update Server Configuration
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Before
|
|
276
|
+
const server = https.createServer(
|
|
277
|
+
{
|
|
278
|
+
minVersion: "TLSv1", // ❌
|
|
279
|
+
},
|
|
280
|
+
app
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// After
|
|
284
|
+
const server = https.createServer(
|
|
285
|
+
{
|
|
286
|
+
minVersion: "TLSv1.2", // ✅
|
|
287
|
+
},
|
|
288
|
+
app
|
|
289
|
+
);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 2. Update Client Agents
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Before
|
|
296
|
+
const agent = new https.Agent({
|
|
297
|
+
secureProtocol: "TLSv1_method", // ❌
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// After
|
|
301
|
+
const agent = new https.Agent({
|
|
302
|
+
minVersion: "TLSv1.2", // ✅
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### 3. Framework Configuration
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// Before
|
|
310
|
+
const httpsOptions = {
|
|
311
|
+
secureProtocol: "SSLv3_method", // ❌
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// After
|
|
315
|
+
const httpsOptions = {
|
|
316
|
+
minVersion: "TLSv1.2", // ✅
|
|
317
|
+
maxVersion: "TLSv1.3",
|
|
318
|
+
};
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## ⚙️ Configuration
|
|
324
|
+
|
|
325
|
+
### Pattern Detection
|
|
326
|
+
|
|
327
|
+
- `minVersion: 'TLSv1'` - Detects minimum version settings
|
|
328
|
+
- `maxVersion: 'TLSv1.1'` - Detects maximum version restrictions
|
|
329
|
+
- `secureProtocol: 'SSLv3_method'` - Detects protocol methods
|
|
330
|
+
- `protocol: 'TLSv1.0'` - Detects string protocols
|
|
331
|
+
|
|
332
|
+
### Supported Frameworks
|
|
333
|
+
|
|
334
|
+
- Express.js
|
|
335
|
+
- Next.js
|
|
336
|
+
- NestJS
|
|
337
|
+
- Nuxt.js
|
|
338
|
+
- Fastify
|
|
339
|
+
- Koa
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## 📚 References
|
|
344
|
+
|
|
345
|
+
- [RFC 8996 - Deprecating TLS 1.0 and 1.1](https://datatracker.ietf.org/doc/html/rfc8996)
|
|
346
|
+
- [PCI DSS Requirements](https://www.pcisecuritystandards.org/)
|
|
347
|
+
- [NIST SP 800-52 Rev. 2](https://csrc.nist.gov/publications/detail/sp/800-52/rev-2/final)
|
|
348
|
+
- [OWASP Transport Layer Protection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html)
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
**Created:** October 10, 2025
|
|
353
|
+
**Version:** 1.0.0
|
|
354
|
+
**Status:** Active
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S014 - Enforce TLS 1.2 or 1.3 Only
|
|
3
|
+
*
|
|
4
|
+
* Main analyzer using symbol-based analysis to detect insecure TLS/SSL version usage.
|
|
5
|
+
*/
|
|
6
|
+
// Command: node cli.js --rule=S014 --input=examples/rule-test-fixtures/rules/S014_tls_version_enforcement --engine=heuristic
|
|
7
|
+
|
|
8
|
+
const S014SymbolBasedAnalyzer = require("./symbol-based-analyzer");
|
|
9
|
+
|
|
10
|
+
class S014Analyzer {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.ruleId = "S014";
|
|
13
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
14
|
+
this.verbose = options.verbose || false;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
this.symbolAnalyzer = new S014SymbolBasedAnalyzer(this.semanticEngine);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.warn(`⚠ [S014] Failed to create symbol analyzer: ${e.message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async initialize(semanticEngine) {
|
|
24
|
+
this.semanticEngine = semanticEngine;
|
|
25
|
+
if (this.symbolAnalyzer && this.symbolAnalyzer.initialize) {
|
|
26
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
analyzeSingle(filePath, options = {}) {
|
|
31
|
+
return this.analyze([filePath], "typescript", options);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async analyze(files, language, options = {}) {
|
|
35
|
+
const violations = [];
|
|
36
|
+
for (const filePath of files) {
|
|
37
|
+
try {
|
|
38
|
+
const vs = await this.analyzeFile(filePath, options);
|
|
39
|
+
violations.push(...vs);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.warn(`⚠ [S014] Error analyzing ${filePath}: ${e.message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return violations;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async analyzeFile(filePath, options = {}) {
|
|
48
|
+
const violationMap = new Map();
|
|
49
|
+
|
|
50
|
+
if (!this.symbolAnalyzer) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
let sourceFile = null;
|
|
56
|
+
if (this.semanticEngine?.project) {
|
|
57
|
+
sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!sourceFile) {
|
|
61
|
+
// Create temporary ts-morph source file
|
|
62
|
+
const fs = require("fs");
|
|
63
|
+
const path = require("path");
|
|
64
|
+
const { Project } = require("ts-morph");
|
|
65
|
+
if (!fs.existsSync(filePath)) {
|
|
66
|
+
throw new Error(`File not found: ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
69
|
+
const tmp = new Project({
|
|
70
|
+
useInMemoryFileSystem: true,
|
|
71
|
+
compilerOptions: { allowJs: true },
|
|
72
|
+
});
|
|
73
|
+
sourceFile = tmp.createSourceFile(path.basename(filePath), content);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (sourceFile) {
|
|
77
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
78
|
+
sourceFile,
|
|
79
|
+
filePath
|
|
80
|
+
);
|
|
81
|
+
symbolViolations.forEach((v) => {
|
|
82
|
+
// Use only line number as key to deduplicate multiple detections on same line
|
|
83
|
+
const key = `${v.line}`;
|
|
84
|
+
if (!violationMap.has(key)) {
|
|
85
|
+
violationMap.set(key, v);
|
|
86
|
+
} else {
|
|
87
|
+
// Keep the more specific message if available
|
|
88
|
+
const existing = violationMap.get(key);
|
|
89
|
+
if (v.message.length > existing.message.length) {
|
|
90
|
+
violationMap.set(key, v);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (this.verbose) {
|
|
97
|
+
console.warn(
|
|
98
|
+
`⚠ [S014] Symbol analysis failed for ${filePath}:`,
|
|
99
|
+
err.message
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Array.from(violationMap.values()).map((v) => ({
|
|
105
|
+
...v,
|
|
106
|
+
filePath,
|
|
107
|
+
file: filePath,
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async cleanup() {
|
|
112
|
+
if (this.symbolAnalyzer) {
|
|
113
|
+
await this.symbolAnalyzer.cleanup?.();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = S014Analyzer;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S014",
|
|
3
|
+
"name": "Enforce TLS 1.2 or 1.3 only",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"description": "S014 - Ensure only TLS 1.2 or TLS 1.3 protocols are used. Detects usage of insecure TLS/SSL versions (SSL v2/v3, TLS 1.0, TLS 1.1) in HTTPS server configurations, client requests, and framework settings.",
|
|
6
|
+
"severity": "error",
|
|
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
|
+
"insecureVersions": [
|
|
33
|
+
"SSLv2",
|
|
34
|
+
"SSLv3",
|
|
35
|
+
"TLSv1",
|
|
36
|
+
"TLSv1.0",
|
|
37
|
+
"TLSv1_method",
|
|
38
|
+
"TLSv1.1",
|
|
39
|
+
"TLSv1_1_method"
|
|
40
|
+
],
|
|
41
|
+
"secureVersions": [
|
|
42
|
+
"TLSv1.2",
|
|
43
|
+
"TLSv1_2_method",
|
|
44
|
+
"TLSv1.3",
|
|
45
|
+
"TLSv1_3_method"
|
|
46
|
+
],
|
|
47
|
+
"configKeys": [
|
|
48
|
+
"minVersion",
|
|
49
|
+
"maxVersion",
|
|
50
|
+
"secureProtocol",
|
|
51
|
+
"secureOptions",
|
|
52
|
+
"protocol"
|
|
53
|
+
],
|
|
54
|
+
"frameworks": ["express", "nextjs", "nuxtjs", "nestjs", "fastify", "koa"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S014 Symbol-Based Analyzer - Enforce TLS 1.2 or 1.3 only
|
|
3
|
+
*
|
|
4
|
+
* Uses AST analysis via ts-morph to detect insecure TLS/SSL versions
|
|
5
|
+
* in server configurations, client agents, and framework settings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class S014SymbolBasedAnalyzer {
|
|
9
|
+
constructor(semanticEngine) {
|
|
10
|
+
this.ruleId = "S014";
|
|
11
|
+
this.semanticEngine = semanticEngine;
|
|
12
|
+
|
|
13
|
+
// Insecure TLS/SSL versions to detect
|
|
14
|
+
this.insecureVersions = [
|
|
15
|
+
/SSLv2/i,
|
|
16
|
+
/SSLv3/i,
|
|
17
|
+
/TLSv1(?!\.2|\.3|_2|_3)/i, // TLS 1.0 (but not 1.2 or 1.3)
|
|
18
|
+
/TLSv1\.0/i,
|
|
19
|
+
/TLSv1_method/i,
|
|
20
|
+
/TLSv1\.1/i,
|
|
21
|
+
/TLSv1_1_method/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// Configuration keys that specify TLS version
|
|
25
|
+
this.versionKeys = [
|
|
26
|
+
"minVersion",
|
|
27
|
+
"maxVersion",
|
|
28
|
+
"secureProtocol",
|
|
29
|
+
"protocol",
|
|
30
|
+
"tlsVersion",
|
|
31
|
+
"sslVersion",
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async initialize() {}
|
|
36
|
+
|
|
37
|
+
analyze(sourceFile, filePath) {
|
|
38
|
+
const violations = [];
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const { SyntaxKind } = require("ts-morph");
|
|
42
|
+
|
|
43
|
+
// Check all object literal expressions for TLS version configs
|
|
44
|
+
const objLits = sourceFile.getDescendantsOfKind(
|
|
45
|
+
SyntaxKind.ObjectLiteralExpression
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
for (const obj of objLits) {
|
|
49
|
+
try {
|
|
50
|
+
const properties = obj.getProperties();
|
|
51
|
+
|
|
52
|
+
for (const prop of properties) {
|
|
53
|
+
if (prop.getKind() !== SyntaxKind.PropertyAssignment) continue;
|
|
54
|
+
|
|
55
|
+
const propAssignment = prop;
|
|
56
|
+
const propName = propAssignment.getName();
|
|
57
|
+
|
|
58
|
+
// Check if this is a TLS version configuration key
|
|
59
|
+
if (this.versionKeys.includes(propName)) {
|
|
60
|
+
const initializer = propAssignment.getInitializer();
|
|
61
|
+
if (!initializer) continue;
|
|
62
|
+
|
|
63
|
+
let value = "";
|
|
64
|
+
const kind = initializer.getKind();
|
|
65
|
+
|
|
66
|
+
if (kind === SyntaxKind.StringLiteral) {
|
|
67
|
+
value = initializer.getLiteralValue();
|
|
68
|
+
} else if (kind === SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
69
|
+
value = initializer.getLiteralText().slice(1, -1); // Remove backticks
|
|
70
|
+
} else if (kind === SyntaxKind.TemplateExpression) {
|
|
71
|
+
// Template literal with expressions: `TLSv${version}`
|
|
72
|
+
// We can't know the runtime value, but we can detect potential issues
|
|
73
|
+
const templateText = initializer.getText();
|
|
74
|
+
// Check if template contains TLS/SSL patterns that might result in insecure versions
|
|
75
|
+
if (/TLSv|SSLv/i.test(templateText)) {
|
|
76
|
+
// This is potentially insecure - warn about dynamic TLS version
|
|
77
|
+
violations.push({
|
|
78
|
+
ruleId: this.ruleId,
|
|
79
|
+
message: `Dynamic TLS/SSL version detected in '${propName}' - ensure it uses TLS 1.2 or 1.3 only`,
|
|
80
|
+
severity: "error",
|
|
81
|
+
line: propAssignment.getStartLineNumber(),
|
|
82
|
+
column:
|
|
83
|
+
propAssignment.getStartLinePos() -
|
|
84
|
+
propAssignment.getStartLinePos(true) +
|
|
85
|
+
1,
|
|
86
|
+
filePath: filePath,
|
|
87
|
+
});
|
|
88
|
+
continue; // Skip further checking for this property
|
|
89
|
+
}
|
|
90
|
+
value = templateText;
|
|
91
|
+
} else if (kind === SyntaxKind.Identifier) {
|
|
92
|
+
// Handle variable reference
|
|
93
|
+
value = initializer.getText();
|
|
94
|
+
} else {
|
|
95
|
+
value = initializer.getText().replace(/['"]/g, "");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check if value contains insecure version
|
|
99
|
+
for (const insecurePattern of this.insecureVersions) {
|
|
100
|
+
if (insecurePattern.test(value)) {
|
|
101
|
+
violations.push({
|
|
102
|
+
ruleId: this.ruleId,
|
|
103
|
+
message: `Insecure TLS/SSL version '${value}' detected in '${propName}' - use TLS 1.2 or 1.3 only`,
|
|
104
|
+
severity: "error",
|
|
105
|
+
line: propAssignment.getStartLineNumber(),
|
|
106
|
+
column:
|
|
107
|
+
propAssignment.getStartLinePos() -
|
|
108
|
+
propAssignment.getStartLinePos(true) +
|
|
109
|
+
1,
|
|
110
|
+
filePath: filePath,
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch (e) {
|
|
118
|
+
// Skip problematic objects
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check standalone variable declarations (only for simple string assignments)
|
|
123
|
+
// This catches cases like: const tlsVersion = "TLSv1"
|
|
124
|
+
const varDecls = sourceFile.getDescendantsOfKind(
|
|
125
|
+
SyntaxKind.VariableDeclaration
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
for (const varDecl of varDecls) {
|
|
129
|
+
try {
|
|
130
|
+
const init = varDecl.getInitializer();
|
|
131
|
+
if (!init) continue;
|
|
132
|
+
|
|
133
|
+
const kind = init.getKind();
|
|
134
|
+
|
|
135
|
+
// Only check simple string literals or template literals (not objects/calls)
|
|
136
|
+
// This prevents duplicate detection with ObjectLiteral properties
|
|
137
|
+
if (
|
|
138
|
+
kind !== SyntaxKind.StringLiteral &&
|
|
139
|
+
kind !== SyntaxKind.NoSubstitutionTemplateLiteral &&
|
|
140
|
+
kind !== SyntaxKind.TemplateExpression
|
|
141
|
+
) {
|
|
142
|
+
continue; // Skip complex expressions
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let value = "";
|
|
146
|
+
if (kind === SyntaxKind.StringLiteral) {
|
|
147
|
+
value = init.getLiteralValue();
|
|
148
|
+
} else if (kind === SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
149
|
+
value = init.getLiteralText().slice(1, -1);
|
|
150
|
+
} else if (kind === SyntaxKind.TemplateExpression) {
|
|
151
|
+
// For template expressions, get the static parts
|
|
152
|
+
value = init.getText();
|
|
153
|
+
} else {
|
|
154
|
+
value = init.getText().replace(/['"]/g, "");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check for insecure versions in variable values
|
|
158
|
+
for (const insecurePattern of this.insecureVersions) {
|
|
159
|
+
if (insecurePattern.test(value)) {
|
|
160
|
+
violations.push({
|
|
161
|
+
ruleId: this.ruleId,
|
|
162
|
+
message: `Insecure TLS/SSL version '${value}' assigned to variable - use TLS 1.2 or 1.3 only`,
|
|
163
|
+
severity: "error",
|
|
164
|
+
line: varDecl.getStartLineNumber(),
|
|
165
|
+
column:
|
|
166
|
+
varDecl.getStartLinePos() - varDecl.getStartLinePos(true) + 1,
|
|
167
|
+
filePath: filePath,
|
|
168
|
+
});
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch (e) {
|
|
173
|
+
// Skip problematic variables
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Note: CallExpression checking is not needed because getDescendantsOfKind
|
|
178
|
+
// for ObjectLiteralExpression already captures all objects including those
|
|
179
|
+
// passed as arguments to function calls (e.g., https.createServer({...}))
|
|
180
|
+
// This prevents duplicate violations.
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.warn(
|
|
183
|
+
`⚠ [S014] Symbol analysis failed for ${filePath}:`,
|
|
184
|
+
err.message
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return violations;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cleanup() {}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = S014SymbolBasedAnalyzer;
|