pediatric-ai-scribe-v3/src/utils/urlSafety.js

50 lines
2.2 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 || p[1] === 0 || (p[1] === 88 && p[2] === 99))) return true;
if (p[0] === 198 && (p[1] === 18 || p[1] === 19 || (p[1] === 51 && p[2] === 100))) return true;
if (p[0] === 203 && p[1] === 0 && p[2] === 113) return true;
if (p[0] >= 224) return true;
return false;
}
if (net.isIPv6(ip)) {
if (ip.includes('%')) return true;
var low = new URL('http://[' + ip + ']').hostname.slice(1, -1).toLowerCase();
// Global unicast only: excludes mapped/NAT64/local/multicast addresses.
if (!/^[23][0-9a-f]{3}:/.test(low)) return true;
var parts = low.split(':');
var second = parseInt(parts[1] || '0', 16);
// IETF special 2001::/23, documentation 2001:db8::/32 and 3fff::/20,
// plus 6to4 and retired 6bone. Ordinary public 2001:: addresses remain usable.
return (parts[0] === '2001' && (second < 0x200 || second === 0xdb8)) ||
parts[0] === '2002' || parts[0] === '3ffe' || (parts[0] === '3fff' && second < 0x1000);
}
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.replace(/^\[|\]$/g, ''), { 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
};