@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,51 @@
|
|
|
1
|
+
````markdown
|
|
2
|
+
# S013 - Enforce TLS for all connections
|
|
3
|
+
|
|
4
|
+
## Overview
|
|
5
|
+
|
|
6
|
+
Rule S013 ensures that applications only create or call secure (HTTPS/TLS) endpoints. It detects insecure server bindings (http.createServer, express app.listen without HTTPS), hard-coded http:// URLs in client calls, and framework-specific misconfigurations that disable TLS.
|
|
7
|
+
|
|
8
|
+
## Framework Support
|
|
9
|
+
|
|
10
|
+
- Express.js: detect `http.createServer`, `app.listen` used without TLS wrapper, `app.set('trust proxy', false)` combined with http
|
|
11
|
+
- Next.js: detect API routes calling `fetch('http://...')` or new Response created from http sources
|
|
12
|
+
- Nuxt.js: detect server config `server: { https: false }` or HTTP public assets
|
|
13
|
+
- NestJS: detect direct use of http server via `createServer` or `app.listen` without TLS
|
|
14
|
+
|
|
15
|
+
## Security Impact
|
|
16
|
+
|
|
17
|
+
Serving over HTTP or making unencrypted client calls can expose credentials, session tokens, and sensitive data to passive observers. TLS must be enforced for all communication in production.
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
❌ Violation (Express server using http):
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
const http = require("http");
|
|
25
|
+
const server = http.createServer(app);
|
|
26
|
+
server.listen(80);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
❌ Violation (client fetch to http):
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
await fetch("http://internal-api.local/auth");
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
✅ Compliant (HTTPS enforced):
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
const https = require("https");
|
|
39
|
+
const fs = require("fs");
|
|
40
|
+
const server = https.createServer(
|
|
41
|
+
{ key: fs.readFileSync("key"), cert: fs.readFileSync("cert") },
|
|
42
|
+
app
|
|
43
|
+
);
|
|
44
|
+
server.listen(443);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Analysis Approach
|
|
48
|
+
|
|
49
|
+
- Symbol-based (primary): analyze AST for server creation calls, imports of `http`, usages of `fetch`/`axios`/`request` with `http://` literals, and framework-specific config objects.
|
|
50
|
+
- Regex-based (fallback): scan lines for `http.createServer`, `fetch('http://`, `app.listen(` with numeric port 80, `server: { https: false }`, etc.
|
|
51
|
+
````
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Command: node cli.js --rule=S013 --input=examples/rule-test-fixtures/rules/S013_tls_enforcement --engine=heuristic
|
|
2
|
+
|
|
3
|
+
const S013SymbolBasedAnalyzer = require("./symbol-based-analyzer");
|
|
4
|
+
|
|
5
|
+
class S013Analyzer {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.ruleId = "S013";
|
|
8
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
9
|
+
this.verbose = options.verbose || false;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
this.symbolAnalyzer = new S013SymbolBasedAnalyzer(this.semanticEngine);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.warn(`⚠ [S013] Failed to create symbol analyzer: ${e.message}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async initialize(semanticEngine) {
|
|
19
|
+
this.semanticEngine = semanticEngine;
|
|
20
|
+
if (this.symbolAnalyzer && this.symbolAnalyzer.initialize) {
|
|
21
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
analyzeSingle(filePath, options = {}) {
|
|
26
|
+
return this.analyze([filePath], "typescript", options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async analyze(files, language, options = {}) {
|
|
30
|
+
const violations = [];
|
|
31
|
+
for (const filePath of files) {
|
|
32
|
+
try {
|
|
33
|
+
const vs = await this.analyzeFile(filePath, options);
|
|
34
|
+
violations.push(...vs);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.warn(`⚠ [S013] Analysis error for ${filePath}: ${e.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return violations;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async analyzeFile(filePath, options = {}) {
|
|
43
|
+
const violationMap = new Map();
|
|
44
|
+
|
|
45
|
+
if (!this.symbolAnalyzer) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
let sourceFile = null;
|
|
51
|
+
if (this.semanticEngine?.project) {
|
|
52
|
+
sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!sourceFile) {
|
|
56
|
+
// create temporary ts-morph source file
|
|
57
|
+
const fs = require("fs");
|
|
58
|
+
const path = require("path");
|
|
59
|
+
const { Project } = require("ts-morph");
|
|
60
|
+
if (!fs.existsSync(filePath)) {
|
|
61
|
+
throw new Error(`File not found: ${filePath}`);
|
|
62
|
+
}
|
|
63
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
64
|
+
const tmp = new Project({
|
|
65
|
+
useInMemoryFileSystem: true,
|
|
66
|
+
compilerOptions: { allowJs: true },
|
|
67
|
+
});
|
|
68
|
+
sourceFile = tmp.createSourceFile(path.basename(filePath), content);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (sourceFile) {
|
|
72
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
73
|
+
sourceFile,
|
|
74
|
+
filePath
|
|
75
|
+
);
|
|
76
|
+
symbolViolations.forEach((v) => {
|
|
77
|
+
const key = `${v.line}:${v.column}:${v.message}`;
|
|
78
|
+
if (!violationMap.has(key)) violationMap.set(key, v);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.warn(`⚠ [S013] Symbol analysis failed: ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Array.from(violationMap.values()).map((v) => ({
|
|
86
|
+
...v,
|
|
87
|
+
filePath,
|
|
88
|
+
file: filePath,
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
cleanup() {
|
|
93
|
+
if (this.symbolAnalyzer?.cleanup) {
|
|
94
|
+
this.symbolAnalyzer.cleanup();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = S013Analyzer;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S013",
|
|
3
|
+
"name": "Enforce TLS for all connections",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"description": "S013 - Ensure TLS (HTTPS) is always used for network connections and server bindings. Detects unsecured HTTP server creation, use of http:// endpoints in client calls, and framework-specific configurations that disable HTTPS.",
|
|
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
|
+
"httpIndicators": [
|
|
33
|
+
"http://",
|
|
34
|
+
"require('http')",
|
|
35
|
+
"require(\"http\")",
|
|
36
|
+
"http.createServer"
|
|
37
|
+
],
|
|
38
|
+
"httpsModules": ["https", "tls"],
|
|
39
|
+
"frameworks": ["express", "nextjs", "nuxtjs", "nestjs"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S013 Symbol-Based Analyzer - Enforce TLS for all connections
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
class S013SymbolBasedAnalyzer {
|
|
6
|
+
constructor(semanticEngine) {
|
|
7
|
+
this.ruleId = "S013";
|
|
8
|
+
this.semanticEngine = semanticEngine;
|
|
9
|
+
this.httpIndicators = [
|
|
10
|
+
/http:\/\//i,
|
|
11
|
+
/ws:\/\//i, // WebSocket insecure protocol
|
|
12
|
+
/http\.createServer/,
|
|
13
|
+
/require\(['\"]http['\"]\)/,
|
|
14
|
+
];
|
|
15
|
+
this.tlsModules = ["https", "tls"];
|
|
16
|
+
this.clientCallMethods = [
|
|
17
|
+
"fetch",
|
|
18
|
+
"axios",
|
|
19
|
+
"request",
|
|
20
|
+
"http.request",
|
|
21
|
+
"http.get",
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async initialize() {}
|
|
26
|
+
|
|
27
|
+
analyze(sourceFile, filePath) {
|
|
28
|
+
const violations = [];
|
|
29
|
+
try {
|
|
30
|
+
const { SyntaxKind } = require("ts-morph");
|
|
31
|
+
|
|
32
|
+
// Check for imports/requires of 'http' module
|
|
33
|
+
const importDecls = sourceFile.getImportDeclarations();
|
|
34
|
+
for (const imp of importDecls) {
|
|
35
|
+
const moduleName = imp.getModuleSpecifierValue();
|
|
36
|
+
if (moduleName === "http") {
|
|
37
|
+
const startLine = imp.getStartLineNumber();
|
|
38
|
+
violations.push({
|
|
39
|
+
ruleId: this.ruleId,
|
|
40
|
+
message: `Import of insecure 'http' module detected - prefer 'https' or TLS-wrapped server`,
|
|
41
|
+
severity: "error",
|
|
42
|
+
line: startLine,
|
|
43
|
+
column: 1,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check for require('http') style and variable assignments with insecure URLs
|
|
49
|
+
const varDecls = sourceFile.getDescendantsOfKind(
|
|
50
|
+
SyntaxKind.VariableDeclaration
|
|
51
|
+
);
|
|
52
|
+
for (const v of varDecls) {
|
|
53
|
+
const init = v.getInitializer();
|
|
54
|
+
if (init && init.getText) {
|
|
55
|
+
const initText = init.getText();
|
|
56
|
+
|
|
57
|
+
// Check for require('http')
|
|
58
|
+
if (/require\(['\"]http['\"]\)/.test(initText)) {
|
|
59
|
+
violations.push({
|
|
60
|
+
ruleId: this.ruleId,
|
|
61
|
+
message: `CommonJS require of insecure 'http' module detected - prefer 'https'`,
|
|
62
|
+
severity: "error",
|
|
63
|
+
line: v.getStartLineNumber(),
|
|
64
|
+
column: 1,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check for template literals with http:// or ws:// in variable assignments
|
|
69
|
+
const kind = init.getKind && init.getKind();
|
|
70
|
+
if (
|
|
71
|
+
kind === SyntaxKind.TemplateExpression ||
|
|
72
|
+
kind === SyntaxKind.NoSubstitutionTemplateLiteral
|
|
73
|
+
) {
|
|
74
|
+
if (/http:\/\//i.test(initText)) {
|
|
75
|
+
violations.push({
|
|
76
|
+
ruleId: this.ruleId,
|
|
77
|
+
message: `Variable assigned insecure URL in template literal - use 'https://'`,
|
|
78
|
+
severity: "error",
|
|
79
|
+
line: v.getStartLineNumber(),
|
|
80
|
+
column: 1,
|
|
81
|
+
});
|
|
82
|
+
} else if (/ws:\/\//i.test(initText)) {
|
|
83
|
+
violations.push({
|
|
84
|
+
ruleId: this.ruleId,
|
|
85
|
+
message: `Variable assigned insecure WebSocket URL in template literal - use 'wss://'`,
|
|
86
|
+
severity: "error",
|
|
87
|
+
line: v.getStartLineNumber(),
|
|
88
|
+
column: 1,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check for http.createServer or server.listen on port 80
|
|
96
|
+
const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
97
|
+
for (const call of calls) {
|
|
98
|
+
const expr = call.getExpression();
|
|
99
|
+
const text = expr.getText();
|
|
100
|
+
|
|
101
|
+
// Check for 'new' expressions (like new WebSocket('ws://...'))
|
|
102
|
+
const parent = call.getParent();
|
|
103
|
+
const isNewExpression =
|
|
104
|
+
parent &&
|
|
105
|
+
parent.getKind &&
|
|
106
|
+
parent.getKind() === SyntaxKind.NewExpression;
|
|
107
|
+
|
|
108
|
+
// http.createServer(...)
|
|
109
|
+
if (/http\.createServer/.test(text)) {
|
|
110
|
+
violations.push({
|
|
111
|
+
ruleId: this.ruleId,
|
|
112
|
+
message: `Insecure server created with http.createServer - use TLS (https.createServer)`,
|
|
113
|
+
severity: "error",
|
|
114
|
+
line: call.getStartLineNumber(),
|
|
115
|
+
column: 1,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// app.listen(80) or server.listen(80)
|
|
120
|
+
if (/\.listen/.test(text)) {
|
|
121
|
+
const args = call.getArguments();
|
|
122
|
+
if (args.length > 0) {
|
|
123
|
+
const first = args[0];
|
|
124
|
+
try {
|
|
125
|
+
const val = first.getText();
|
|
126
|
+
if (/\b80\b/.test(val) || /['\"]http:\/\//.test(val)) {
|
|
127
|
+
violations.push({
|
|
128
|
+
ruleId: this.ruleId,
|
|
129
|
+
message: `Server listening on insecure port or http endpoint (${val}) - configure TLS (443) or use HTTPS`,
|
|
130
|
+
severity: "error",
|
|
131
|
+
line: call.getStartLineNumber(),
|
|
132
|
+
column: 1,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
// ignore
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// client calls with http:// or ws:// in literal args (fetch('http://...'))
|
|
142
|
+
const args = call.getArguments();
|
|
143
|
+
for (const a of args) {
|
|
144
|
+
try {
|
|
145
|
+
const kind = a.getKind && a.getKind();
|
|
146
|
+
|
|
147
|
+
// Check StringLiteral
|
|
148
|
+
if (kind === SyntaxKind.StringLiteral) {
|
|
149
|
+
const lit = a.getLiteralValue();
|
|
150
|
+
if (/^http:\/\//i.test(lit)) {
|
|
151
|
+
const msgPrefix = isNewExpression
|
|
152
|
+
? "Constructor"
|
|
153
|
+
: "Insecure client call";
|
|
154
|
+
violations.push({
|
|
155
|
+
ruleId: this.ruleId,
|
|
156
|
+
message: `${msgPrefix} to '${lit}' detected - use 'https://'`,
|
|
157
|
+
severity: "error",
|
|
158
|
+
line: call.getStartLineNumber(),
|
|
159
|
+
column: 1,
|
|
160
|
+
});
|
|
161
|
+
} else if (/^ws:\/\//i.test(lit)) {
|
|
162
|
+
violations.push({
|
|
163
|
+
ruleId: this.ruleId,
|
|
164
|
+
message: `Insecure WebSocket connection to '${lit}' detected - use 'wss://'`,
|
|
165
|
+
severity: "error",
|
|
166
|
+
line: call.getStartLineNumber(),
|
|
167
|
+
column: 1,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check TemplateExpression and NoSubstitutionTemplateLiteral
|
|
173
|
+
if (
|
|
174
|
+
kind === SyntaxKind.TemplateExpression ||
|
|
175
|
+
kind === SyntaxKind.NoSubstitutionTemplateLiteral
|
|
176
|
+
) {
|
|
177
|
+
const text = a.getText();
|
|
178
|
+
if (/http:\/\//i.test(text)) {
|
|
179
|
+
violations.push({
|
|
180
|
+
ruleId: this.ruleId,
|
|
181
|
+
message: `Insecure URL in template literal detected - use 'https://'`,
|
|
182
|
+
severity: "error",
|
|
183
|
+
line: a.getStartLineNumber(),
|
|
184
|
+
column: 1,
|
|
185
|
+
});
|
|
186
|
+
} else if (/ws:\/\//i.test(text)) {
|
|
187
|
+
violations.push({
|
|
188
|
+
ruleId: this.ruleId,
|
|
189
|
+
message: `Insecure WebSocket in template literal detected - use 'wss://'`,
|
|
190
|
+
severity: "error",
|
|
191
|
+
line: a.getStartLineNumber(),
|
|
192
|
+
column: 1,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
// ignore
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check NewExpression separately for WebSocket and similar constructors
|
|
203
|
+
const newExprs = sourceFile.getDescendantsOfKind(
|
|
204
|
+
SyntaxKind.NewExpression
|
|
205
|
+
);
|
|
206
|
+
for (const newExpr of newExprs) {
|
|
207
|
+
try {
|
|
208
|
+
const args = newExpr.getArguments();
|
|
209
|
+
for (const a of args) {
|
|
210
|
+
const kind = a.getKind && a.getKind();
|
|
211
|
+
|
|
212
|
+
if (kind === SyntaxKind.StringLiteral) {
|
|
213
|
+
const lit = a.getLiteralValue();
|
|
214
|
+
if (/^ws:\/\//i.test(lit)) {
|
|
215
|
+
violations.push({
|
|
216
|
+
ruleId: this.ruleId,
|
|
217
|
+
message: `Insecure WebSocket connection to '${lit}' detected - use 'wss://'`,
|
|
218
|
+
severity: "error",
|
|
219
|
+
line: newExpr.getStartLineNumber(),
|
|
220
|
+
column: 1,
|
|
221
|
+
});
|
|
222
|
+
} else if (/^http:\/\//i.test(lit)) {
|
|
223
|
+
violations.push({
|
|
224
|
+
ruleId: this.ruleId,
|
|
225
|
+
message: `Constructor with insecure URL '${lit}' detected - use 'https://'`,
|
|
226
|
+
severity: "error",
|
|
227
|
+
line: newExpr.getStartLineNumber(),
|
|
228
|
+
column: 1,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} catch (e) {
|
|
234
|
+
// ignore
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check framework config objects for https: false or tls disabled
|
|
239
|
+
const objLits = sourceFile.getDescendantsOfKind(
|
|
240
|
+
SyntaxKind.ObjectLiteralExpression
|
|
241
|
+
);
|
|
242
|
+
for (const obj of objLits) {
|
|
243
|
+
const text = obj.getText();
|
|
244
|
+
if (/https\s*:\s*false/.test(text) || /tls\s*:\s*false/.test(text)) {
|
|
245
|
+
violations.push({
|
|
246
|
+
ruleId: this.ruleId,
|
|
247
|
+
message: `Framework configuration disables TLS (https: false) - enable TLS for production`,
|
|
248
|
+
severity: "error",
|
|
249
|
+
line: obj.getStartLineNumber(),
|
|
250
|
+
column: 1,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check object property assignments for insecure URLs
|
|
256
|
+
const properties = sourceFile.getDescendantsOfKind(
|
|
257
|
+
SyntaxKind.PropertyAssignment
|
|
258
|
+
);
|
|
259
|
+
for (const prop of properties) {
|
|
260
|
+
try {
|
|
261
|
+
const initializer = prop.getInitializer();
|
|
262
|
+
if (initializer) {
|
|
263
|
+
const kind = initializer.getKind && initializer.getKind();
|
|
264
|
+
|
|
265
|
+
// Check string literal property values
|
|
266
|
+
if (kind === SyntaxKind.StringLiteral) {
|
|
267
|
+
const value = initializer.getLiteralValue();
|
|
268
|
+
if (/^http:\/\//i.test(value)) {
|
|
269
|
+
violations.push({
|
|
270
|
+
ruleId: this.ruleId,
|
|
271
|
+
message: `Object property contains insecure URL '${value}' - use 'https://'`,
|
|
272
|
+
severity: "error",
|
|
273
|
+
line: prop.getStartLineNumber(),
|
|
274
|
+
column: 1,
|
|
275
|
+
});
|
|
276
|
+
} else if (/^ws:\/\//i.test(value)) {
|
|
277
|
+
violations.push({
|
|
278
|
+
ruleId: this.ruleId,
|
|
279
|
+
message: `Object property contains insecure WebSocket URL '${value}' - use 'wss://'`,
|
|
280
|
+
severity: "error",
|
|
281
|
+
line: prop.getStartLineNumber(),
|
|
282
|
+
column: 1,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} catch (e) {
|
|
288
|
+
// ignore
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check array literals for insecure URLs
|
|
293
|
+
const arrayLits = sourceFile.getDescendantsOfKind(
|
|
294
|
+
SyntaxKind.ArrayLiteralExpression
|
|
295
|
+
);
|
|
296
|
+
for (const arr of arrayLits) {
|
|
297
|
+
const elements = arr.getElements();
|
|
298
|
+
for (const elem of elements) {
|
|
299
|
+
try {
|
|
300
|
+
const kind = elem.getKind && elem.getKind();
|
|
301
|
+
if (kind === SyntaxKind.StringLiteral) {
|
|
302
|
+
const value = elem.getLiteralValue();
|
|
303
|
+
if (/^http:\/\//i.test(value)) {
|
|
304
|
+
violations.push({
|
|
305
|
+
ruleId: this.ruleId,
|
|
306
|
+
message: `Array element contains insecure URL '${value}' - use 'https://'`,
|
|
307
|
+
severity: "error",
|
|
308
|
+
line: elem.getStartLineNumber(),
|
|
309
|
+
column: 1,
|
|
310
|
+
});
|
|
311
|
+
} else if (/^ws:\/\//i.test(value)) {
|
|
312
|
+
violations.push({
|
|
313
|
+
ruleId: this.ruleId,
|
|
314
|
+
message: `Array element contains insecure WebSocket URL '${value}' - use 'wss://'`,
|
|
315
|
+
severity: "error",
|
|
316
|
+
line: elem.getStartLineNumber(),
|
|
317
|
+
column: 1,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch (e) {
|
|
322
|
+
// ignore
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.warn(
|
|
328
|
+
`⚠ [S013] Symbol analysis failed for ${filePath}:`,
|
|
329
|
+
err.message
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return violations;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
cleanup() {}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = S013SymbolBasedAnalyzer;
|