diff --git a/src/util.ts b/src/util.ts index 8d24195..0db0d13 100644 --- a/src/util.ts +++ b/src/util.ts @@ -26,7 +26,7 @@ export function sanitize(str: string): string { // IPs are accepted — DNS-rebinding protection is out of scope for a desktop // plugin talking to public CDNs. function isPrivateHost(hostname: string): boolean { - let h = hostname.replace(/^\[|\]$/g, '').toLowerCase(); + let h = hostname.replace(/^\[|\]$/g, '').toLowerCase().replace(/\.$/, ''); if (h === 'localhost' || h.endsWith('.local')) return true; if (h.startsWith('::ffff:')) { // IPv4-mapped IPv6; the URL parser serializes it as hex groups diff --git a/tests/util.test.ts b/tests/util.test.ts index cd25bd3..f1e1662 100644 --- a/tests/util.test.ts +++ b/tests/util.test.ts @@ -121,6 +121,8 @@ describe('assertDownloadable', () => { expect(() => assertDownloadable('http://127.0.0.1:8080/x')).toThrow(); expect(() => assertDownloadable('http://127.8.9.10/x')).toThrow(); expect(() => assertDownloadable('http://[::1]/x')).toThrow(); + expect(() => assertDownloadable('http://localhost./x')).toThrow(); // FQDN trailing dot + expect(() => assertDownloadable('http://printer.local./x')).toThrow(); expect(() => assertDownloadable('http://0x7f000001/x')).toThrow(); // URL-normalized to 127.0.0.1 expect(() => assertDownloadable('http://[::ffff:127.0.0.1]/x')).toThrow(); });