RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/Cursor rules/ivangrynenko/cursorrules

Cursor rule

.cursor/rules/javascript-server-side-request-forgery.mdc

Detect and prevent Server-Side Request Forgery (SSRF) vulnerabilities in JavaScript applications as defined in OWASP Top 10:2021-A10

Cursor rules

Quality

20/100

Scores the file, not the repository.

Length

2,783 words

1 headings · 15 code blocks

Repository

86

— · pushed 280 days ago

Last changed

3 days ago

First indexed 3 days ago.
ivangrynenko/cursorrules/.cursor/rules/javascript-server-side-request-forgery.mdcRawGitHub
1---
2description: Detect and prevent Server-Side Request Forgery (SSRF) vulnerabilities in JavaScript applications as defined in OWASP Top 10:2021-A10
3globs: **/*.js, **/*.jsx, **/*.ts, **/*.tsx, !**/node_modules/**, !**/dist/**, !**/build/**, !**/coverage/**
4---
5# JavaScript Server-Side Request Forgery (OWASP A10:2021)
6 
7<rule>
8name: javascript_server_side_request_forgery
9description: Detect and prevent Server-Side Request Forgery (SSRF) vulnerabilities in JavaScript applications as defined in OWASP Top 10:2021-A10
10 
11actions:
12 - type: enforce
13 conditions:
14 # Pattern 1: URL from User Input
15 - pattern: "(fetch|axios\\.get|axios\\.post|axios\\.put|axios\\.delete|axios\\.patch|http\\.get|http\\.request|https\\.get|https\\.request|\\$\\.ajax|XMLHttpRequest|got|request|superagent|needle)\\s*\\([^)]*(?:\\$_GET|\\$_POST|\\$_REQUEST|req\\.(?:body|query|params)|request\\.(?:body|query|params)|event\\.(?:body|queryStringParameters|pathParameters)|params|userInput|data\\["
16 message: "Potential SSRF vulnerability: URL constructed from user input. Implement URL validation, allowlisting, or use a URL parser library to validate and sanitize user-provided URLs."
17
18 # Pattern 2: Dynamic URL in HTTP Request
19 - pattern: "(fetch|axios|http\\.get|http\\.request|https\\.get|https\\.request|\\$\\.ajax|XMLHttpRequest|got|request|superagent|needle)\\s*\\(\\s*['\"`]https?:\\/\\/[^'\"`]*['\"`]\\s*\\+\\s*"
20 message: "Potential SSRF vulnerability: Dynamic URL in HTTP request. Use URL parsing and validation before making the request."
21
22 # Pattern 3: URL Redirection Without Validation
23 - pattern: "(res\\.redirect|res\\.location|window\\.location|location\\.href|location\\.replace|location\\.assign|location\\.port|history\\.pushState|history\\.replaceState)\\s*\\([^)]*(?:req\\.(?:query|body|params)|request\\.(?:query|body|params)|userInput)"
24 message: "URL redirection without proper validation may lead to SSRF. Implement strict validation for URLs before redirecting."
25
26 # Pattern 4: Direct IP Address Usage
27 - pattern: "(fetch|axios\\.get|axios\\.post|axios\\.put|axios\\.delete|axios\\.patch|http\\.get|http\\.request|https\\.get|https\\.request)\\s*\\(\\s*['\"`]https?:\\/\\/\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"
28 message: "Direct use of IP addresses in requests may bypass hostname-based restrictions. Consider using allowlisted hostnames instead."
29
30 # Pattern 5: Local Network Access
31 - pattern: "(fetch|axios\\.get|axios\\.post|axios\\.put|axios\\.delete|axios\\.patch|http\\.get|http\\.request|https\\.get|https\\.request)\\s*\\(\\s*['\"`]https?:\\/\\/(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|192\\.168\\.|10\\.|172\\.(?:1[6-9]|2[0-9]|3[0-1])\\.|::1)"
32 message: "Request to internal network address detected. Restrict access to internal resources to prevent SSRF attacks."
33
34 # Pattern 6: File Protocol Usage
35 - pattern: "(fetch|axios\\.get|axios\\.post|axios\\.put|axios\\.delete|axios\\.patch|http\\.get|http\\.request|https\\.get|https\\.request)\\s*\\(\\s*['\"`]file:\\/\\/"
36 message: "Use of file:// protocol may lead to local file access. Block or restrict file:// protocol usage."
37
38 # Pattern 7: Missing URL Validation
39 - pattern: "(fetch|axios\\.get|axios\\.post|axios\\.put|axios\\.delete|axios\\.patch|http\\.get|http\\.request|https\\.get|https\\.request)\\s*\\([^)]*\\burl\\b[^)]*\\)"
40 negative_pattern: "(validat|sanitiz|check|parse).*\\burl\\b|allowlist|whitelist|URL\\.(parse|canParse)|new URL\\(|isValidURL"
41 message: "HTTP request without URL validation. Implement URL validation before making external requests."
42
43 # Pattern 8: HTTP Request in User-Defined Function
44 - pattern: "function\\s+[a-zA-Z0-9_]*(?:request|fetch|get|http|curl)\\s*\\([^)]*\\)\\s*\\{[^}]*(?:fetch|axios|http\\.get|http\\.request|https\\.get|https\\.request)"
45 negative_pattern: "(validat|sanitiz|check|parse).*\\burl\\b|allowlist|whitelist|new URL\\(|isValidURL"
46 message: "User-defined HTTP request function without URL validation. Implement proper URL validation and sanitization."
47
48 # Pattern 9: Proxy Functionality
49 - pattern: "(?:proxy|forward|relay).*(?:req\\.(?:url|path)|request\\.(?:url|path))"
50 negative_pattern: "(validat|sanitiz|check|parse).*\\burl\\b|allowlist|whitelist"
51 message: "Proxy or request forwarding functionality detected. Implement strict URL validation and allowlisting."
52
53 # Pattern 10: Alternative HTTP Methods
54 - pattern: "(fetch|axios)\\s*\\([^)]*method\\s*:\\s*['\"`](?:GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)['\"`]"
55 negative_pattern: "(validat|sanitiz|check|parse).*\\burl\\b|allowlist|whitelist|new URL\\(|isValidURL"
56 message: "HTTP request with explicit method without URL validation. Implement URL validation for all HTTP methods."
57
58 # Pattern 11: URL Building from Parts
59 - pattern: "new URL\\s*\\((?:[^,)]+,\\s*){1,}(?:req\\.(?:body|query|params)|request\\.(?:body|query|params)|userinput)"
60 message: "Building URL with user input. Validate and sanitize all URL components and use an allowlist for base URLs."
61
62 # Pattern 12: Protocol-Relative URLs
63 - pattern: "(fetch|axios)\\s*\\(['\"`]\\/\\/[^'\"`]+['\"`]"
64 message: "Protocol-relative URL usage may lead to SSRF. Always specify the protocol and validate URLs."
65
66 # Pattern 13: Express-like Route with URL Parameter
67 - pattern: "app\\.(?:get|post|put|delete|patch)\\s*\\(['\"`][^'\"`]*\\/:[a-zA-Z0-9_]+(?:\\/|['\"`])"
68 negative_pattern: "(validat|sanitiz|check|parse).*\\burl\\b|allowlist|whitelist|new URL\\(|isValidURL"
69 message: "Route with dynamic parameter that might be used in URL construction. Ensure proper validation before making any HTTP requests within this route handler."
70
71 # Pattern 14: URL Parsing without Validation
72 - pattern: "URL\\.parse\\s*\\(|new URL\\s*\\("
73 negative_pattern: "try\\s*\\{|catch\\s*\\(|validat|sanitiz|check"
74 message: "URL parsing without validation or error handling. Implement proper error handling and validation for URL parsing."
75
76 # Pattern 15: Service Discovery / Cloud Metadata Access
77 - pattern: "(fetch|axios\\.get|http\\.get)\\s*\\(['\"`]https?:\\/\\/(?:169\\.254\\.169\\.254|fd00:ec2|metadata\\.google|metadata\\.azure|169\\.254\\.169\\.254\\/latest\\/meta-data)"
78 message: "Access to cloud service metadata endpoints detected. Restrict access to cloud metadata services to prevent server information disclosure."
79 
80 - type: suggest
81 message: |
82 **JavaScript Server-Side Request Forgery (SSRF) Prevention Best Practices:**
83
84 1. **Implement URL Validation and Sanitization:**
85 - Use built-in URL parsing libraries to validate URLs
86 - Validate both the URL format and components
87 - Example:
88```javascript
89 function isValidUrl(url) {
90 try {
91 const parsedUrl = new URL(url);
92 // Check protocol is http: or https:
93 if (!/^https?:$/.test(parsedUrl.protocol)) {
94 return false;
95 }
96 // Additional validation logic here
97 return true;
98 } catch (error) {
99 // Invalid URL format
100 return false;
101 }
102 }
103
104 // Usage
105 const userProvidedUrl = req.body.targetUrl;
106 if (!isValidUrl(userProvidedUrl)) {
107 return res.status(400).json({ error: 'Invalid URL format or protocol' });
108 }
109
110 // Now make the request with the validated URL
111```
112
113 2. **Implement Strict Allowlisting:**
114 - Define allowlist of permitted domains and endpoints
115 - Reject requests to any domains not on the allowlist
116 - Example:
117```javascript
118 const ALLOWED_DOMAINS = [
119 'api.example.com',
120 'cdn.example.com',
121 'partner-api.trusted-domain.com'
122 ];
123
124 function isAllowedDomain(url) {
125 try {
126 const parsedUrl = new URL(url);
127 return ALLOWED_DOMAINS.includes(parsedUrl.hostname);
128 } catch (error) {
129 return false;
130 }
131 }
132
133 // Usage
134 const targetUrl = req.body.webhookUrl;
135 if (!isAllowedDomain(targetUrl)) {
136 logger.warn({
137 message: 'SSRF attempt blocked: domain not in allowlist',
138 url: targetUrl,
139 ip: req.ip,
140 userId: req.user?.id
141 });
142 return res.status(403).json({ error: 'Domain not allowed' });
143 }
144```
145
146 3. **Block Access to Internal Networks:**
147 - Prevent requests to private IP ranges
148 - Block localhost and internal hostnames
149 - Example:
150```javascript
151 function isInternalHostname(hostname) {
152 // Check for localhost and common internal hostnames
153 if (hostname === 'localhost' || hostname.endsWith('.local') || hostname.endsWith('.internal')) {
154 return true;
155 }
156 return false;
157 }
158
159 function isPrivateIP(ip) {
160 // Check for private IP ranges
161 const privateRanges = [
162 /^127\./, // 127.0.0.0/8
163 /^10\./, // 10.0.0.0/8
164 /^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12
165 /^192\.168\./, // 192.168.0.0/16
166 /^169\.254\./, // 169.254.0.0/16
167 /^::1$/, // localhost IPv6
168 /^f[cd][0-9a-f]{2}:/i, // fc00::/7 unique local IPv6
169 /^fe80:/i // fe80::/10 link-local IPv6
170 ];
171
172 return privateRanges.some(range => range.test(ip));
173 }
174
175 function isUrlSafe(url) {
176 try {
177 const parsedUrl = new URL(url);
178
179 // Block internal hostnames
180 if (isInternalHostname(parsedUrl.hostname)) {
181 return false;
182 }
183
184 // Resolve hostname to IP (in real implementation, use async DNS resolution)
185 // This example is simplified - in production you would use DNS resolution
186 let ip;
187 try {
188 // Note: This is a pseudo-code example
189 // In real code, you'd use a DNS resolution library
190 ip = dnsResolve(parsedUrl.hostname);
191
192 // Block private IPs
193 if (isPrivateIP(ip)) {
194 return false;
195 }
196 } catch (error) {
197 // If DNS resolution fails, err on the side of caution
198 return false;
199 }
200
201 return true;
202 } catch (error) {
203 return false;
204 }
205 }
206```
207
208 4. **Disable Dangerous URL Protocols:**
209 - Restrict allowed URL protocols to HTTP and HTTPS
210 - Block file://, ftp://, gopher://, etc.
211 - Example:
212```javascript
213 function hasAllowedProtocol(url) {
214 try {
215 const parsedUrl = new URL(url);
216 const allowedProtocols = ['http:', 'https:'];
217 return allowedProtocols.includes(parsedUrl.protocol);
218 } catch (error) {
219 return false;
220 }
221 }
222
223 // Usage
224 const targetUrl = req.body.documentUrl;
225 if (!hasAllowedProtocol(targetUrl)) {
226 logger.warn({
227 message: 'SSRF attempt blocked: disallowed protocol',
228 url: targetUrl,
229 protocol: new URL(targetUrl).protocol,
230 ip: req.ip
231 });
232 return res.status(403).json({ error: 'URL protocol not allowed' });
233 }
234```
235
236 5. **Implement Network-Level Protection:**
237 - Use firewall rules to block outbound requests to internal networks
238 - Configure proxy servers to restrict external requests
239 - Example:
240```javascript
241 // Using a proxy for outbound requests
242 const axios = require('axios');
243 const HttpsProxyAgent = require('https-proxy-agent');
244
245 // Configure proxy with appropriate controls
246 const httpsAgent = new HttpsProxyAgent({
247 host: 'proxy.example.com',
248 port: 3128,
249 // This proxy should be configured to block access to internal networks
250 });
251
252 // Make requests through the proxy
253 async function secureExternalRequest(url) {
254 try {
255 const response = await axios.get(url, {
256 httpsAgent,
257 timeout: 5000, // Set reasonable timeout
258 maxRedirects: 2 // Limit redirects
259 });
260 return response.data;
261 } catch (error) {
262 logger.error({
263 message: 'External request failed',
264 url,
265 error: error.message
266 });
267 throw new Error('Failed to fetch external resource');
268 }
269 }
270```
271
272 6. **Use Service-Specific Endpoints:**
273 - Instead of passing full URLs, use service identifiers
274 - Map identifiers to URLs on the server side
275 - Example:
276```javascript
277 // Client makes request with service identifier, not raw URL
278 app.get('/proxy-service/:serviceId', async (req, res) => {
279 const { serviceId } = req.params;
280
281 // Service mapping defined server-side
282 const serviceMap = {
283 'weather-api': 'https://api.weather.example.com/current',
284 'news-feed': 'https://api.news.example.com/feed',
285 'product-info': 'https://api.products.example.com/details'
286 };
287
288 // Check if service is defined
289 if (!serviceMap[serviceId]) {
290 return res.status(404).json({ error: 'Service not found' });
291 }
292
293 try {
294 // Make request to mapped URL (not user-controlled)
295 const response = await axios.get(serviceMap[serviceId]);
296 return res.json(response.data);
297 } catch (error) {
298 return res.status(500).json({ error: 'Service request failed' });
299 }
300 });
301```
302
303 7. **Implement Context-Specific Encodings:**
304 - Use context-appropriate encoding for URL parameters
305 - Don't rely solely on standard URL encoding
306 - Example:
307```javascript
308 function safeUrl(baseUrl, params) {
309 // Start with a verified base URL
310 const url = new URL(baseUrl);
311
312 // Add parameters safely
313 for (const [key, value] of Object.entries(params)) {
314 // Ensure values are strings and properly encoded
315 url.searchParams.append(key, String(value));
316 }
317
318 // Verify the final URL is still valid
319 if (!isAllowedDomain(url.toString())) {
320 throw new Error('URL creation resulted in disallowed domain');
321 }
322
323 return url.toString();
324 }
325
326 // Usage
327 try {
328 const apiUrl = safeUrl('https://api.example.com/data', {
329 id: userId,
330 format: 'json'
331 });
332 const response = await axios.get(apiUrl);
333 // Process response
334 } catch (error) {
335 // Handle error
336 }
337```
338
339 8. **Use Defense in Depth:**
340 - Combine multiple validation strategies
341 - Don't rely on a single protection measure
342 - Example:
343```javascript
344 async function secureExternalRequest(url, options = {}) {
345 // 1. Validate URL format
346 if (!isValidUrl(url)) {
347 throw new Error('Invalid URL format');
348 }
349
350 // 2. Check against allowlist
351 if (!isAllowedDomain(url)) {
352 throw new Error('Domain not in allowlist');
353 }
354
355 // 3. Verify not internal network
356 const parsedUrl = new URL(url);
357 if (await isInternalNetwork(parsedUrl.hostname)) {
358 throw new Error('Access to internal networks not allowed');
359 }
360
361 // 4. Validate protocol
362 if (!hasAllowedProtocol(url)) {
363 throw new Error('Protocol not allowed');
364 }
365
366 // 5. Set additional security headers and options
367 const secureOptions = {
368 ...options,
369 timeout: options.timeout || 5000,
370 maxRedirects: options.maxRedirects || 2,
371 headers: {
372 ...options.headers,
373 'User-Agent': 'SecureApp/1.0'
374 }
375 };
376
377 // 6. Make request with all validations passed
378 try {
379 return await axios(url, secureOptions);
380 } catch (error) {
381 logger.error({
382 message: 'Secure external request failed',
383 url,
384 error: error.message
385 });
386 throw new Error('External request failed');
387 }
388 }
389```
390
391 9. **Validate and Sanitize Request Parameters:**
392 - Don't trust any user-supplied input for URL construction
393 - Validate all components used in URL building
394 - Example:
395```javascript
396 // API that fetches weather data for a city
397 app.get('/api/weather', async (req, res) => {
398 const { city } = req.query;
399
400 // 1. Validate parameter exists and is valid
401 if (!city || typeof city !== 'string' || city.length > 100) {
402 return res.status(400).json({ error: 'Invalid city parameter' });
403 }
404
405 // 2. Sanitize the parameter
406 const sanitizedCity = encodeURIComponent(city.trim());
407
408 // 3. Construct URL with validated parameter
409 const weatherApiUrl = `https://api.weather.example.com/current?city=${sanitizedCity}`;
410
411 // 4. Additional validation of the final URL
412 if (!isValidUrl(weatherApiUrl)) {
413 return res.status(400).json({ error: 'Invalid URL construction' });
414 }
415
416 try {
417 const response = await axios.get(weatherApiUrl);
418 return res.json(response.data);
419 } catch (error) {
420 logger.error({
421 message: 'Weather API request failed',
422 city,
423 error: error.message
424 });
425 return res.status(500).json({ error: 'Failed to fetch weather data' });
426 }
427 });
428```
429
430 10. **Implement Request Timeouts:**
431 - Set appropriate timeouts for all HTTP requests
432 - Prevent long-running SSRF probes
433 - Example:
434```javascript
435 async function fetchWithTimeout(url, options = {}) {
436 // Default timeout of 5 seconds
437 const timeout = options.timeout || 5000;
438
439 // Create an abort controller to handle timeout
440 const controller = new AbortController();
441 const timeoutId = setTimeout(() => controller.abort(), timeout);
442
443 try {
444 const response = await fetch(url, {
445 ...options,
446 signal: controller.signal
447 });
448
449 clearTimeout(timeoutId);
450 return response;
451 } catch (error) {
452 clearTimeout(timeoutId);
453 if (error.name === 'AbortError') {
454 throw new Error(`Request timed out after ${timeout}ms`);
455 }
456 throw error;
457 }
458 }
459
460 // Usage
461 try {
462 const response = await fetchWithTimeout('https://api.example.com/data', {
463 timeout: 3000, // 3 seconds timeout
464 headers: { 'Content-Type': 'application/json' }
465 });
466 const data = await response.json();
467 // Process data
468 } catch (error) {
469 console.error('Request failed:', error.message);
470 }
471```
472
473 11. **Rate Limit External Requests:**
474 - Implement rate limiting for outbound requests
475 - Prevent SSRF probing and DoS attacks
476 - Example:
477```javascript
478 const { RateLimiter } = require('limiter');
479
480 // Create a rate limiter: 100 requests per minute
481 const externalRequestLimiter = new RateLimiter({
482 tokensPerInterval: 100,
483 interval: 'minute'
484 });
485
486 async function rateLimitedRequest(url, options = {}) {
487 // Check if we have tokens available
488 const remainingRequests = await externalRequestLimiter.removeTokens(1);
489
490 if (remainingRequests < 0) {
491 throw new Error('Rate limit exceeded for external requests');
492 }
493
494 // Proceed with the request
495 return axios(url, options);
496 }
497
498 // Usage
499 app.get('/api/external-data', async (req, res) => {
500 const { url } = req.query;
501
502 if (!isValidUrl(url) || !isAllowedDomain(url)) {
503 return res.status(403).json({ error: 'URL not allowed' });
504 }
505
506 try {
507 const response = await rateLimitedRequest(url);
508 return res.json(response.data);
509 } catch (error) {
510 if (error.message === 'Rate limit exceeded for external requests') {
511 return res.status(429).json({ error: 'Too many requests' });
512 }
513 return res.status(500).json({ error: 'Failed to fetch data' });
514 }
515 });
516```
517
518 12. **Use Web Application Firewalls (WAF):**
519 - Configure WAF rules to detect and block SSRF patterns
520 - Implement server-side firewall rules
521 - Example:
522```javascript
523 // Middleware to detect SSRF attack patterns
524 function ssrfProtectionMiddleware(req, res, next) {
525 const url = req.query.url || req.body.url;
526
527 if (!url) {
528 return next();
529 }
530
531 // Check for suspicious URL patterns
532 const ssrfPatterns = [
533 /file:\/\//i,
534 /^(ftps?|gopher|data|dict):\/\//i,
535 /^\/\/\//,
536 /(localhost|127\.0\.0\.1|0\.0\.0\.0|::1)/i,
537 /^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)/
538 ];
539
540 if (ssrfPatterns.some(pattern => pattern.test(url))) {
541 logger.warn({
542 message: 'Potential SSRF attack detected',
543 url,
544 ip: req.ip,
545 path: req.path,
546 method: req.method,
547 userId: req.user?.id
548 });
549
550 return res.status(403).json({
551 error: 'Access denied - suspicious URL detected'
552 });
553 }
554
555 next();
556 }
557
558 // Apply middleware to all routes
559 app.use(ssrfProtectionMiddleware);
560```
561
562 13. **Implement Centralized Request Services:**
563 - Create a dedicated service for external requests
564 - Implement all security controls in one place
565 - Example:
566```javascript
567 // externalRequestService.js
568 const axios = require('axios');
569
570 class ExternalRequestService {
571 constructor(options = {}) {
572 this.allowedDomains = options.allowedDomains || [];
573 this.maxRedirects = options.maxRedirects || 2;
574 this.timeout = options.timeout || 5000;
575 this.logger = options.logger || console;
576 }
577
578 async request(url, options = {}) {
579 // Validate URL
580 if (!this._isValidUrl(url)) {
581 throw new Error('Invalid URL format');
582 }
583
584 // Check allowlist
585 if (!this._isAllowedDomain(url)) {
586 throw new Error('Domain not in allowlist');
587 }
588
589 // Configure request options
590 const requestOptions = {
591 ...options,
592 timeout: options.timeout || this.timeout,
593 maxRedirects: options.maxRedirects || this.maxRedirects,
594 validateStatus: status => status >= 200 && status < 300
595 };
596
597 try {
598 const response = await axios(url, requestOptions);
599 return response.data;
600 } catch (error) {
601 this.logger.error({
602 message: 'External request failed',
603 url,
604 error: error.message
605 });
606 throw new Error(`External request failed: ${error.message}`);
607 }
608 }
609
610 _isValidUrl(url) {
611 try {
612 const parsedUrl = new URL(url);
613 return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
614 } catch (error) {
615 return false;
616 }
617 }
618
619 _isAllowedDomain(url) {
620 try {
621 const parsedUrl = new URL(url);
622 return this.allowedDomains.includes(parsedUrl.hostname);
623 } catch (error) {
624 return false;
625 }
626 }
627 }
628
629 module.exports = ExternalRequestService;
630
631 // Usage in application
632 const ExternalRequestService = require('./externalRequestService');
633
634 const requestService = new ExternalRequestService({
635 allowedDomains: [
636 'api.example.com',
637 'cdn.example.com',
638 'partner.trusted-domain.com'
639 ],
640 logger: appLogger,
641 timeout: 3000
642 });
643
644 app.get('/api/external-data', async (req, res) => {
645 try {
646 // Use the service for all external requests
647 const data = await requestService.request('https://api.example.com/data');
648 return res.json(data);
649 } catch (error) {
650 return res.status(500).json({ error: error.message });
651 }
652 });
653```
654
655 14. **Monitor and Audit External Requests:**
656 - Log all external requests for audit purposes
657 - Implement anomaly detection
658 - Example:
659```javascript
660 // Middleware to log and monitor all external requests
661 function requestMonitoringMiddleware(req, res, next) {
662 // Only intercept routes that might make external requests
663 if (!req.path.startsWith('/api/proxy') && !req.path.startsWith('/api/external')) {
664 return next();
665 }
666
667 // Store original fetch/http.request methods
668 const originalFetch = global.fetch;
669 const originalHttpRequest = require('http').request;
670 const originalHttpsRequest = require('https').request;
671
672 // Override fetch
673 global.fetch = async function monitoredFetch(url, options) {
674 const requestId = uuid.v4();
675 const startTime = Date.now();
676
677 logger.info({
678 message: 'External request initiated',
679 requestId,
680 url,
681 method: options?.method || 'GET',
682 userContext: {
683 userId: req.user?.id,
684 ip: req.ip,
685 userAgent: req.headers['user-agent']
686 },
687 timestamp: new Date().toISOString()
688 });
689
690 try {
691 const response = await originalFetch(url, options);
692
693 // Log successful request
694 logger.info({
695 message: 'External request completed',
696 requestId,
697 url,
698 statusCode: response.status,
699 duration: Date.now() - startTime,
700 timestamp: new Date().toISOString()
701 });
702
703 return response;
704 } catch (error) {
705 // Log failed request
706 logger.error({
707 message: 'External request failed',
708 requestId,
709 url,
710 error: error.message,
711 duration: Date.now() - startTime,
712 timestamp: new Date().toISOString()
713 });
714
715 throw error;
716 }
717 };
718
719 // Similar overrides for http.request and https.request
720 // ...
721
722 // Continue with the request
723 res.on('finish', () => {
724 // Restore original methods after request completes
725 global.fetch = originalFetch;
726 require('http').request = originalHttpRequest;
727 require('https').request = originalHttpsRequest;
728 });
729
730 next();
731 }
732
733 // Apply middleware
734 app.use(requestMonitoringMiddleware);
735```
736
737 15. **Implement Output Validation:**
738 - Validate responses from external services
739 - Use schema validation for expected formats
740 - Example:
741```javascript
742 const Joi = require('joi');
743
744 // Define expected schemas for external APIs
745 const apiSchemas = {
746 weatherApi: Joi.object({
747 location: Joi.string().required(),
748 temperature: Joi.number().required(),
749 conditions: Joi.string().required(),
750 forecast: Joi.array().items(Joi.object())
751 }),
752
753 userApi: Joi.object({
754 id: Joi.string().required(),
755 name: Joi.string().required(),
756 email: Joi.string().email().required()
757 })
758 };
759
760 async function validateExternalResponse(data, schemaName) {
761 const schema = apiSchemas[schemaName];
762
763 if (!schema) {
764 throw new Error(`Schema not found: ${schemaName}`);
765 }
766
767 try {
768 const result = await schema.validateAsync(data);
769 return result;
770 } catch (error) {
771 logger.error({
772 message: 'External API response validation failed',
773 schemaName,
774 error: error.message,
775 data: JSON.stringify(data).substring(0, 200) // Log partial data for debugging
776 });
777
778 throw new Error(`Invalid response format from external API: ${error.message}`);
779 }
780 }
781
782 // Usage
783 app.get('/api/weather/:city', async (req, res) => {
784 const { city } = req.params;
785
786 try {
787 // Fetch data from external API
788 const apiUrl = `https://api.weather.example.com/current?city=${encodeURIComponent(city)}`;
789 const response = await axios.get(apiUrl);
790
791 // Validate the response against the expected schema
792 const validatedData = await validateExternalResponse(response.data, 'weatherApi');
793
794 // Return the validated data
795 return res.json(validatedData);
796 } catch (error) {
797 return res.status(500).json({ error: error.message });
798 }
799 });
800```
801 
802 - type: validate
803 conditions:
804 # Check 1: URL validation
805 - pattern: "function\\s+(?:isValidUrl|validateUrl|checkUrl)\\s*\\([^)]*\\)\\s*\\{[^}]*new URL\\([^)]*\\)"
806 message: "Using URL validation function with proper parsing."
807
808 # Check 2: Domain allowlisting
809 - pattern: "(?:allowlist|whitelist|allowed(?:Domain|Host))\\s*=\\s*\\["
810 message: "Implementing domain allowlisting for outbound requests."
811
812 # Check 3: Private IP filtering
813 - pattern: "(?:isPrivateIP|isInternalNetwork|blockInternalAddresses)"
814 message: "Checking for and blocking private IP addresses."
815
816 # Check 4: Protocol restriction
817 - pattern: "(?:allowedProtocols|validProtocols)\\s*=\\s*\\[\\s*['\"]https?:['\"]"
818 message: "Restricting URL protocols to HTTP/HTTPS only."
819
820 # Check 5: Request timeout implementation
821 - pattern: "timeout:\\s*\\d+"
822 message: "Setting timeouts for outbound HTTP requests."
823 
824metadata:
825 priority: high
826 version: 1.0
827 tags:
828 - security
829 - javascript
830 - nodejs
831 - browser
832 - ssrf
833 - owasp
834 - language:javascript
835 - framework:express
836 - framework:react
837 - framework:vue
838 - framework:angular
839 - category:security
840 - subcategory:ssrf
841 - standard:owasp-top10
842 - risk:a10-server-side-request-forgery
843 references:
844 - "https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/"
845 - "https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"
846 - "https://portswigger.net/web-security/ssrf"
847 - "https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.md"
848 - "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/19-Server-Side_Request_Forgery"
849 - "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html#ssrf-protection"
850</rule>
851 

Sections

  • JavaScript Server-Side Request Forgery (OWASP A10:2021)

Stack — with the evidence

shell

(0.80)

github-actions

(0.60)

Glob targeting

  • **/*.js
  • **/*.jsx
  • **/*.ts
  • **/*.tsx
  • !**/node_modules/**
  • !**/dist/**
  • !**/build/**
  • !**/coverage/**

Format

Cursor rules

The most expressive format here. Many small .mdc files, each with frontmatter declaring when it should load, so a rule about migrations only enters context when a migration is open. Costs the most to maintain and only one editor reads it.

What the corpus says about it

Repository

Owner
ivangrynenko
Language
—
License
—
Archived
no

All configs in this repo

Also in ivangrynenko/cursorrules

Diff this repo’s formats

One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?

The other instruction files in this repository
RepositoryFormatStackCoversScoreChanged
ivangrynenko/cursorrules.cursor/rules/accessibility-standards.mdc · 86Cursor rulesshellgithub-actionsui44/1003 days ago
ivangrynenko/cursorrules.cursor/rules/api-standards.mdc · 86Cursor rulesshellgithub-actionsapi44/1003 days ago
ivangrynenko/cursorrules.cursor/rules/behat-steps.mdc · 86Cursor rulesshellgithub-actionslint-formatstyleperformanceagent-behaviour42/1003 days ago
ivangrynenko/cursorrules.cursor/rules/build-optimization.mdc · 86Cursor rulesshellgithub-actionsbuild48/1003 days ago
ivangrynenko/cursorrules.cursor/rules/confluence-editing-standards.mdc · 86Cursor rulesshellgithub-actionsstylearchsecuritydeployment60/1003 days ago
ivangrynenko/cursorrules.cursor/rules/debugging-standards.mdc · 86Cursor rulesshellgithub-actionsno sections30/1003 days ago
ivangrynenko/cursorrules.cursor/rules/docker-compose-standards.mdc · 86Cursor rulesshellgithub-actionsstyle62/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-broken-access-control.mdc · 86Cursor rulesshellgithub-actionsstylesecurity52/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-cryptographic-failures.mdc · 86Cursor rulesshellgithub-actionssecurity48/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-database-standards.mdc · 86Cursor rulesshellgithub-actionsdatabase30/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-injection.mdc · 86Cursor rulesshellgithub-actionssecuritydo-not55/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-insecure-design.mdc · 86Cursor rulesshellgithub-actionssecurity48/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-integrity-failures.mdc · 86Cursor rulesshellgithub-actionsstyle60/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-logging-failures.mdc · 86Cursor rulesshellgithub-actionssecurity48/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-security-misconfiguration.mdc · 86Cursor rulesshellgithub-actionssecurity48/1003 days ago
ivangrynenko/cursorrules.cursor/rules/drupal-vulnerable-components.mdc · 86Cursor rulesshellgithub-actionsstylesecurity67/1003 days ago
ivangrynenko/cursorrules.cursor/rules/git-commit-standards.mdc · 86Cursor rulesshellgithub-actionsgit44/1003 days ago
ivangrynenko/cursorrules.cursor/rules/github-actions-standards.mdc · 86Cursor rulesshellgithub-actionsno sections44/1003 days ago
ivangrynenko/cursorrules.cursor/rules/improve-cursorrules-efficiency.mdc · 86Cursor rulesshellgithub-actionsno sections34/1003 days ago
ivangrynenko/cursorrules.cursor/rules/javascript-cryptographic-failures.mdc · 86Cursor rulesshellgithub-actionssecurity40/1003 days ago
Diff against .cursor/rules/accessibility-standards.mdc Diff against .cursor/rules/api-standards.mdc Diff against .cursor/rules/behat-steps.mdc Diff against .cursor/rules/build-optimization.mdc Diff against .cursor/rules/confluence-editing-standards.mdc Diff against .cursor/rules/debugging-standards.mdc Diff against .cursor/rules/docker-compose-standards.mdc Diff against .cursor/rules/drupal-broken-access-control.mdc Diff against .cursor/rules/drupal-cryptographic-failures.mdc Diff against .cursor/rules/drupal-database-standards.mdc Diff against .cursor/rules/drupal-injection.mdc Diff against .cursor/rules/drupal-insecure-design.mdc Diff against .cursor/rules/drupal-integrity-failures.mdc Diff against .cursor/rules/drupal-logging-failures.mdc Diff against .cursor/rules/drupal-security-misconfiguration.mdc Diff against .cursor/rules/drupal-vulnerable-components.mdc Diff against .cursor/rules/git-commit-standards.mdc Diff against .cursor/rules/github-actions-standards.mdc Diff against .cursor/rules/improve-cursorrules-efficiency.mdc Diff against .cursor/rules/javascript-cryptographic-failures.mdc

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45Cursor rulestypescriptpytest+15testlint-formatstylearch+5100/1003 days ago
hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126Cursor rulesgobun+5setupbuildtestlint-format+6100/1003 days ago
langflow-ai/langflow.cursor/rules/docs_development.mdc · 153kCursor rulespythonnode+16setupbuildtestlint-format+797/1003 days ago
TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45Cursor rulestypescriptpytest+15teststyletesting-strategysecurity+397/1003 days ago
skillrecordings/egghead-next.cursor/rules/project-update-user-rules.mdc · 1.4kCursor rulestypescriptnode+14buildtestlint-formatstyle+796/1003 days ago
nerds-odd-e/doughnut.cursor/rules/cli.mdc · 49Cursor rulestypescriptcypress+14setupbuildteststyle+496/1003 days ago
skillrecordings/egghead-next.cursor/rules/gh-task-plan.mdc · 1.4kCursor rulestypescriptnode+14teststylearchtypes+296/1003 days ago
skillrecordings/egghead-next.cursor/rules/project-update-rules.mdc · 1.4kCursor rulestypescriptnode+14buildtestlint-formatstyle+796/1003 days ago
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack