From 24ce601d945f9bb25f2b1faed657d11c8b26c729 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Jun 2026 04:10:47 +1000 Subject: [PATCH] fix(ingest): pinnedDispatcher lookup must use undici array form cb(null, address, family) was returning Invalid IP address: undefined under undici v6. Returning the full records array (each {address, family}) gives undici what it expects and lets it pick the best family. Co-Authored-By: Claude Opus 4.7 --- lib/ingest/safe_fetch.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/ingest/safe_fetch.js b/lib/ingest/safe_fetch.js index e3b4d6b..28ab0b4 100644 --- a/lib/ingest/safe_fetch.js +++ b/lib/ingest/safe_fetch.js @@ -62,15 +62,16 @@ async function resolveAndCheck(host) { throw new SafeFetchError(`${host} resolves to blocked address ${r.address}`, 'blocked_addr'); } } - return records[0]; + return records; // pass all validated records to the dispatcher } -function pinnedDispatcher(address, family) { +function pinnedDispatcher(records) { return new Agent({ connect: { - // undici will call our lookup instead of OS DNS, so the chosen IP - // is the one we already validated. No TOCTOU re-resolution. - lookup: (_hostname, _options, cb) => cb(null, address, family) + // undici will call our lookup instead of OS DNS, so it only sees + // IPs we've already validated. The array form `(err, results)` is + // what undici v6+ expects (results[i] = {address, family}). + lookup: (_hostname, _options, cb) => cb(null, records) } }); } @@ -82,17 +83,14 @@ export async function safeFetch(url, options = {}, { maxHops = 5 } = {}) { if (u.protocol !== 'http:' && u.protocol !== 'https:') { throw new SafeFetchError(`unsupported scheme ${u.protocol}`, 'scheme'); } - let address, family; + let records; if (net.isIP(u.hostname)) { if (isBlockedAddr(u.hostname)) throw new SafeFetchError(`blocked literal IP ${u.hostname}`, 'blocked_addr'); - address = u.hostname; - family = net.isIPv4(u.hostname) ? 4 : 6; + records = [{ address: u.hostname, family: net.isIPv4(u.hostname) ? 4 : 6 }]; } else { - const rec = await resolveAndCheck(u.hostname); - address = rec.address; - family = rec.family; + records = await resolveAndCheck(u.hostname); } - const dispatcher = pinnedDispatcher(address, family); + const dispatcher = pinnedDispatcher(records); const res = await fetch(current, { ...options, redirect: 'manual', dispatcher }); if ([301,302,303,307,308].includes(res.status)) { const loc = res.headers.get('location');