UNPKG

@nanggo/social-preview

Version:

Generate beautiful social media preview images from any URL

122 lines (121 loc) 4.41 kB
"use strict"; /** * Secure HTTP/HTTPS Agent implementation * Prevents DNS rebinding and SSRF attacks by validating IP addresses at connection time */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSecureHttpAgent = createSecureHttpAgent; exports.createSecureHttpsAgent = createSecureHttpsAgent; exports.getSecureHttpAgent = getSecureHttpAgent; exports.getSecureHttpsAgent = getSecureHttpsAgent; exports.getSecureAgentForUrl = getSecureAgentForUrl; const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const dns_1 = __importDefault(require("dns")); const ip_validation_1 = require("./ip-validation"); /** * Secure DNS lookup function that blocks private/reserved IP addresses * Validates ALL resolved IP addresses to prevent SSRF bypass attacks */ const secureLookup = (hostname, options, callback) => { // Normalize parameters to support both (host, cb) and (host, opts, cb) let actualOptions = {}; let actualCallback; if (typeof options === 'function') { actualCallback = options; actualOptions = {}; } else { actualOptions = options ?? {}; actualCallback = (callback || (() => { })); } // Force 'all' option to get all IP addresses for comprehensive validation const lookupOptions = { ...(typeof actualOptions === 'object' ? actualOptions : {}), all: true }; dns_1.default.lookup(hostname, lookupOptions, (err, addresses) => { if (err) { return actualCallback(err, '', 0); } // Ensure addresses is always an array const addressList = Array.isArray(addresses) ? addresses : [addresses]; if (addressList.length === 0) { const noAddressError = new Error(`No IP addresses resolved for hostname: ${hostname}`); return actualCallback(noAddressError, '', 0); } // Check ALL resolved addresses for security violations const blockedAddresses = []; const safeAddresses = []; for (const addr of addressList) { if ((0, ip_validation_1.isPrivateOrReservedIP)(addr.address)) { blockedAddresses.push(addr.address); } else { safeAddresses.push(addr); } } // If ANY address is private/reserved, block the entire lookup if (blockedAddresses.length > 0) { const securityError = new Error(`Connection blocked due to private/reserved IP addresses: ${blockedAddresses.join(', ')}. ` + `Total resolved: ${addressList.length}, blocked: ${blockedAddresses.length}`); return actualCallback(securityError, blockedAddresses[0], addressList[0]?.family || 4); } // All addresses are safe, return the first safe address (Node.js standard behavior) const firstSafeAddress = safeAddresses[0]; actualCallback(null, firstSafeAddress.address, firstSafeAddress.family); }); }; /** * Create secure HTTP agent with private IP blocking */ function createSecureHttpAgent() { return new http_1.default.Agent({ keepAlive: true, keepAliveMsecs: 30000, maxSockets: 50, maxFreeSockets: 10, timeout: 30000, lookup: secureLookup, }); } /** * Create secure HTTPS agent with private IP blocking */ function createSecureHttpsAgent() { return new https_1.default.Agent({ keepAlive: true, keepAliveMsecs: 30000, maxSockets: 50, maxFreeSockets: 10, timeout: 30000, lookup: secureLookup, }); } /** * Get default secure agents (singleton pattern for performance) */ let defaultHttpAgent; let defaultHttpsAgent; function getSecureHttpAgent() { if (!defaultHttpAgent) { defaultHttpAgent = createSecureHttpAgent(); } return defaultHttpAgent; } function getSecureHttpsAgent() { if (!defaultHttpsAgent) { defaultHttpsAgent = createSecureHttpsAgent(); } return defaultHttpsAgent; } /** * Get appropriate secure agent based on protocol */ function getSecureAgentForUrl(url) { const urlObj = new URL(url); return urlObj.protocol === 'https:' ? getSecureHttpsAgent() : getSecureHttpAgent(); }