fix: allow private OpenAI image endpoints

This commit is contained in:
Peter Steinberger
2026-04-24 00:35:47 +01:00
parent 5be5233250
commit d16b879334
5 changed files with 54 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
- Agents/OpenAI: surface selected-model capacity failures from PI, Codex, and auto-reply harness paths with a model-switch hint instead of the generic empty-response error. Thanks @vincentkoc.
- Providers/OpenAI: route `openai/gpt-image-2` through configured Codex OAuth directly when an `openai-codex` profile is active, instead of probing `OPENAI_API_KEY` first.
- Providers/OpenAI: harden image generation auth routing and Codex OAuth response parsing so fallback only applies to public OpenAI API routes and bounded SSE results. Thanks @Takhoffman.
- Providers/OpenAI: honor the private-network SSRF opt-in for OpenAI-compatible image generation endpoints, so trusted LocalAI/LAN `image_generate` routes work without disabling SSRF checks globally. Fixes #62879. Thanks @seitzbg.
- Providers/OpenAI: stop advertising the removed `gpt-5.3-codex-spark` Codex model through fallback catalogs, and suppress stale rows with a GPT-5.5 recovery hint.
- Plugins/QR: replace legacy `qrcode-terminal` QR rendering with bounded `qrcode-tui` helpers for plugin login/setup flows. (#65969) Thanks @vincentkoc.
- Voice-call/realtime: wait for OpenAI session configuration before greeting or forwarding buffered audio, and reject non-allowlisted Twilio callers before stream setup. (#43501) Thanks @forrestblount.

View File

@@ -236,6 +236,10 @@ does not first try `OPENAI_API_KEY` or silently fall back to an API key for that
request. Configure `models.providers.openai` explicitly with an API key,
custom base URL, or Azure endpoint when you want the direct OpenAI Images API
route instead.
If that custom image endpoint is on a trusted LAN/private address, also set
`browser.ssrfPolicy.dangerouslyAllowPrivateNetwork: true`; OpenClaw keeps
private/internal OpenAI-compatible image endpoints blocked unless this opt-in is
present.
Generate:

View File

@@ -35,6 +35,10 @@ Codex OAuth uses the same `openai/gpt-image-2` model ref. When an
through that same OAuth profile instead of first trying `OPENAI_API_KEY`.
Explicit custom `models.providers.openai` image config, such as an API key or
custom/Azure base URL, opts back into the direct OpenAI Images API route.
For OpenAI-compatible LAN endpoints such as LocalAI, keep the custom
`models.providers.openai.baseUrl` and explicitly opt in with
`browser.ssrfPolicy.dangerouslyAllowPrivateNetwork: true`; private/internal
image endpoints remain blocked by default.
3. Ask the agent: _"Generate an image of a friendly robot mascot."_

View File

@@ -316,6 +316,47 @@ describe("openai image generation provider", () => {
expect(result.images).toHaveLength(1);
});
it("allows OpenAI-compatible private image endpoints when browser SSRF policy opts in", async () => {
mockGeneratedPngResponse();
const provider = buildOpenAIImageGenerationProvider();
const result = await provider.generateImage({
provider: "openai",
model: "flux2-klein",
prompt: "A simple, clean illustration of a red apple with a green leaf",
cfg: {
browser: {
ssrfPolicy: {
dangerouslyAllowPrivateNetwork: true,
},
},
models: {
providers: {
openai: {
baseUrl: "http://192.168.1.15:8082/v1",
apiKey: "local-noauth",
models: [],
},
},
},
},
});
expect(resolveProviderHttpRequestConfigMock).toHaveBeenCalledWith(
expect.objectContaining({
baseUrl: "http://192.168.1.15:8082/v1",
allowPrivateNetwork: true,
}),
);
expect(postJsonRequestMock).toHaveBeenCalledWith(
expect.objectContaining({
url: "http://192.168.1.15:8082/v1/images/generations",
allowPrivateNetwork: true,
}),
);
expect(result.images).toHaveLength(1);
});
it("forwards generation count and custom size overrides", async () => {
mockGeneratedPngResponse();

View File

@@ -21,6 +21,7 @@ import {
resolveProviderHttpRequestConfig,
sanitizeConfiguredModelProviderRequest,
} from "openclaw/plugin-sdk/provider-http";
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
import { OPENAI_DEFAULT_IMAGE_MODEL as DEFAULT_OPENAI_IMAGE_MODEL } from "./default-models.js";
import { resolveConfiguredOpenAIBaseUrl } from "./shared.js";
@@ -190,6 +191,9 @@ function shouldAllowPrivateImageEndpoint(req: {
if (req.provider === MOCK_OPENAI_PROVIDER_ID) {
return true;
}
if (isPrivateNetworkOptInEnabled(req.cfg?.browser?.ssrfPolicy)) {
return true;
}
const baseUrl = resolveConfiguredOpenAIBaseUrl(req.cfg);
if (!baseUrl.startsWith("http://127.0.0.1:") && !baseUrl.startsWith("http://localhost:")) {
return false;