manifest
Version:
Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard
107 lines • 4.12 kB
JavaScript
;
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