From 716a3a58657fda0f1ec93a910cb2cf4afebc5009 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 24 Apr 2026 01:04:06 +0100 Subject: [PATCH] fix: honor Google image private-network opt-in --- CHANGELOG.md | 1 + extensions/google/api.test.ts | 7 ++--- extensions/google/api.ts | 8 +++-- .../google/image-generation-provider.test.ts | 30 +++++++++++++++++++ .../google/image-generation-provider.ts | 9 +++++- 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fba67de5fe7..d5ad45bc44f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Providers/Google: honor the private-network SSRF opt-in for Gemini image generation requests, so trusted proxy setups that resolve Google API hosts to private addresses can use `image_generate`. Fixes #67216. - Agents/transport: stop embedded runs from lowering the process-wide undici stream timeouts, so slow Gemini image generation and other long-running provider requests no longer inherit short run-attempt headers timeouts. Fixes #70423. Thanks @giangthb. - Providers/OpenRouter: send image-understanding prompts as user text before image parts, restoring non-empty vision responses for OpenRouter multimodal models. Fixes #70410. - Memory/QMD: recreate stale managed QMD collections when startup repair finds the collection name already exists, so root memory narrows back to `MEMORY.md` instead of staying on broad workspace markdown indexing. diff --git a/extensions/google/api.test.ts b/extensions/google/api.test.ts index a53a88f58f6..66279961938 100644 --- a/extensions/google/api.test.ts +++ b/extensions/google/api.test.ts @@ -1,4 +1,3 @@ -import type { ProviderRequestTransportOverrides } from "openclaw/plugin-sdk/provider-http"; import { describe, expect, it } from "vitest"; import { isGoogleGenerativeAiApi, @@ -217,7 +216,7 @@ describe("google generative ai helpers", () => { expect(normalized).toBe("https://generativelanguage.googleapis.com/v1beta/openai"); }); - it("rejects non-Google Gemini base URLs and ignores smuggled private-network flags", () => { + it("rejects non-Google Gemini base URLs and honors explicit private-network opt-in", () => { expect(() => resolveGoogleGenerativeAiHttpRequestConfig({ apiKey: "api-key-123", @@ -241,8 +240,8 @@ describe("google generative ai helpers", () => { baseUrl: "https://generativelanguage.googleapis.com/v1beta", capability: "image", transport: "http", - request: { allowPrivateNetwork: true } as unknown as ProviderRequestTransportOverrides, + request: { allowPrivateNetwork: true }, }); - expect(config.allowPrivateNetwork).toBe(false); + expect(config.allowPrivateNetwork).toBe(true); }); }); diff --git a/extensions/google/api.ts b/extensions/google/api.ts index 896d409edf1..b49526fd473 100644 --- a/extensions/google/api.ts +++ b/extensions/google/api.ts @@ -42,6 +42,10 @@ export { export { buildGoogleGeminiCliProvider } from "./gemini-cli-provider.js"; export { buildGoogleProvider } from "./provider-registration.js"; +type GoogleGenerativeAiRequestOverrides = ProviderRequestTransportOverrides & { + allowPrivateNetwork?: boolean; +}; + function resolveTrustedGoogleGenerativeAiBaseUrl(baseUrl?: string): string { const normalized = normalizeGoogleGenerativeAiBaseUrl(baseUrl ?? DEFAULT_GOOGLE_API_BASE_URL) ?? @@ -69,14 +73,14 @@ export function resolveGoogleGenerativeAiHttpRequestConfig(params: { apiKey: string; baseUrl?: string; headers?: Record; - request?: ProviderRequestTransportOverrides; + request?: GoogleGenerativeAiRequestOverrides; capability: "image" | "audio" | "video"; transport: "http" | "media-understanding"; }) { return resolveProviderHttpRequestConfig({ baseUrl: resolveTrustedGoogleGenerativeAiBaseUrl(params.baseUrl), defaultBaseUrl: DEFAULT_GOOGLE_API_BASE_URL, - allowPrivateNetwork: false, + allowPrivateNetwork: params.request?.allowPrivateNetwork, headers: params.headers, request: params.request, defaultHeaders: parseGeminiAuth(params.apiKey).headers, diff --git a/extensions/google/image-generation-provider.test.ts b/extensions/google/image-generation-provider.test.ts index 991c30f4460..7add6f21c66 100644 --- a/extensions/google/image-generation-provider.test.ts +++ b/extensions/google/image-generation-provider.test.ts @@ -278,6 +278,36 @@ describe("Google image-generation provider", () => { ); }); + it("honors configured private-network opt-in for Google image generation", async () => { + mockGoogleApiKeyAuth(); + installGoogleFetchMock(); + const postJsonRequestSpy = vi.spyOn(providerHttp, "postJsonRequest"); + + const provider = buildGoogleImageGenerationProvider(); + await provider.generateImage({ + provider: "google", + model: "gemini-3.1-flash-image-preview", + prompt: "draw a fox", + cfg: { + models: { + providers: { + google: { + baseUrl: "https://generativelanguage.googleapis.com/v1beta", + request: { allowPrivateNetwork: true }, + models: [], + }, + }, + }, + }, + }); + + expect(postJsonRequestSpy).toHaveBeenCalledWith( + expect.objectContaining({ + allowPrivateNetwork: true, + }), + ); + }); + it("normalizes a configured bare Google host to the v1beta API root", async () => { mockGoogleApiKeyAuth(); const fetchMock = installGoogleFetchMock(); diff --git a/extensions/google/image-generation-provider.ts b/extensions/google/image-generation-provider.ts index 4925727f892..15076025f76 100644 --- a/extensions/google/image-generation-provider.ts +++ b/extensions/google/image-generation-provider.ts @@ -1,7 +1,11 @@ import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generation"; import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth"; import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; -import { assertOkOrThrowHttpError, postJsonRequest } from "openclaw/plugin-sdk/provider-http"; +import { + assertOkOrThrowHttpError, + postJsonRequest, + sanitizeConfiguredModelProviderRequest, +} from "openclaw/plugin-sdk/provider-http"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { normalizeGoogleModelId, resolveGoogleGenerativeAiHttpRequestConfig } from "./api.js"; @@ -132,6 +136,9 @@ export function buildGoogleImageGenerationProvider(): ImageGenerationProvider { resolveGoogleGenerativeAiHttpRequestConfig({ apiKey: auth.apiKey, baseUrl: req.cfg?.models?.providers?.google?.baseUrl, + request: sanitizeConfiguredModelProviderRequest( + req.cfg?.models?.providers?.google?.request, + ), capability: "image", transport: "http", });