@sun-asterisk/sunlint 1.3.5 → 1.3.7

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.
@@ -0,0 +1,180 @@
1
+ # Rule S058: No SSRF (Server-Side Request Forgery)
2
+
3
+ ## Overview
4
+ Rule S058 prevents Server-Side Request Forgery (SSRF) attacks by detecting and flagging HTTP requests that use user-controlled URLs without proper validation.
5
+
6
+ ## What is SSRF?
7
+ SSRF allows attackers to make HTTP requests from the server to arbitrary destinations, potentially accessing:
8
+ - Internal services (databases, admin panels)
9
+ - Cloud metadata endpoints (AWS, GCP, Azure)
10
+ - Local files via file:// protocol
11
+ - Internal network resources
12
+
13
+ ## Detection Strategy
14
+
15
+ ### 1. HTTP Client Detection
16
+ Detects calls to HTTP libraries:
17
+ ```typescript
18
+ // Detected patterns
19
+ fetch(url)
20
+ axios.get(url)
21
+ http.request(url)
22
+ httpClient.post(url)
23
+ ```
24
+
25
+ ### 2. URL Source Tracing
26
+ Traces URL variables back to their source:
27
+ ```typescript
28
+ // ❌ User-controlled (DANGEROUS)
29
+ const url = req.query.targetUrl;
30
+ fetch(url);
31
+
32
+ // ✅ Hardcoded (SAFE)
33
+ const url = "https://api.trusted-service.com";
34
+ fetch(url);
35
+ ```
36
+
37
+ ### 3. Validation Check
38
+ Verifies if URL validation exists:
39
+ ```typescript
40
+ // ❌ No validation
41
+ const url = req.body.webhookUrl;
42
+ fetch(url);
43
+
44
+ // ✅ With validation
45
+ const url = req.body.webhookUrl;
46
+ validateUrlAllowList(url);
47
+ fetch(url);
48
+ ```
49
+
50
+ ## Examples
51
+
52
+ ### ❌ Violations
53
+
54
+ #### 1. User-controlled URL without validation
55
+ ```typescript
56
+ app.post('/webhook', (req, res) => {
57
+ const webhookUrl = req.body.url;
58
+ // VIOLATION: User input directly used in HTTP request
59
+ fetch(webhookUrl);
60
+ });
61
+ ```
62
+
63
+ #### 2. Dangerous hardcoded URLs
64
+ ```typescript
65
+ // VIOLATION: Internal metadata endpoint
66
+ fetch('http://169.254.169.254/latest/meta-data/');
67
+
68
+ // VIOLATION: Local file access
69
+ fetch('file:///etc/passwd');
70
+ ```
71
+
72
+ #### 3. Query parameter injection
73
+ ```typescript
74
+ app.get('/proxy', (req, res) => {
75
+ const targetUrl = req.query.url;
76
+ // VIOLATION: No validation of target URL
77
+ axios.get(targetUrl);
78
+ });
79
+ ```
80
+
81
+ ### ✅ Safe Patterns
82
+
83
+ #### 1. Allow-list validation
84
+ ```typescript
85
+ const ALLOWED_DOMAINS = ['api.trusted.com', 'webhook.company.com'];
86
+
87
+ function validateUrlAllowList(url) {
88
+ const parsed = new URL(url);
89
+ if (!ALLOWED_DOMAINS.includes(parsed.hostname)) {
90
+ throw new Error('Domain not allowed');
91
+ }
92
+ if (parsed.protocol !== 'https:') {
93
+ throw new Error('Only HTTPS allowed');
94
+ }
95
+ }
96
+
97
+ app.post('/webhook', (req, res) => {
98
+ const webhookUrl = req.body.url;
99
+ validateUrlAllowList(webhookUrl); // ✅ Validated
100
+ fetch(webhookUrl);
101
+ });
102
+ ```
103
+
104
+ #### 2. Hardcoded trusted URLs
105
+ ```typescript
106
+ // ✅ Safe: Hardcoded trusted domain
107
+ const API_BASE = 'https://api.trusted-service.com';
108
+ fetch(\`\${API_BASE}/users\`);
109
+ ```
110
+
111
+ #### 3. Configuration-based URLs
112
+ ```typescript
113
+ // ✅ Safe: From config, not user input
114
+ const externalApiUrl = process.env.EXTERNAL_API_URL;
115
+ fetch(externalApiUrl);
116
+ ```
117
+
118
+ ## Configuration
119
+
120
+ ### Blocked Elements
121
+ - **Protocols**: `file://`, `ftp://`, `ldap://`, etc.
122
+ - **IPs**: `127.0.0.1`, `169.254.169.254`, private ranges
123
+ - **Ports**: `22`, `3306`, `6379`, `5432`, etc.
124
+
125
+ ### Detection Patterns
126
+ - **HTTP Clients**: `fetch`, `axios`, `http`, `request`, etc.
127
+ - **User Input**: `req.body`, `req.query`, `ctx.request`, etc.
128
+ - **Validation Functions**: `validateUrl`, `isAllowedUrl`, etc.
129
+
130
+ ## Best Practices
131
+
132
+ ### 1. Use Allow-lists
133
+ ```typescript
134
+ const ALLOWED_HOSTS = [
135
+ 'api.company.com',
136
+ 'webhook.trusted-partner.com'
137
+ ];
138
+
139
+ function isAllowedUrl(url) {
140
+ return ALLOWED_HOSTS.some(host => url.includes(host));
141
+ }
142
+ ```
143
+
144
+ ### 2. Protocol Restriction
145
+ ```typescript
146
+ function validateUrl(url) {
147
+ const parsed = new URL(url);
148
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
149
+ throw new Error('Invalid protocol');
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### 3. Timeout & Redirect Limits
155
+ ```typescript
156
+ const options = {
157
+ timeout: 5000,
158
+ maxRedirects: 0, // Prevent redirect attacks
159
+ headers: { 'User-Agent': 'MyApp/1.0' }
160
+ };
161
+
162
+ fetch(validatedUrl, options);
163
+ ```
164
+
165
+ ## Testing
166
+
167
+ Run S058 on your codebase:
168
+ ```bash
169
+ # Test single file
170
+ node cli.js --input=src/webhook.ts --rule=S058 --engine=heuristic
171
+
172
+ # Test entire project
173
+ node cli.js --input=src --rule=S058 --engine=heuristic
174
+ ```
175
+
176
+ ## Related Security Rules
177
+ - **S001**: SQL Injection Prevention
178
+ - **S002**: XSS Prevention
179
+ - **S057**: Input Validation
180
+ - **S059**: Path Traversal Prevention
@@ -0,0 +1,403 @@
1
+ // rules/security/S058_no_ssrf/analyzer.js
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const { CommentDetector } = require('../../utils/rule-helpers');
5
+
6
+ class S058SSRFAnalyzer {
7
+ constructor(semanticEngine = null) {
8
+ this.ruleId = 'S058';
9
+ this.ruleName = 'No SSRF (Server-Side Request Forgery)';
10
+ this.description = 'S058 - Prevent SSRF attacks by validating URLs from user input before making HTTP requests';
11
+ this.semanticEngine = semanticEngine;
12
+ this.verbose = false;
13
+
14
+ // Load config from config.json
15
+ this.loadConfig();
16
+ }
17
+
18
+ loadConfig() {
19
+ try {
20
+ const configPath = path.join(__dirname, 'config.json');
21
+ const configData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
22
+ this.options = configData.options || {};
23
+ this.httpClientPatterns = this.options.httpClientPatterns || [];
24
+ this.userInputSources = this.options.userInputSources || [];
25
+ this.dangerousProtocols = this.options.dangerousProtocols || [];
26
+ this.blockedIPs = this.options.blockedIPs || [];
27
+ this.blockedPorts = this.options.blockedPorts || [];
28
+ this.allowedDomains = this.options.allowedDomains || [];
29
+ this.validationFunctions = this.options.validationFunctions || [];
30
+ this.policy = this.options.policy || {};
31
+ this.thresholds = this.options.thresholds || {};
32
+ } catch (error) {
33
+ console.warn(`[S058] Could not load config: ${error.message}`);
34
+ this.options = {};
35
+ this.httpClientPatterns = [];
36
+ this.userInputSources = [];
37
+ this.dangerousProtocols = [];
38
+ this.blockedIPs = [];
39
+ this.blockedPorts = [];
40
+ this.allowedDomains = [];
41
+ this.validationFunctions = [];
42
+ this.policy = {};
43
+ this.thresholds = {};
44
+ }
45
+ }
46
+
47
+ async initialize(semanticEngine = null) {
48
+ if (semanticEngine) {
49
+ this.semanticEngine = semanticEngine;
50
+ }
51
+ this.verbose = semanticEngine?.verbose || false;
52
+ }
53
+
54
+ // Main analyze method required by heuristic engine
55
+ async analyze(files, language, options = {}) {
56
+ const violations = [];
57
+
58
+ if (this.verbose) {
59
+ console.log(`[DEBUG] 🎯 S058: Analyzing ${files.length} files for SSRF vulnerabilities`);
60
+ }
61
+
62
+ for (const filePath of files) {
63
+ if (this.verbose) {
64
+ console.log(`[DEBUG] 🎯 S058: Analyzing ${filePath.split('/').pop()}`);
65
+ }
66
+
67
+ try {
68
+ const content = fs.readFileSync(filePath, 'utf8');
69
+ const fileExtension = path.extname(filePath);
70
+ const fileViolations = this.analyzeFile(filePath, content, fileExtension);
71
+ violations.push(...fileViolations);
72
+ } catch (error) {
73
+ console.warn(`[S058] Error analyzing ${filePath}: ${error.message}`);
74
+ }
75
+ }
76
+
77
+ if (this.verbose) {
78
+ console.log(`[DEBUG] 🎯 S058: Found ${violations.length} SSRF violations`);
79
+ }
80
+
81
+ return violations;
82
+ }
83
+
84
+ analyzeFile(filePath, content, fileExtension) {
85
+ const violations = [];
86
+ const detectedLanguage = this.detectLanguage(fileExtension);
87
+
88
+ if (!detectedLanguage) {
89
+ return violations;
90
+ }
91
+
92
+ // Use semantic engine (AST) if available, fallback to heuristic
93
+ if (this.semanticEngine && typeof this.semanticEngine.parseCode === 'function') {
94
+ return this.analyzeWithAST(filePath, content, detectedLanguage);
95
+ } else {
96
+ return this.analyzeWithHeuristic(filePath, content, detectedLanguage);
97
+ }
98
+ }
99
+
100
+ detectLanguage(fileExtension) {
101
+ const extensions = {
102
+ '.ts': 'typescript',
103
+ '.tsx': 'typescript',
104
+ '.js': 'javascript',
105
+ '.jsx': 'javascript',
106
+ '.mjs': 'javascript'
107
+ };
108
+ return extensions[fileExtension] || null;
109
+ }
110
+
111
+ analyzeWithHeuristic(filePath, content, language) {
112
+ const violations = [];
113
+ const lines = content.split('\n');
114
+
115
+ // Detect HTTP client calls and trace URLs
116
+ const httpCalls = this.detectHttpCalls(content, lines);
117
+
118
+ for (const call of httpCalls) {
119
+ const urlSource = this.traceUrlSource(call, content, lines);
120
+
121
+ if (urlSource.isUserControlled) {
122
+ // Check if URL is validated
123
+ const hasValidation = this.checkUrlValidation(call, content, lines);
124
+
125
+ if (!hasValidation) {
126
+ violations.push({
127
+ ruleId: this.ruleId,
128
+ message: `Potential SSRF vulnerability: HTTP request with user-controlled URL '${call.urlVariable}' without validation`,
129
+ severity: 'error',
130
+ line: call.line,
131
+ column: call.column,
132
+ filePath: filePath,
133
+ details: {
134
+ httpMethod: call.method,
135
+ urlVariable: call.urlVariable,
136
+ userInputSource: urlSource.source,
137
+ suggestion: `Add URL validation using: ${this.validationFunctions[0] || 'validateUrlAllowList'}(${call.urlVariable})`
138
+ }
139
+ });
140
+ }
141
+ }
142
+
143
+ // Check for hardcoded dangerous URLs
144
+ if (call.isHardcoded) {
145
+ const dangerousUrl = this.checkDangerousUrl(call.url);
146
+ if (dangerousUrl.isDangerous) {
147
+ violations.push({
148
+ ruleId: this.ruleId,
149
+ message: `Dangerous URL detected: ${dangerousUrl.reason}`,
150
+ severity: 'error',
151
+ line: call.line,
152
+ column: call.column,
153
+ filePath: filePath,
154
+ details: {
155
+ url: call.url,
156
+ reason: dangerousUrl.reason,
157
+ suggestion: 'Remove dangerous URL or add to allow-list if legitimate'
158
+ }
159
+ });
160
+ }
161
+ }
162
+ }
163
+
164
+ return violations;
165
+ }
166
+
167
+ detectHttpCalls(content, lines) {
168
+ const calls = [];
169
+
170
+ if (this.verbose) {
171
+ console.log(`[DEBUG] 🔍 S058: Detecting HTTP calls in ${lines.length} lines`);
172
+ }
173
+
174
+ for (const pattern of this.httpClientPatterns) {
175
+ const regex = new RegExp(pattern, 'gi');
176
+ let match;
177
+
178
+ while ((match = regex.exec(content)) !== null) {
179
+ const lineNumber = content.substring(0, match.index).split('\n').length;
180
+ const line = lines[lineNumber - 1];
181
+ const columnPosition = match.index - content.lastIndexOf('\n', match.index - 1) - 1;
182
+
183
+ // ✅ CHECK: Skip if this code is in comments
184
+ const isInComment = CommentDetector.isLineInBlockComment(lines, lineNumber - 1) ||
185
+ CommentDetector.isPositionInComment(line, columnPosition);
186
+
187
+ if (isInComment) {
188
+ if (this.verbose) {
189
+ console.log(`[DEBUG] 🔍 S058: SKIPPING comment at line ${lineNumber}: ${line.trim()}`);
190
+ }
191
+ continue;
192
+ }
193
+
194
+ if (this.verbose) {
195
+ console.log(`[DEBUG] 🔍 S058: Found HTTP call pattern "${pattern}" at line ${lineNumber}: ${line.trim()}`);
196
+ }
197
+
198
+ // Extract URL parameter
199
+ const urlMatch = this.extractUrlFromCall(line, match[0]);
200
+
201
+ if (urlMatch) {
202
+ calls.push({
203
+ method: match[0],
204
+ line: lineNumber,
205
+ column: columnPosition,
206
+ urlVariable: urlMatch.variable,
207
+ url: urlMatch.value,
208
+ isHardcoded: urlMatch.isHardcoded,
209
+ fullCall: line.trim()
210
+ });
211
+
212
+ if (this.verbose) {
213
+ console.log(`[DEBUG] 🔍 S058: Extracted URL: ${urlMatch.variable} (hardcoded: ${urlMatch.isHardcoded})`);
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ if (this.verbose) {
220
+ console.log(`[DEBUG] 🔍 S058: Found ${calls.length} HTTP calls total (after comment filtering)`);
221
+ }
222
+
223
+ return calls;
224
+ }
225
+
226
+ extractUrlFromCall(line, methodCall) {
227
+ // Extract URL from HTTP call - simplified regex approach
228
+ const patterns = [
229
+ // fetch(url), axios.get(url)
230
+ /(?:fetch|\.(?:get|post|put|delete|patch|request))\s*\(\s*([^,\)]+)/,
231
+ // More complex patterns can be added
232
+ ];
233
+
234
+ for (const pattern of patterns) {
235
+ const match = line.match(pattern);
236
+ if (match) {
237
+ const urlParam = match[1].trim();
238
+
239
+ // Check if it's a string literal
240
+ if (urlParam.startsWith('"') || urlParam.startsWith("'") || urlParam.startsWith('`')) {
241
+ return {
242
+ variable: urlParam,
243
+ value: urlParam.slice(1, -1), // Remove quotes
244
+ isHardcoded: true
245
+ };
246
+ } else {
247
+ return {
248
+ variable: urlParam,
249
+ value: null,
250
+ isHardcoded: false
251
+ };
252
+ }
253
+ }
254
+ }
255
+
256
+ return null;
257
+ }
258
+
259
+ traceUrlSource(call, content, lines) {
260
+ if (call.isHardcoded) {
261
+ return { isUserControlled: false, source: 'hardcoded' };
262
+ }
263
+
264
+ // Simple variable tracing
265
+ const variable = this.escapeRegex(call.urlVariable);
266
+
267
+ // Check if variable comes from user input
268
+ for (const inputPattern of this.userInputSources) {
269
+ // userInputSources patterns are already regex-escaped in config.json
270
+ const escapedPattern = inputPattern;
271
+
272
+ try {
273
+ // Check direct assignment: const url = req.body.url
274
+ const assignmentRegex = new RegExp(`const\\s+${variable}\\s*=\\s*${escapedPattern}`, 'i');
275
+ if (assignmentRegex.test(content)) {
276
+ return {
277
+ isUserControlled: true,
278
+ source: inputPattern
279
+ };
280
+ }
281
+
282
+ // Check let assignment: let url = req.query.endpoint
283
+ const letAssignmentRegex = new RegExp(`let\\s+${variable}\\s*=\\s*${escapedPattern}`, 'i');
284
+ if (letAssignmentRegex.test(content)) {
285
+ return {
286
+ isUserControlled: true,
287
+ source: inputPattern
288
+ };
289
+ }
290
+
291
+ // Check var assignment: var url = req.params.id
292
+ const varAssignmentRegex = new RegExp(`var\\s+${variable}\\s*=\\s*${escapedPattern}`, 'i');
293
+ if (varAssignmentRegex.test(content)) {
294
+ return {
295
+ isUserControlled: true,
296
+ source: inputPattern
297
+ };
298
+ }
299
+
300
+ // Check property access: url = req.body.path
301
+ const propertyRegex = new RegExp(`${variable}\\s*=\\s*${escapedPattern}`, 'i');
302
+ if (propertyRegex.test(content)) {
303
+ return {
304
+ isUserControlled: true,
305
+ source: inputPattern
306
+ };
307
+ }
308
+
309
+ // Also check reverse assignment patterns
310
+ const reverseRegex = new RegExp(`${escapedPattern}.*${variable}`, 'i');
311
+ if (reverseRegex.test(content)) {
312
+ return {
313
+ isUserControlled: true,
314
+ source: inputPattern
315
+ };
316
+ }
317
+ } catch (e) {
318
+ // Skip invalid regex patterns
319
+ if (this.verbose) {
320
+ console.log(`[DEBUG] S058: Skipping invalid regex pattern for ${inputPattern}: ${e.message}`);
321
+ }
322
+ continue;
323
+ }
324
+ }
325
+
326
+ return { isUserControlled: false, source: 'unknown' };
327
+ }
328
+
329
+ escapeRegex(string) {
330
+ // Escape special regex characters
331
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
332
+ }
333
+
334
+ checkUrlValidation(call, content, lines) {
335
+ // Check if URL validation function is called before HTTP request
336
+ const beforeCallContent = content.substring(0, content.indexOf(call.fullCall));
337
+
338
+ for (const validationFn of this.validationFunctions) {
339
+ try {
340
+ const escapedVariable = this.escapeRegex(call.urlVariable);
341
+ const escapedFunction = this.escapeRegex(validationFn);
342
+ const regex = new RegExp(`${escapedFunction}\\s*\\(.*${escapedVariable}`, 'i');
343
+ if (regex.test(beforeCallContent)) {
344
+ return true;
345
+ }
346
+ } catch (e) {
347
+ // Skip invalid regex patterns
348
+ if (this.verbose) {
349
+ console.log(`[DEBUG] S058: Skipping invalid validation regex for ${validationFn}: ${e.message}`);
350
+ }
351
+ continue;
352
+ }
353
+ }
354
+
355
+ return false;
356
+ }
357
+
358
+ checkDangerousUrl(url) {
359
+ if (!url) return { isDangerous: false };
360
+
361
+ // Check dangerous protocols
362
+ for (const protocol of this.dangerousProtocols) {
363
+ if (url.toLowerCase().includes(protocol)) {
364
+ return {
365
+ isDangerous: true,
366
+ reason: `Dangerous protocol: ${protocol}`
367
+ };
368
+ }
369
+ }
370
+
371
+ // Check blocked IPs
372
+ for (const ipPattern of this.blockedIPs) {
373
+ const regex = new RegExp(ipPattern, 'i');
374
+ if (regex.test(url)) {
375
+ return {
376
+ isDangerous: true,
377
+ reason: `Blocked IP range: ${ipPattern}`
378
+ };
379
+ }
380
+ }
381
+
382
+ // Check blocked ports
383
+ for (const port of this.blockedPorts) {
384
+ const portRegex = new RegExp(`:${port}(?:/|$)`, 'i');
385
+ if (portRegex.test(url)) {
386
+ return {
387
+ isDangerous: true,
388
+ reason: `Blocked port: ${port}`
389
+ };
390
+ }
391
+ }
392
+
393
+ return { isDangerous: false };
394
+ }
395
+
396
+ analyzeWithAST(filePath, content, language) {
397
+ // Enhanced AST-based analysis for more precise detection
398
+ // This would use the semantic engine for deeper analysis
399
+ return this.analyzeWithHeuristic(filePath, content, language);
400
+ }
401
+ }
402
+
403
+ module.exports = S058SSRFAnalyzer;
@@ -0,0 +1,125 @@
1
+ {
2
+ "ruleId": "S058",
3
+ "name": "No SSRF (Server-Side Request Forgery)",
4
+ "description": "Prevent SSRF attacks by validating URLs from user input before making HTTP requests",
5
+ "category": "security",
6
+ "severity": "error",
7
+ "options": {
8
+ "httpClientPatterns": [
9
+ "fetch\\s*\\(",
10
+ "axios\\.(?:get|post|put|delete|patch|request)\\s*\\(",
11
+ "http\\.(?:get|post|put|delete|patch|request)\\s*\\(",
12
+ "https\\.(?:get|post|put|delete|patch|request)\\s*\\(",
13
+ "(?:^|\\s|=|\\()request\\s*\\(",
14
+ "got\\s*\\(",
15
+ "superagent\\.",
16
+ "needle\\.",
17
+ "bent\\(",
18
+ "node-fetch\\s*\\(",
19
+ "isomorphic-fetch\\s*\\(",
20
+ "ky\\s*\\(",
21
+ "httpClient\\.",
22
+ "\\.httpClient\\."
23
+ ],
24
+ "userInputSources": [
25
+ "req\\.body",
26
+ "req\\.query",
27
+ "req\\.params",
28
+ "request\\.body",
29
+ "request\\.query",
30
+ "request\\.params",
31
+ "ctx\\.request\\.body",
32
+ "ctx\\.query",
33
+ "ctx\\.params",
34
+ "event\\.body",
35
+ "event\\.queryStringParameters",
36
+ "event\\.pathParameters",
37
+ "\\.query\\.",
38
+ "\\.body\\.",
39
+ "\\.params\\.",
40
+ "process\\.argv",
41
+ "process\\.env\\.",
42
+ "from.*request",
43
+ "from.*input",
44
+ "user.*input",
45
+ "client.*data",
46
+ "external.*data"
47
+ ],
48
+ "dangerousProtocols": [
49
+ "file://",
50
+ "ftp://",
51
+ "sftp://",
52
+ "ldap://",
53
+ "ldaps://",
54
+ "dict://",
55
+ "gopher://",
56
+ "jar://",
57
+ "netdoc://",
58
+ "mailto:",
59
+ "news:",
60
+ "imap://",
61
+ "pop3://",
62
+ "smb://",
63
+ "afp://",
64
+ "telnet://",
65
+ "ssh://"
66
+ ],
67
+ "blockedIPs": [
68
+ "127\\.0\\.0\\.1",
69
+ "::1",
70
+ "localhost",
71
+ "169\\.254\\.169\\.254",
72
+ "metadata\\.google\\.internal",
73
+ "169\\.254\\.",
74
+ "10\\.",
75
+ "172\\.(1[6-9]|2[0-9]|3[01])\\.",
76
+ "192\\.168\\."
77
+ ],
78
+ "blockedPorts": [
79
+ "22",
80
+ "23",
81
+ "25",
82
+ "53",
83
+ "135",
84
+ "139",
85
+ "445",
86
+ "1433",
87
+ "1521",
88
+ "3306",
89
+ "3389",
90
+ "5432",
91
+ "5984",
92
+ "6379",
93
+ "8080",
94
+ "9200",
95
+ "11211",
96
+ "27017",
97
+ "50070"
98
+ ],
99
+ "allowedDomains": [
100
+ "api\\.trusted-service\\.com",
101
+ "service\\.company\\.com"
102
+ ],
103
+ "validationFunctions": [
104
+ "validateUrl",
105
+ "validateUrlAllowList",
106
+ "checkAllowedUrl",
107
+ "isAllowedUrl",
108
+ "sanitizeUrl",
109
+ "verifyUrl",
110
+ "urlValidator"
111
+ ],
112
+ "policy": {
113
+ "requireExplicitValidation": true,
114
+ "enforceAllowList": true,
115
+ "blockPrivateIPs": true,
116
+ "checkProtocols": true,
117
+ "requireHttpsOnly": false,
118
+ "maxRedirects": 0
119
+ },
120
+ "thresholds": {
121
+ "maxSuspiciousUrls": 3,
122
+ "maxUnvalidatedRequests": 1
123
+ }
124
+ }
125
+ }