Skip to content

Commit f0aeb15

Browse files
shushushvclaude
andcommitted
fix(openai): allow RFC 2544 fake-IP range for Realtime session requests
Proxy stacks such as Clash, Surge, and sing-box in TUN/fake-IP mode resolve public domains (api.openai.com) to 198.18.0.0/15 (IPv4) or fc00::/7 (IPv6), which the SSRF guard blocks as special-use ranges. The Realtime session-secret fetch path was missing the opt-in that already exists for other provider HTTP calls. Add OPENAI_REALTIME_SSRF_POLICY with allowRfc2544BenchmarkRange, allowIpv6UniqueLocalRange, and hostnameAllowlist scoped to api.openai.com. Update voice and transcription request assertions to cover all three policy fields. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent 65805e5 commit f0aeb15

5 files changed

Lines changed: 32 additions & 3 deletions

File tree

extensions/openai/realtime-provider-shared.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ import {
55
resolveProviderRequestHeaders,
66
} from "openclaw/plugin-sdk/provider-http";
77
import { captureWsEvent } from "openclaw/plugin-sdk/proxy-capture";
8-
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
8+
import {
9+
fetchWithSsrFGuard,
10+
ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist,
11+
} from "openclaw/plugin-sdk/ssrf-runtime";
912
import {
1013
asFiniteNumber,
1114
asOptionalRecord as asObjectRecord,
1215
normalizeOptionalString,
1316
} from "openclaw/plugin-sdk/string-coerce-runtime";
1417

18+
const OPENAI_REALTIME_SSRF_POLICY = ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist(
19+
"https://api.openai.com/v1",
20+
);
21+
1522
export const trimToUndefined = normalizeOptionalString;
1623
export { asFiniteNumber, asObjectRecord };
1724

@@ -103,6 +110,7 @@ async function createOpenAIRealtimeSecret(
103110
},
104111
body: JSON.stringify(params.body),
105112
},
113+
policy: OPENAI_REALTIME_SSRF_POLICY,
106114
auditContext: params.auditContext,
107115
});
108116
const payload = await (async () => {

extensions/openai/realtime-transcription-provider.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ vi.mock("openclaw/plugin-sdk/provider-auth", () => ({
7070

7171
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
7272
fetchWithSsrFGuard: ssrfMocks.fetchWithSsrFGuard,
73+
ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist: () => ({
74+
allowRfc2544BenchmarkRange: true,
75+
allowIpv6UniqueLocalRange: true,
76+
hostnameAllowlist: ["api.openai.com"],
77+
}),
7378
}));
7479

7580
type FakeWebSocketInstance = InstanceType<typeof FakeWebSocket>;
@@ -240,6 +245,11 @@ describe("buildOpenAIRealtimeTranscriptionProvider", () => {
240245
const request = mockCallArg(ssrfMocks.fetchWithSsrFGuard);
241246
expect(request.auditContext).toBe("openai-realtime-transcription-session");
242247
expect(request.url).toBe("https://api.openai.com/v1/realtime/transcription_sessions");
248+
expect(request.policy).toEqual({
249+
allowRfc2544BenchmarkRange: true,
250+
allowIpv6UniqueLocalRange: true,
251+
hostnameAllowlist: ["api.openai.com"],
252+
});
243253
const init = request.init as {
244254
method?: string;
245255
headers?: Record<string, string>;

extensions/openai/realtime-voice-provider.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ vi.mock("ws", () => ({
8181

8282
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
8383
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
84+
ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist: () => ({
85+
allowRfc2544BenchmarkRange: true,
86+
allowIpv6UniqueLocalRange: true,
87+
hostnameAllowlist: ["api.openai.com"],
88+
}),
8489
}));
8590

8691
vi.mock("openclaw/plugin-sdk/provider-auth", () => ({
@@ -405,6 +410,11 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
405410

406411
expectRecordFields(requireFetchRequest(), "fetch request", {
407412
url: "https://api.openai.com/v1/realtime/client_secrets",
413+
policy: {
414+
allowRfc2544BenchmarkRange: true,
415+
allowIpv6UniqueLocalRange: true,
416+
hostnameAllowlist: ["api.openai.com"],
417+
},
408418
});
409419
expectRecordFields(requireFetchInit(), "fetch init", { method: "POST" });
410420
expectRecordFields(requireFetchHeaders(), "fetch headers", {

scripts/plugin-sdk-surface-report.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ let publicDeprecatedExportsByEntrypointBudget;
161161
try {
162162
budgets = {
163163
publicEntrypoints: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_ENTRYPOINTS", 319),
164-
publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10271),
165-
publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5161),
164+
publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10272),
165+
publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5162),
166166
publicDeprecatedExports: readBudgetEnv(
167167
"OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS",
168168
3230,

src/plugin-sdk/ssrf-runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export {
1212
resolveSsrFPolicyForUrl,
1313
ssrfPolicyFromHttpBaseUrlAllowedHostname,
1414
ssrfPolicyFromHttpBaseUrlAllowedOrigin,
15+
ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist,
1516
type LookupFn,
1617
type SsrFPolicy,
1718
} from "../infra/net/ssrf.js";

0 commit comments

Comments
 (0)