@nanggo/social-preview
Version:
Generate beautiful social media preview images from any URL
122 lines (121 loc) • 4.41 kB
JavaScript
;
/**
* 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();
}