Cursor rule
.cursor/rules/javascript-server-side-request-forgery.mdcDetect 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 blocksRepository
86
— · pushed 280 days agoLast changed
3 days ago
First indexed 3 days ago.12345# JavaScript Server-Side Request Forgery (OWASP A10:2021)67<rule>8name: javascript_server_side_request_forgery9description: Detect and prevent Server-Side Request Forgery (SSRF) vulnerabilities in JavaScript applications as defined in OWASP Top 10:2021-A101011actions:12 - type: enforce13 conditions:14 # Pattern 1: URL from User Input15 - 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."1718 # Pattern 2: Dynamic URL in HTTP Request19 - 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."2122 # Pattern 3: URL Redirection Without Validation23 - 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."2526 # Pattern 4: Direct IP Address Usage27 - 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."2930 # Pattern 5: Local Network Access31 - 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."3334 # Pattern 6: File Protocol Usage35 - 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."3738 # Pattern 7: Missing URL Validation39 - 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."4243 # Pattern 8: HTTP Request in User-Defined Function44 - 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."4748 # Pattern 9: Proxy Functionality49 - 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."5253 # Pattern 10: Alternative HTTP Methods54 - 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."5758 # Pattern 11: URL Building from Parts59 - 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."6162 # Pattern 12: Protocol-Relative URLs63 - pattern: "(fetch|axios)\\s*\\(['\"`]\\/\\/[^'\"`]+['\"`]"64 message: "Protocol-relative URL usage may lead to SSRF. Always specify the protocol and validate URLs."6566 # Pattern 13: Express-like Route with URL Parameter67 - 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."7071 # Pattern 14: URL Parsing without Validation72 - 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."7576 # Pattern 15: Service Discovery / Cloud Metadata Access77 - 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."7980 - type: suggest81 message: |82 **JavaScript Server-Side Request Forgery (SSRF) Prevention Best Practices:**8384 1. **Implement URL Validation and Sanitization:**85 - Use built-in URL parsing libraries to validate URLs86 - Validate both the URL format and components87 - Example:88```javascript89 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 here97 return true;98 } catch (error) {99 // Invalid URL format100 return false;101 }102 }103104 // Usage105 const userProvidedUrl = req.body.targetUrl;106 if (!isValidUrl(userProvidedUrl)) {107 return res.status(400).json({ error: 'Invalid URL format or protocol' });108 }109110 // Now make the request with the validated URL111```112113 2. **Implement Strict Allowlisting:**114 - Define allowlist of permitted domains and endpoints115 - Reject requests to any domains not on the allowlist116 - Example:117```javascript118 const ALLOWED_DOMAINS = [119 'api.example.com',120 'cdn.example.com',121 'partner-api.trusted-domain.com'122 ];123124 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 }132133 // Usage134 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?.id141 });142 return res.status(403).json({ error: 'Domain not allowed' });143 }144```145146 3. **Block Access to Internal Networks:**147 - Prevent requests to private IP ranges148 - Block localhost and internal hostnames149 - Example:150```javascript151 function isInternalHostname(hostname) {152 // Check for localhost and common internal hostnames153 if (hostname === 'localhost' || hostname.endsWith('.local') || hostname.endsWith('.internal')) {154 return true;155 }156 return false;157 }158159 function isPrivateIP(ip) {160 // Check for private IP ranges161 const privateRanges = [162 /^127\./, // 127.0.0.0/8163 /^10\./, // 10.0.0.0/8164 /^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12165 /^192\.168\./, // 192.168.0.0/16166 /^169\.254\./, // 169.254.0.0/16167 /^::1$/, // localhost IPv6168 /^f[cd][0-9a-f]{2}:/i, // fc00::/7 unique local IPv6169 /^fe80:/i // fe80::/10 link-local IPv6170 ];171172 return privateRanges.some(range => range.test(ip));173 }174175 function isUrlSafe(url) {176 try {177 const parsedUrl = new URL(url);178179 // Block internal hostnames180 if (isInternalHostname(parsedUrl.hostname)) {181 return false;182 }183184 // Resolve hostname to IP (in real implementation, use async DNS resolution)185 // This example is simplified - in production you would use DNS resolution186 let ip;187 try {188 // Note: This is a pseudo-code example189 // In real code, you'd use a DNS resolution library190 ip = dnsResolve(parsedUrl.hostname);191192 // Block private IPs193 if (isPrivateIP(ip)) {194 return false;195 }196 } catch (error) {197 // If DNS resolution fails, err on the side of caution198 return false;199 }200201 return true;202 } catch (error) {203 return false;204 }205 }206```207208 4. **Disable Dangerous URL Protocols:**209 - Restrict allowed URL protocols to HTTP and HTTPS210 - Block file://, ftp://, gopher://, etc.211 - Example:212```javascript213 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 }222223 // Usage224 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.ip231 });232 return res.status(403).json({ error: 'URL protocol not allowed' });233 }234```235236 5. **Implement Network-Level Protection:**237 - Use firewall rules to block outbound requests to internal networks238 - Configure proxy servers to restrict external requests239 - Example:240```javascript241 // Using a proxy for outbound requests242 const axios = require('axios');243 const HttpsProxyAgent = require('https-proxy-agent');244245 // Configure proxy with appropriate controls246 const httpsAgent = new HttpsProxyAgent({247 host: 'proxy.example.com',248 port: 3128,249 // This proxy should be configured to block access to internal networks250 });251252 // Make requests through the proxy253 async function secureExternalRequest(url) {254 try {255 const response = await axios.get(url, {256 httpsAgent,257 timeout: 5000, // Set reasonable timeout258 maxRedirects: 2 // Limit redirects259 });260 return response.data;261 } catch (error) {262 logger.error({263 message: 'External request failed',264 url,265 error: error.message266 });267 throw new Error('Failed to fetch external resource');268 }269 }270```271272 6. **Use Service-Specific Endpoints:**273 - Instead of passing full URLs, use service identifiers274 - Map identifiers to URLs on the server side275 - Example:276```javascript277 // Client makes request with service identifier, not raw URL278 app.get('/proxy-service/:serviceId', async (req, res) => {279 const { serviceId } = req.params;280281 // Service mapping defined server-side282 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 };287288 // Check if service is defined289 if (!serviceMap[serviceId]) {290 return res.status(404).json({ error: 'Service not found' });291 }292293 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```302303 7. **Implement Context-Specific Encodings:**304 - Use context-appropriate encoding for URL parameters305 - Don't rely solely on standard URL encoding306 - Example:307```javascript308 function safeUrl(baseUrl, params) {309 // Start with a verified base URL310 const url = new URL(baseUrl);311312 // Add parameters safely313 for (const [key, value] of Object.entries(params)) {314 // Ensure values are strings and properly encoded315 url.searchParams.append(key, String(value));316 }317318 // Verify the final URL is still valid319 if (!isAllowedDomain(url.toString())) {320 throw new Error('URL creation resulted in disallowed domain');321 }322323 return url.toString();324 }325326 // Usage327 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 response334 } catch (error) {335 // Handle error336 }337```338339 8. **Use Defense in Depth:**340 - Combine multiple validation strategies341 - Don't rely on a single protection measure342 - Example:343```javascript344 async function secureExternalRequest(url, options = {}) {345 // 1. Validate URL format346 if (!isValidUrl(url)) {347 throw new Error('Invalid URL format');348 }349350 // 2. Check against allowlist351 if (!isAllowedDomain(url)) {352 throw new Error('Domain not in allowlist');353 }354355 // 3. Verify not internal network356 const parsedUrl = new URL(url);357 if (await isInternalNetwork(parsedUrl.hostname)) {358 throw new Error('Access to internal networks not allowed');359 }360361 // 4. Validate protocol362 if (!hasAllowedProtocol(url)) {363 throw new Error('Protocol not allowed');364 }365366 // 5. Set additional security headers and options367 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 };376377 // 6. Make request with all validations passed378 try {379 return await axios(url, secureOptions);380 } catch (error) {381 logger.error({382 message: 'Secure external request failed',383 url,384 error: error.message385 });386 throw new Error('External request failed');387 }388 }389```390391 9. **Validate and Sanitize Request Parameters:**392 - Don't trust any user-supplied input for URL construction393 - Validate all components used in URL building394 - Example:395```javascript396 // API that fetches weather data for a city397 app.get('/api/weather', async (req, res) => {398 const { city } = req.query;399400 // 1. Validate parameter exists and is valid401 if (!city || typeof city !== 'string' || city.length > 100) {402 return res.status(400).json({ error: 'Invalid city parameter' });403 }404405 // 2. Sanitize the parameter406 const sanitizedCity = encodeURIComponent(city.trim());407408 // 3. Construct URL with validated parameter409 const weatherApiUrl = `https://api.weather.example.com/current?city=${sanitizedCity}`;410411 // 4. Additional validation of the final URL412 if (!isValidUrl(weatherApiUrl)) {413 return res.status(400).json({ error: 'Invalid URL construction' });414 }415416 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.message424 });425 return res.status(500).json({ error: 'Failed to fetch weather data' });426 }427 });428```429430 10. **Implement Request Timeouts:**431 - Set appropriate timeouts for all HTTP requests432 - Prevent long-running SSRF probes433 - Example:434```javascript435 async function fetchWithTimeout(url, options = {}) {436 // Default timeout of 5 seconds437 const timeout = options.timeout || 5000;438439 // Create an abort controller to handle timeout440 const controller = new AbortController();441 const timeoutId = setTimeout(() => controller.abort(), timeout);442443 try {444 const response = await fetch(url, {445 ...options,446 signal: controller.signal447 });448449 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 }459460 // Usage461 try {462 const response = await fetchWithTimeout('https://api.example.com/data', {463 timeout: 3000, // 3 seconds timeout464 headers: { 'Content-Type': 'application/json' }465 });466 const data = await response.json();467 // Process data468 } catch (error) {469 console.error('Request failed:', error.message);470 }471```472473 11. **Rate Limit External Requests:**474 - Implement rate limiting for outbound requests475 - Prevent SSRF probing and DoS attacks476 - Example:477```javascript478 const { RateLimiter } = require('limiter');479480 // Create a rate limiter: 100 requests per minute481 const externalRequestLimiter = new RateLimiter({482 tokensPerInterval: 100,483 interval: 'minute'484 });485486 async function rateLimitedRequest(url, options = {}) {487 // Check if we have tokens available488 const remainingRequests = await externalRequestLimiter.removeTokens(1);489490 if (remainingRequests < 0) {491 throw new Error('Rate limit exceeded for external requests');492 }493494 // Proceed with the request495 return axios(url, options);496 }497498 // Usage499 app.get('/api/external-data', async (req, res) => {500 const { url } = req.query;501502 if (!isValidUrl(url) || !isAllowedDomain(url)) {503 return res.status(403).json({ error: 'URL not allowed' });504 }505506 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```517518 12. **Use Web Application Firewalls (WAF):**519 - Configure WAF rules to detect and block SSRF patterns520 - Implement server-side firewall rules521 - Example:522```javascript523 // Middleware to detect SSRF attack patterns524 function ssrfProtectionMiddleware(req, res, next) {525 const url = req.query.url || req.body.url;526527 if (!url) {528 return next();529 }530531 // Check for suspicious URL patterns532 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 ];539540 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?.id548 });549550 return res.status(403).json({551 error: 'Access denied - suspicious URL detected'552 });553 }554555 next();556 }557558 // Apply middleware to all routes559 app.use(ssrfProtectionMiddleware);560```561562 13. **Implement Centralized Request Services:**563 - Create a dedicated service for external requests564 - Implement all security controls in one place565 - Example:566```javascript567 // externalRequestService.js568 const axios = require('axios');569570 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 }577578 async request(url, options = {}) {579 // Validate URL580 if (!this._isValidUrl(url)) {581 throw new Error('Invalid URL format');582 }583584 // Check allowlist585 if (!this._isAllowedDomain(url)) {586 throw new Error('Domain not in allowlist');587 }588589 // Configure request options590 const requestOptions = {591 ...options,592 timeout: options.timeout || this.timeout,593 maxRedirects: options.maxRedirects || this.maxRedirects,594 validateStatus: status => status >= 200 && status < 300595 };596597 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.message605 });606 throw new Error(`External request failed: ${error.message}`);607 }608 }609610 _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 }618619 _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 }628629 module.exports = ExternalRequestService;630631 // Usage in application632 const ExternalRequestService = require('./externalRequestService');633634 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: 3000642 });643644 app.get('/api/external-data', async (req, res) => {645 try {646 // Use the service for all external requests647 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```654655 14. **Monitor and Audit External Requests:**656 - Log all external requests for audit purposes657 - Implement anomaly detection658 - Example:659```javascript660 // Middleware to log and monitor all external requests661 function requestMonitoringMiddleware(req, res, next) {662 // Only intercept routes that might make external requests663 if (!req.path.startsWith('/api/proxy') && !req.path.startsWith('/api/external')) {664 return next();665 }666667 // Store original fetch/http.request methods668 const originalFetch = global.fetch;669 const originalHttpRequest = require('http').request;670 const originalHttpsRequest = require('https').request;671672 // Override fetch673 global.fetch = async function monitoredFetch(url, options) {674 const requestId = uuid.v4();675 const startTime = Date.now();676677 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 });689690 try {691 const response = await originalFetch(url, options);692693 // Log successful request694 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 });702703 return response;704 } catch (error) {705 // Log failed request706 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 });714715 throw error;716 }717 };718719 // Similar overrides for http.request and https.request720 // ...721722 // Continue with the request723 res.on('finish', () => {724 // Restore original methods after request completes725 global.fetch = originalFetch;726 require('http').request = originalHttpRequest;727 require('https').request = originalHttpsRequest;728 });729730 next();731 }732733 // Apply middleware734 app.use(requestMonitoringMiddleware);735```736737 15. **Implement Output Validation:**738 - Validate responses from external services739 - Use schema validation for expected formats740 - Example:741```javascript742 const Joi = require('joi');743744 // Define expected schemas for external APIs745 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 }),752753 userApi: Joi.object({754 id: Joi.string().required(),755 name: Joi.string().required(),756 email: Joi.string().email().required()757 })758 };759760 async function validateExternalResponse(data, schemaName) {761 const schema = apiSchemas[schemaName];762763 if (!schema) {764 throw new Error(`Schema not found: ${schemaName}`);765 }766767 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 debugging776 });777778 throw new Error(`Invalid response format from external API: ${error.message}`);779 }780 }781782 // Usage783 app.get('/api/weather/:city', async (req, res) => {784 const { city } = req.params;785786 try {787 // Fetch data from external API788 const apiUrl = `https://api.weather.example.com/current?city=${encodeURIComponent(city)}`;789 const response = await axios.get(apiUrl);790791 // Validate the response against the expected schema792 const validatedData = await validateExternalResponse(response.data, 'weatherApi');793794 // Return the validated data795 return res.json(validatedData);796 } catch (error) {797 return res.status(500).json({ error: error.message });798 }799 });800```801802 - type: validate803 conditions:804 # Check 1: URL validation805 - pattern: "function\\s+(?:isValidUrl|validateUrl|checkUrl)\\s*\\([^)]*\\)\\s*\\{[^}]*new URL\\([^)]*\\)"806 message: "Using URL validation function with proper parsing."807808 # Check 2: Domain allowlisting809 - pattern: "(?:allowlist|whitelist|allowed(?:Domain|Host))\\s*=\\s*\\["810 message: "Implementing domain allowlisting for outbound requests."811812 # Check 3: Private IP filtering813 - pattern: "(?:isPrivateIP|isInternalNetwork|blockInternalAddresses)"814 message: "Checking for and blocking private IP addresses."815816 # Check 4: Protocol restriction817 - pattern: "(?:allowedProtocols|validProtocols)\\s*=\\s*\\[\\s*['\"]https?:['\"]"818 message: "Restricting URL protocols to HTTP/HTTPS only."819820 # Check 5: Request timeout implementation821 - pattern: "timeout:\\s*\\d+"822 message: "Setting timeouts for outbound HTTP requests."823824metadata:825 priority: high826 version: 1.0827 tags:828 - security829 - javascript830 - nodejs831 - browser832 - ssrf833 - owasp834 - language:javascript835 - framework:express836 - framework:react837 - framework:vue838 - framework:angular839 - category:security840 - subcategory:ssrf841 - standard:owasp-top10842 - risk:a10-server-side-request-forgery843 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
Also in ivangrynenko/cursorrules
Diff this repo’s formatsOne 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?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| ivangrynenko/cursorrules.cursor/rules/accessibility-standards.mdc · 86 | Cursor rules | ui | 44/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/api-standards.mdc · 86 | Cursor rules | api | 44/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/behat-steps.mdc · 86 | Cursor rules | lint-formatstyleperformanceagent-behaviour | 42/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/build-optimization.mdc · 86 | Cursor rules | build | 48/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/confluence-editing-standards.mdc · 86 | Cursor rules | stylearchsecuritydeployment | 60/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/debugging-standards.mdc · 86 | Cursor rules | no sections | 30/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/docker-compose-standards.mdc · 86 | Cursor rules | style | 62/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-broken-access-control.mdc · 86 | Cursor rules | stylesecurity | 52/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-cryptographic-failures.mdc · 86 | Cursor rules | security | 48/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-database-standards.mdc · 86 | Cursor rules | database | 30/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-injection.mdc · 86 | Cursor rules | securitydo-not | 55/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-insecure-design.mdc · 86 | Cursor rules | security | 48/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-integrity-failures.mdc · 86 | Cursor rules | style | 60/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-logging-failures.mdc · 86 | Cursor rules | security | 48/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-security-misconfiguration.mdc · 86 | Cursor rules | security | 48/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/drupal-vulnerable-components.mdc · 86 | Cursor rules | stylesecurity | 67/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/git-commit-standards.mdc · 86 | Cursor rules | git | 44/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/github-actions-standards.mdc · 86 | Cursor rules | no sections | 44/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/improve-cursorrules-efficiency.mdc · 86 | Cursor rules | no sections | 34/100 | 3 days ago | |
| ivangrynenko/cursorrules.cursor/rules/javascript-cryptographic-failures.mdc · 86 | Cursor rules | security | 40/100 | 3 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.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 3 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 3 days ago | |
| skillrecordings/egghead-next.cursor/rules/project-update-user-rules.mdc · 1.4k | Cursor rules | buildtestlint-formatstyle+7 | 96/100 | 3 days ago | |
| nerds-odd-e/doughnut.cursor/rules/cli.mdc · 49 | Cursor rules | setupbuildteststyle+4 | 96/100 | 3 days ago | |
| skillrecordings/egghead-next.cursor/rules/gh-task-plan.mdc · 1.4k | Cursor rules | teststylearchtypes+2 | 96/100 | 3 days ago | |
| skillrecordings/egghead-next.cursor/rules/project-update-rules.mdc · 1.4k | Cursor rules | buildtestlint-formatstyle+7 | 96/100 | 3 days ago |
