pediatric-ai-scribe-v3/src/utils/urlSafety.js
2026-05-08 19:08:29 +02:00

42 lines
1.5 KiB
JavaScript

var dns = require('dns').promises;
var net = require('net');
function isPrivateIp(ip) {
if (!ip) return true;
if (net.isIPv4(ip)) {
var p = ip.split('.').map(Number);
if (p[0] === 10 || p[0] === 127 || p[0] === 0) return true;
if (p[0] === 100 && p[1] >= 64 && p[1] <= 127) return true;
if (p[0] === 169 && p[1] === 254) return true;
if (p[0] === 172 && p[1] >= 16 && p[1] <= 31) return true;
if (p[0] === 192 && p[1] === 168) return true;
if (p[0] >= 224) return true;
return false;
}
if (net.isIPv6(ip)) {
var low = ip.toLowerCase();
if (low === '::1' || low === '::') return true;
if (low.startsWith('fe80:') || low.startsWith('fc') || low.startsWith('fd')) return true;
return false;
}
return true;
}
async function assertSafeHttpsUrl(urlStr, label) {
var u;
try { u = new URL(urlStr); }
catch (e) { throw new Error((label || 'URL') + ' is invalid'); }
if (u.protocol !== 'https:') throw new Error((label || 'URL') + ' must use https://');
if (u.username || u.password) throw new Error((label || 'URL') + ' must not include credentials');
var addrs = await dns.lookup(u.hostname, { all: true });
if (!addrs.length) throw new Error((label || 'URL') + ' did not resolve');
for (var i = 0; i < addrs.length; i++) {
if (isPrivateIp(addrs[i].address)) throw new Error((label || 'URL') + ' resolves to a private IP');
}
return u;
}
module.exports = {
assertSafeHttpsUrl: assertSafeHttpsUrl,
isPrivateIp: isPrivateIp
};