fix(sse): pin DNS on the three public-only image fetch sites (#13883) (#14032)

Merged via /merge-batch (2026-09-19) on top of the current `release/v3.8.51` tip.

**Reconciled before landing:** the train-3 ejection from 2026-09-18 (`tests/unit/image-generation-route.test.ts` → promptless topaz request came back 502 because `pinDns: true` bypassed the test's mocked `globalThis.fetch`) is fixed in `5fafc490` by registering the same `setPinnedFetchTestOverride()` seam the other four image tests already use, cleared in `resetStorage()`/`after`. Production keeps pinning for real.

**Evidence on the merged tree:** `image-generation-route.test.ts` 25/25; all 18 image-path test files that mock `globalThis.fetch` (handler, upscale, fal, nanobanana, agnes, alibaba, bailian, kie, magnific, minimax, pollinations, qwen, edits-multipart, fetch-timeout, route-auth, pindns-toctou-13883) 183/183; `typecheck:core` clean; `check:open-sse-typecheck` 0 errors; file-size, changelog-integrity, complexity and cognitive-complexity gates OK.

Closes #13883
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-09-19 00:41:22 -03:00
committed by GitHub
parent 2e0edd2241
commit ec4d1eff43
10 changed files with 173 additions and 56 deletions

View File

@@ -0,0 +1 @@
- **security(images):** close a DNS-rebinding TOCTOU (#13883) at the three newer public-only image download sites — `resolveImageSource` and the NanoBanana result-URL conversion in `imageGeneration.ts`, and `resolveUpscaleImageSource` in `imageUpscale/shared.ts`. All three validated a caller-supplied URL's DNS answer as public but then let the download perform an independent, un-pinned second resolution at connect time, so a host that answered differently between the two lookups (public, then loopback/LAN) could reach an internal address; they now set `pinDns: true` (reusing the existing `createPinnedFetch` helper already used by embeddings and the vision/audio/video bridges), binding the connection to the exact validated address.

View File

@@ -2226,7 +2226,7 @@ function extractImageInputs(body) {
};
}
async function resolveImageSource(source) {
export async function resolveImageSource(source) {
if (typeof source !== "string" || source.trim().length === 0) {
throw new Error("Invalid image source");
}
@@ -2243,15 +2243,11 @@ async function resolveImageSource(source) {
}
if (isHttpUrl(trimmed)) {
// GHSA-34rg-3pqj-35g9: this URL is caller input (`image_url` / `mask_url` / message
// parts) — pin `public-only` explicitly (string check + DNS validation of every
// resolved answer). Never let it fall back to the operator outbound policy
// (`getProviderOutboundGuard()`), which is `block-metadata` on a local-first default
// install and would let a request body make the server fetch loopback/LAN URLs and
// forward the bytes upstream. `pinDns` stays off on purpose: this handler's only
// transport is `globalThis.fetch` (no `fetchImpl` seam) and connection pinning
// replaces it with a raw undici fetch — same shape as the AI Horde result download.
const remoteImage = await fetchRemoteImage(trimmed, { guard: "public-only" });
// GHSA-34rg-3pqj-35g9 / #13883: caller-input URL — pin `public-only` (never the operator
// outbound policy, which would let a request body reach loopback/LAN) and `pinDns: true`
// to close the DNS-rebinding TOCTOU where a second, un-pinned resolution at connect time
// could answer differently than the validated lookup and bypass the guard.
const remoteImage = await fetchRemoteImage(trimmed, { guard: "public-only", pinDns: true });
return {
buffer: remoteImage.buffer,
base64: remoteImage.buffer.toString("base64"),
@@ -3242,7 +3238,7 @@ function normalizeNanoBananaSyncPayload(data, prompt) {
return { data: images.filter(Boolean) };
}
async function normalizeNanoBananaTaskResult(taskData, body, log) {
export async function normalizeNanoBananaTaskResult(taskData, body, log) {
const response = taskData?.response || {};
const urlCandidates = [
@@ -3280,10 +3276,10 @@ async function normalizeNanoBananaTaskResult(taskData, body, log) {
if (urlCandidates.length > 0) {
const firstUrl = urlCandidates[0];
// GHSA-34rg-3pqj-35g9: upstream-supplied result URL, not an OmniRoute-controlled
// host — pin `public-only` exactly like the AI Horde result download does, never
// the operator outbound policy (see `resolveImageSource` for why `pinDns` is off).
const remoteImage = await fetchRemoteImage(firstUrl, { guard: "public-only" });
// GHSA-34rg-3pqj-35g9 / #13883: upstream-supplied result URL, not an OmniRoute-
// controlled host — pin `public-only`, never the operator outbound policy, and
// `pinDns: true` to close the DNS-rebinding TOCTOU (see `resolveImageSource`).
const remoteImage = await fetchRemoteImage(firstUrl, { guard: "public-only", pinDns: true });
const base64 = remoteImage.buffer.toString("base64");
return [{ b64_json: base64, revised_prompt: body.prompt }];
}

View File

@@ -163,15 +163,15 @@ export async function resolveUpscaleImageSource(source: string): Promise<Upscale
}
if (/^https?:\/\//i.test(trimmed)) {
// GHSA-34rg-3pqj-35g9: `source` is caller input (14 body aliases, `provider_options.*`,
// message parts) — pin `public-only` explicitly (string check + DNS validation of
// every resolved answer). Never let it fall back to the operator outbound policy
// (`block-metadata` on a local-first default install), which would let a request
// body make the server fetch loopback/LAN URLs and upload the bytes to the upscale
// provider. `pinDns` stays off on purpose: the download transport here is
// `globalThis.fetch` and connection pinning replaces it with a raw undici fetch —
// same shape as the AI Horde result download in `imageGeneration/providers/aihorde.ts`.
const remote = await fetchRemoteImage(trimmed, { guard: "public-only" });
// GHSA-34rg-3pqj-35g9 / #13883: `source` is caller input (14 body aliases,
// `provider_options.*`, message parts) — pin `public-only` explicitly (string check +
// DNS validation of every resolved answer). Never let it fall back to the operator
// outbound policy (`block-metadata` on a local-first default install), which would let
// a request body make the server fetch loopback/LAN URLs and upload the bytes to the
// upscale provider. `pinDns: true` closes the DNS-rebinding TOCTOU: without it, a
// second, un-pinned resolution at connect time could answer differently than the
// validated lookup and bypass the public-only guard.
const remote = await fetchRemoteImage(trimmed, { guard: "public-only", pinDns: true });
assertSourceBytes(remote.buffer);
// fetchRemoteImage falls back to application/octet-stream; sniff whenever the
// server did not send a usable image/* type so multipart uploads stay correct.

View File

@@ -146,6 +146,16 @@ async function readResponseBuffer(response: Response, maxBytes: number) {
return Buffer.concat(chunks, totalBytes);
}
// #13883: test-only escape hatch for `pinDns: true` callers that have no `fetchImpl` seam
// of their own (imageGeneration.ts / imageUpscale/shared.ts). `createPinnedFetch` opens a
// real undici connection, bypassing a test's monkeypatched `globalThis.fetch`; setting this
// override lets such a test keep exercising its mock instead of a real network attempt.
// Production callers never call the setter, so `pinDns` still pins for real in production.
let pinnedFetchTestOverride: typeof fetch | undefined;
export function setPinnedFetchTestOverride(fetchImpl: typeof fetch | undefined): void {
pinnedFetchTestOverride = fetchImpl;
}
export async function fetchRemoteMedia(
input: string | URL,
options: RemoteMediaFetchOptions = {}
@@ -171,6 +181,7 @@ export async function fetchRemoteMedia(
const addresses = await assertHostnameResolvesPublic(currentUrl, guard, lookup);
const fetchImpl =
injectedFetch ??
pinnedFetchTestOverride ??
(pinDns && addresses.length
? createPinnedFetch(addresses[0].address, addresses[0].family)
: fetch);

View File

@@ -20,10 +20,11 @@ process.on("exit", () => {
});
const { handleImageGeneration } = await import("../../open-sse/handlers/imageGeneration.ts");
const { setPinnedFetchTestOverride } = await import("../../src/shared/network/remoteImageFetch.ts");
test("handleImageGeneration returns Fal images as base64 when response_format is omitted", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url) => {
const mockFetchImpl = async (url) => {
const stringUrl = String(url);
if (stringUrl === "https://fal.run/fal-ai/flux-2-flex") {
return new Response(
@@ -39,6 +40,11 @@ test("handleImageGeneration returns Fal images as base64 when response_format is
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
// #13883: resolveImageSource now sets `pinDns: true`, which pins the connection via a
// real undici socket and would bypass this mocked globalThis.fetch — route it through
// the test-only pinned-fetch override instead (src/shared/network/remoteImageFetch.ts).
globalThis.fetch = mockFetchImpl;
setPinnedFetchTestOverride(mockFetchImpl);
try {
const result = await handleImageGeneration({
@@ -51,5 +57,6 @@ test("handleImageGeneration returns Fal images as base64 when response_format is
assert.equal(result.data.data[0].url, undefined);
} finally {
globalThis.fetch = originalFetch;
setPinnedFetchTestOverride(undefined);
}
});

View File

@@ -7,16 +7,9 @@ import { join } from "node:path";
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-images-"));
// Stub DNS for fetchRemoteImage's GHSA-cmhj-wh2f-9cgx DNS-rebinding guard
// (assertHostnameResolvesPublic in src/shared/network/remoteImageFetch.ts).
// Several image-handler tests (Fal AI URL->b64 normalization, BFL polling
// with base64 input images, NanoBanana polling with URL->b64 conversion)
// mock globalThis.fetch with example.com URLs that don't resolve in CI; the
// handler invokes fetchRemoteImage without exposing a `lookup` injection
// point, so we monkey-patch dns.promises.lookup to always return a public IP
// so the rebinding guard passes and the test exercises the mocked fetch
// behaviour as intended. Node --test runs each file in its own process, so
// this rebinding does not leak across files.
// Stub DNS for fetchRemoteImage's GHSA-cmhj-wh2f-9cgx guard so mocked example.com URLs
// resolve as public. #13883's `pinDns: true` pins the connection via undici, bypassing a
// mocked globalThis.fetch — `mockFetch()` also sets the `setPinnedFetchTestOverride()` seam.
const originalDnsLookup = dns.promises.lookup;
(dns.promises as { lookup: unknown }).lookup = (async (
_hostname: string,
@@ -32,6 +25,11 @@ process.on("exit", () => {
const { IMAGE_PROVIDERS, parseImageModel, getAllImageModels } =
await import("../../open-sse/config/imageRegistry.ts");
const { handleImageGeneration } = await import("../../open-sse/handlers/imageGeneration.ts");
const { setPinnedFetchTestOverride } = await import("../../src/shared/network/remoteImageFetch.ts");
function mockFetch(impl) {
globalThis.fetch = impl;
setPinnedFetchTestOverride(impl);
}
function immediateTimeout(callback, _ms, ...args) {
if (typeof callback === "function") callback(...args);
@@ -366,7 +364,7 @@ test("handleImageGeneration calls Fal AI with Key auth and normalizes URL result
const originalFetch = globalThis.fetch;
let requestCapture;
globalThis.fetch = async (url, options = {}) => {
mockFetch(async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "https://fal.run/fal-ai/flux-pro/v1.1-ultra") {
requestCapture = {
@@ -391,7 +389,7 @@ test("handleImageGeneration calls Fal AI with Key auth and normalizes URL result
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
});
try {
const result = await handleImageGeneration({
@@ -416,7 +414,7 @@ test("handleImageGeneration calls Fal AI with Key auth and normalizes URL result
assert.equal(requestCapture.body.sync_mode, true);
assert.equal(result.data.data[0].b64_json, "BQYH");
} finally {
globalThis.fetch = originalFetch;
mockFetch(originalFetch);
}
});
@@ -424,7 +422,7 @@ test("handleImageGeneration routes Stability AI edit models to native endpoints"
const originalFetch = globalThis.fetch;
let requestCapture;
globalThis.fetch = async (url, options = {}) => {
mockFetch(async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "https://example.com/stability-input.png") {
return new Response(new Uint8Array([4, 5]), {
@@ -447,7 +445,7 @@ test("handleImageGeneration routes Stability AI edit models to native endpoints"
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
});
try {
const result = await handleImageGeneration({
@@ -476,7 +474,7 @@ test("handleImageGeneration routes Stability AI edit models to native endpoints"
assert.equal((requestCapture.body.get("mask") as Blob).size, 1);
assert.equal(result.data.data[0].b64_json, "c3RhYmlsaXR5LWltYWdl");
} finally {
globalThis.fetch = originalFetch;
mockFetch(originalFetch);
}
});
@@ -537,7 +535,7 @@ test("handleImageGeneration polls Black Forest Labs results and sends base64 inp
let pollCapture;
globalThis.setTimeout = immediateTimeout;
globalThis.fetch = async (url, options = {}) => {
mockFetch(async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "https://example.com/bfl-input.png") {
return new Response(new Uint8Array([1, 2]), {
@@ -582,7 +580,7 @@ test("handleImageGeneration polls Black Forest Labs results and sends base64 inp
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
});
try {
const result = await handleImageGeneration({
@@ -605,7 +603,7 @@ test("handleImageGeneration polls Black Forest Labs results and sends base64 inp
assert.equal(pollCapture.headers["x-key"], "bfl-key");
assert.equal(result.data.data[0].b64_json, "CQgH");
} finally {
globalThis.fetch = originalFetch;
mockFetch(originalFetch);
globalThis.setTimeout = originalSetTimeout;
}
});
@@ -660,7 +658,7 @@ test("handleImageGeneration uploads source images to Topaz and returns base64 ou
const originalFetch = globalThis.fetch;
let requestCapture;
globalThis.fetch = async (url, options = {}) => {
mockFetch(async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "https://example.com/topaz-input.png") {
return new Response(new Uint8Array([1, 2, 3]), {
@@ -686,7 +684,7 @@ test("handleImageGeneration uploads source images to Topaz and returns base64 ou
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
});
try {
const result = await handleImageGeneration({
@@ -709,7 +707,7 @@ test("handleImageGeneration uploads source images to Topaz and returns base64 ou
assert.ok(requestCapture.image instanceof File);
assert.equal(result.data.data[0].b64_json, "BwcH");
} finally {
globalThis.fetch = originalFetch;
mockFetch(originalFetch);
}
});
@@ -1050,7 +1048,7 @@ test("handleImageGeneration polls NanoBanana task results and converts URLs to b
const originalFetch = globalThis.fetch;
const calls = [];
globalThis.fetch = async (url, options = {}) => {
mockFetch(async (url, options = {}) => {
const stringUrl = String(url);
calls.push(stringUrl);
@@ -1078,7 +1076,7 @@ test("handleImageGeneration polls NanoBanana task results and converts URLs to b
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
});
try {
const result = await handleImageGeneration({
@@ -1099,7 +1097,7 @@ test("handleImageGeneration polls NanoBanana task results and converts URLs to b
]);
assert.deepEqual(result.data.data, [{ b64_json: "AQIDBA==", revised_prompt: "banana async" }]);
} finally {
globalThis.fetch = originalFetch;
mockFetch(originalFetch);
}
});
@@ -2193,7 +2191,7 @@ test("handleImageGeneration still downloads a public image_url whose DNS resolve
const fetchedUrls = [];
let requestCapture;
globalThis.fetch = async (url, options = {}) => {
mockFetch(async (url, options = {}) => {
const stringUrl = String(url);
fetchedUrls.push(stringUrl);
if (stringUrl === "https://cdn.example.com/public-input.png") {
@@ -2210,7 +2208,7 @@ test("handleImageGeneration still downloads a public image_url whose DNS resolve
});
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
});
try {
const result = await handleImageGeneration({
@@ -2229,6 +2227,6 @@ test("handleImageGeneration still downloads a public image_url whose DNS resolve
assert.equal(fetchedUrls[0], "https://cdn.example.com/public-input.png");
assert.equal((requestCapture.body.get("image") as Blob).size, 3);
} finally {
globalThis.fetch = originalFetch;
mockFetch(originalFetch);
}
});

View File

@@ -19,6 +19,7 @@ const providerChatRoute =
await import("../../src/app/api/v1/providers/[provider]/chat/completions/route.ts");
const imageEditRoute = await import("../../src/app/api/v1/images/edits/route.ts");
const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts");
const { setPinnedFetchTestOverride } = await import("../../src/shared/network/remoteImageFetch.ts");
const originalFetch = globalThis.fetch;
@@ -72,6 +73,7 @@ function createCodexEditForm(
async function resetStorage() {
globalThis.fetch = originalFetch;
setPinnedFetchTestOverride(undefined);
apiKeysDb.resetApiKeyState();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
@@ -120,6 +122,7 @@ test.beforeEach(async () => {
test.after(() => {
globalThis.fetch = originalFetch;
setPinnedFetchTestOverride(undefined);
apiKeysDb.resetApiKeyState();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
@@ -234,7 +237,7 @@ test("v1 image models GET exposes current Codex image models and hides inactive
test("v1 image generation POST accepts promptless requests for image-only models", async () => {
await seedConnection("topaz", { apiKey: "topaz-key" });
globalThis.fetch = async (url, options: RequestInit = {}) => {
const mockFetchImpl = async (url, options: RequestInit = {}) => {
const stringUrl = String(url);
if (stringUrl === "https://example.com/topaz-input.png") {
return new Response(new Uint8Array([1, 2, 3]), {
@@ -254,6 +257,11 @@ test("v1 image generation POST accepts promptless requests for image-only models
throw new Error(`Unexpected URL: ${stringUrl}`);
};
// #13883: resolveImageSource now sets `pinDns: true`, which pins the connection via a
// real undici socket and would bypass this mocked globalThis.fetch — route it through
// the test-only pinned-fetch override instead (src/shared/network/remoteImageFetch.ts).
globalThis.fetch = mockFetchImpl;
setPinnedFetchTestOverride(mockFetchImpl);
const response = await imageRoute.POST(
new Request("http://localhost/api/v1/images/generations", {

View File

@@ -34,6 +34,7 @@ import { handleImageUpscale } from "../../open-sse/handlers/imageUpscale.ts";
import { handleStabilityImageUpscale } from "../../open-sse/handlers/imageUpscale/stability.ts";
import { handleTopazImageUpscale } from "../../open-sse/handlers/imageUpscale/topaz.ts";
import { IMAGE_PROVIDERS } from "../../open-sse/config/imageRegistry.ts";
import { setPinnedFetchTestOverride } from "../../src/shared/network/remoteImageFetch.ts";
// ── Fixtures ───────────────────────────────────────────────────────────────
@@ -758,10 +759,15 @@ for (const privateUrl of ["http://127.0.0.1:1/x.png", "http://192.168.1.50/x.png
test("resolveUpscaleImageSource still downloads a public URL whose DNS resolves to a public IP (GHSA-34rg-3pqj-35g9)", async () => {
const originalFetch = globalThis.fetch;
const fetchedUrls: string[] = [];
globalThis.fetch = (async (url: string | URL | Request) => {
const mockFetchImpl = (async (url: string | URL | Request) => {
fetchedUrls.push(String(url));
return new Response(bytes(PNG_1X1), { status: 200, headers: { "content-type": "image/png" } });
}) as unknown as typeof fetch;
// #13883: resolveUpscaleImageSource now sets `pinDns: true`, which pins the connection
// via a real undici socket and would bypass this mocked globalThis.fetch — route it
// through the test-only pinned-fetch override instead (src/shared/network/remoteImageFetch.ts).
globalThis.fetch = mockFetchImpl;
setPinnedFetchTestOverride(mockFetchImpl);
try {
const source = await withPublicDns(() =>
@@ -772,5 +778,6 @@ test("resolveUpscaleImageSource still downloads a public URL whose DNS resolves
assert.deepEqual(fetchedUrls, ["https://cdn.example.com/public.png"]);
} finally {
globalThis.fetch = originalFetch;
setPinnedFetchTestOverride(undefined);
}
});

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import dns from "node:dns";
import { handleImageGeneration } from "../../open-sse/handlers/imageGeneration.ts";
import { setPinnedFetchTestOverride } from "../../src/shared/network/remoteImageFetch.ts";
// Stub DNS for fetchRemoteImage's GHSA-cmhj-wh2f-9cgx DNS-rebinding guard
// (assertHostnameResolvesPublic in src/shared/network/remoteImageFetch.ts).
@@ -93,7 +94,7 @@ test("handleImageGeneration(nanobanana): async submit+poll returns URL payload",
test("handleImageGeneration(nanobanana): response_format=b64_json converts URL to b64", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url) => {
const mockFetchImpl = async (url) => {
const u = String(url);
if (u.includes("/generate")) {
@@ -123,6 +124,12 @@ test("handleImageGeneration(nanobanana): response_format=b64_json converts URL t
throw new Error(`Unexpected URL: ${u}`);
};
// #13883: resolveImageSource (used for the URL result → base64 conversion) now sets
// `pinDns: true`, which pins the connection via a real undici socket and would bypass
// this mocked globalThis.fetch — route it through the test-only pinned-fetch override
// instead (src/shared/network/remoteImageFetch.ts).
globalThis.fetch = mockFetchImpl;
setPinnedFetchTestOverride(mockFetchImpl);
try {
const result = await handleImageGeneration({
@@ -140,6 +147,7 @@ test("handleImageGeneration(nanobanana): response_format=b64_json converts URL t
assert.equal(result.data.data[0].b64_json, "iVBORw==");
} finally {
globalThis.fetch = originalFetch;
setPinnedFetchTestOverride(undefined);
}
});

View File

@@ -0,0 +1,81 @@
import test from "node:test";
import assert from "node:assert/strict";
import dns from "node:dns";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-pindns-13883-"));
// #13883 — security: pinDns off at the three new public-only image fetch sites let a
// DNS-rebinding hostname (public at validation time, private at real connect time) bypass
// the `guard: "public-only"` check, since an un-pinned fetch performs its own, independent
// DNS resolution. `resolveImageSource` / `normalizeNanoBananaTaskResult` (imageGeneration.ts)
// and `resolveUpscaleImageSource` (imageUpscale/shared.ts) now all set `pinDns: true`, which
// closes the gap by binding the connection to the single validated DNS answer instead of
// letting the transport re-resolve it — see `src/shared/network/dnsPinnedFetch.ts`.
//
// None of these three call sites expose a `lookup` injection point (they always use the
// real resolver), so this regression guard resolves a fake hostname to a public-looking,
// deliberately unreachable TEST-NET-3 address (RFC 5737 — never routed on the public
// internet) and asserts each site's request goes out through the real pinned undici socket
// (and therefore fails closed against that unreachable address) rather than through a
// mocked `globalThis.fetch`. If a future change dropped `pinDns: true` at any of these
// sites, the un-pinned `fetch()` call would hit the mock below instead — turning this red.
function withPublicDns<T>(run: () => Promise<T>): Promise<T> {
const original = dns.promises.lookup;
(dns.promises as { lookup: unknown }).lookup = (async (
_hostname: string,
options?: { all?: boolean }
) => {
const record = { address: "203.0.113.7", family: 4 }; // RFC 5737 TEST-NET-3: unreachable
return options && options.all ? [record] : record;
}) as typeof dns.promises.lookup;
return run().finally(() => {
(dns.promises as { lookup: unknown }).lookup = original;
});
}
const { resolveImageSource, normalizeNanoBananaTaskResult } =
await import("../../open-sse/handlers/imageGeneration.ts");
const { resolveUpscaleImageSource } =
await import("../../open-sse/handlers/imageUpscale/shared.ts");
/** Runs `attempt` with a mocked `globalThis.fetch` that must never be reached when
* `pinDns: true` is wired correctly, and confirms the call still fails closed (the pinned
* connection targets an unreachable address instead of falling back to the mock). */
async function assertPinnedNotMocked(attempt: () => Promise<unknown>): Promise<void> {
let mockCalled = false;
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () => {
mockCalled = true;
throw new Error("globalThis.fetch must not be reached when pinDns is active");
}) as typeof fetch;
try {
await assert.rejects(() => withPublicDns(attempt));
assert.equal(mockCalled, false, "pinDns must bypass globalThis.fetch, not call it");
} finally {
globalThis.fetch = originalFetch;
}
}
test("resolveImageSource (imageGeneration.ts) fetches through the real pinned socket, not a mocked fetch (#13883)", async () => {
await assertPinnedNotMocked(() => resolveImageSource("https://rebind-13883.example.com/x.png"));
});
test("normalizeNanoBananaTaskResult result-URL download fetches through the real pinned socket, not a mocked fetch (#13883)", async () => {
const taskData = {
response: { resultImageUrl: "https://rebind-13883.example.com/result.png" },
};
await assertPinnedNotMocked(() =>
normalizeNanoBananaTaskResult(taskData, { response_format: "b64_json" }, null)
);
});
test("resolveUpscaleImageSource (imageUpscale/shared.ts) fetches through the real pinned socket, not a mocked fetch (#13883)", async () => {
await assertPinnedNotMocked(() =>
resolveUpscaleImageSource("https://rebind-13883.example.com/source.png")
);
});