import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import http from 'http';
import { AddressInfo } from 'net';
vi.mock('dns/promises', () => {
return {
default: { lookup: vi.fn() },
lookup: vi.fn(),
};
});
async function setMockLookup(impl: (hostname: string) => Array<{ address: string; family: number }>) {
const dns = await import('dns/promises');
(dns.default.lookup as any).mockImplementation(async (h: string) => impl(h));
(dns.lookup as any).mockImplementation(async (h: string) => impl(h));
}
async function loadPinnedFetch() {
return await import('../src/utils/pinned-fetch');
}
describe('pinnedFetch DNS rebinding defence', () => {
let server: http.Server;
let serverPort: number;
beforeEach(async () => {
server = http.createServer((_req, res) => {
res.writeHead(200, { 'content-type': 'text/html' });
res.end('
ok
');
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
serverPort = (server.address() as AddressInfo).port;
});
afterEach(async () => {
await new Promise((resolve) => server.close(() => resolve()));
});
it('refuses hostnames whose DNS returns a loopback record', async () => {
await setMockLookup(() => [{ address: '127.0.0.1', family: 4 }]);
const { pinnedFetch } = await loadPinnedFetch();
await expect(
pinnedFetch(`http://evil.example:${serverPort}/x`),
).rejects.toMatchObject({ message: expect.stringContaining('private') });
});
it('refuses hostnames where ANY A record is private (dual-answer rebinding)', async () => {
await setMockLookup(() => [
{ address: '8.8.8.8', family: 4 },
{ address: '192.168.1.2', family: 4 },
]);
const { pinnedFetch } = await loadPinnedFetch();
await expect(
pinnedFetch(`http://evil.example:${serverPort}/x`),
).rejects.toMatchObject({ message: expect.stringContaining('private') });
});
it('refuses hostnames that resolve only to IPv6 ULA', async () => {
await setMockLookup(() => [{ address: 'fc00::1', family: 6 }]);
const { pinnedFetch } = await loadPinnedFetch();
await expect(
pinnedFetch(`http://evil.example:${serverPort}/x`),
).rejects.toMatchObject({ message: expect.stringContaining('private') });
});
it('refuses metadata service IP directly', async () => {
await setMockLookup(() => [{ address: '169.254.169.254', family: 4 }]);
const { pinnedFetch } = await loadPinnedFetch();
await expect(
pinnedFetch(`http://evil.example:${serverPort}/x`),
).rejects.toMatchObject({ message: expect.stringContaining('private') });
});
it('refuses when DNS returns no records', async () => {
await setMockLookup(() => []);
const { pinnedFetch } = await loadPinnedFetch();
await expect(
pinnedFetch(`http://evil.example:${serverPort}/x`),
).rejects.toMatchObject({ message: expect.stringContaining('DNS') });
});
});