UNPKG

manifest

Version:

Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard

107 lines 4.12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isPrivateIp = isPrivateIp; exports.isCloudMetadataIp = isCloudMetadataIp; exports.validatePublicUrl = validatePublicUrl; const promises_1 = require("dns/promises"); const PRIVATE_RANGES = [ cidr('127.0.0.0', 8), cidr('10.0.0.0', 8), cidr('172.16.0.0', 12), cidr('192.168.0.0', 16), cidr('169.254.0.0', 16), cidr('0.0.0.0', 8), ]; const PRIVATE_V6_PREFIXES = ['::1', 'fc00::', 'fd00::', 'fe80::']; function cidr(base, prefix) { const addr = ipv4ToBigInt(base); const mask = (BigInt('0xFFFFFFFF') << BigInt(32 - prefix)) & BigInt('0xFFFFFFFF'); return { addr: addr & mask, mask }; } function ipv4ToBigInt(ip) { const parts = ip.split('.').map(Number); return (BigInt((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) & BigInt('0xFFFFFFFF')); } function isPrivateIp(ip) { const mapped = ip.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i); const normalizedIp = mapped ? mapped[1] : ip; if (normalizedIp.includes(':')) { const lower = normalizedIp.toLowerCase(); return PRIVATE_V6_PREFIXES.some((prefix) => lower.startsWith(prefix)); } const parsed = normalizedIp.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/); if (!parsed) return false; const addr = ipv4ToBigInt(normalizedIp); return PRIVATE_RANGES.some((range) => (addr & range.mask) === range.addr); } const CLOUD_METADATA_RANGES = [ cidr('169.254.169.254', 32), cidr('169.254.0.0', 16), ]; const CLOUD_METADATA_V6 = ['fd00::', 'fe80::']; function isCloudMetadataIp(ip) { const mapped = ip.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i); const normalizedIp = mapped ? mapped[1] : ip; if (normalizedIp.includes(':')) { const lower = normalizedIp.toLowerCase(); return CLOUD_METADATA_V6.some((prefix) => lower.startsWith(prefix)); } const parsed = normalizedIp.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/); if (!parsed) return false; const addr = ipv4ToBigInt(normalizedIp); return CLOUD_METADATA_RANGES.some((range) => (addr & range.mask) === range.addr); } async function validatePublicUrl(url) { if (process.env['NODE_ENV'] === 'test' && process.env['SKIP_SSRF_VALIDATION'] !== 'false') return; let parsed; try { parsed = new URL(url); } catch { throw new Error('Invalid URL format'); } if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { throw new Error('Only http and https URLs are allowed'); } const hostname = parsed.hostname.replace(/^\[|\]$/g, ''); if (process.env['MANIFEST_MODE'] === 'local') { if (isCloudMetadataIp(hostname)) { throw new Error('URLs pointing to cloud metadata endpoints are not allowed'); } try { const result = await (0, promises_1.lookup)(hostname, { all: true }); const addresses = Array.isArray(result) ? result : [result]; for (const entry of addresses) { if (isCloudMetadataIp(entry.address)) { throw new Error('URLs pointing to cloud metadata endpoints are not allowed'); } } } catch (err) { if (err instanceof Error && err.message.includes('cloud metadata')) throw err; } return; } if (isPrivateIp(hostname)) { throw new Error('URLs pointing to private or internal networks are not allowed'); } try { const result = await (0, promises_1.lookup)(hostname, { all: true }); const addresses = Array.isArray(result) ? result : [result]; for (const entry of addresses) { if (isPrivateIp(entry.address)) { throw new Error('URLs pointing to private or internal networks are not allowed'); } } } catch (err) { if (err instanceof Error && err.message.includes('private')) throw err; throw new Error(`Failed to resolve hostname: ${hostname}`); } } //# sourceMappingURL=url-validation.js.map