Skip to content

Commit 6740ab2

Browse files
committed
fix: harden memory env proxy guard (#71506) (thanks @DhtIsCoding)
1 parent 56ef7fd commit 6740ab2

11 files changed

Lines changed: 292 additions & 57 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
1919

2020
### Fixes
2121

22+
- Memory-host SDK: use trusted env-proxy mode for remote embedding and batch HTTP calls only when Undici will proxy that target, preserving SSRF DNS pinning for `ALL_PROXY`-only and `NO_PROXY` bypass cases. Fixes #52162. (#71506) Thanks @DhtIsCoding.
2223
- Gateway/dashboard: render Control UI and WebSocket links with `https://`/`wss://` when `gateway.tls.enabled=true`, including `openclaw gateway status`. Fixes #71494. (#71499) Thanks @deepkilo.
2324
- Agents/OpenAI-compatible: default proxy/local completions tool requests to `tool_choice: "auto"` when tools are present, so providers enter native tool-calling mode instead of replying with plain-text tool directives. (#71472) Thanks @Speed-maker.
2425
- OpenAI image generation: use `gpt-5.5` for the Codex OAuth responses transport instead of the retired `gpt-5.4` model, fixing 500s from ChatGPT Codex image generation. Fixes #71513. Thanks @baolongl.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
3+
const { fetchWithSsrFGuardMock, shouldUseEnvHttpProxyForUrlMock } = vi.hoisted(() => ({
4+
fetchWithSsrFGuardMock: vi.fn(),
5+
shouldUseEnvHttpProxyForUrlMock: vi.fn(() => false),
6+
}));
7+
8+
vi.mock("../../../../src/infra/net/fetch-guard.js", async () => {
9+
const actual = await vi.importActual<typeof import("../../../../src/infra/net/fetch-guard.js")>(
10+
"../../../../src/infra/net/fetch-guard.js",
11+
);
12+
return {
13+
...actual,
14+
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
15+
};
16+
});
17+
18+
vi.mock("../../../../src/infra/net/proxy-env.js", async () => {
19+
const actual = await vi.importActual<typeof import("../../../../src/infra/net/proxy-env.js")>(
20+
"../../../../src/infra/net/proxy-env.js",
21+
);
22+
return {
23+
...actual,
24+
shouldUseEnvHttpProxyForUrl: shouldUseEnvHttpProxyForUrlMock,
25+
};
26+
});
27+
28+
import { GUARDED_FETCH_MODE } from "../../../../src/infra/net/fetch-guard.js";
29+
import { withRemoteHttpResponse } from "./remote-http.js";
30+
31+
describe("package withRemoteHttpResponse", () => {
32+
beforeEach(() => {
33+
vi.clearAllMocks();
34+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(false);
35+
fetchWithSsrFGuardMock.mockResolvedValue({
36+
response: new Response("ok", { status: 200 }),
37+
finalUrl: "https://memory.example/v1",
38+
release: vi.fn(async () => {}),
39+
});
40+
});
41+
42+
it("uses trusted env proxy mode when the target will use EnvHttpProxyAgent", async () => {
43+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(true);
44+
45+
await withRemoteHttpResponse({
46+
url: "https://memory.example/v1/embeddings",
47+
onResponse: async () => undefined,
48+
});
49+
50+
expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
51+
expect.objectContaining({
52+
url: "https://memory.example/v1/embeddings",
53+
mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY,
54+
}),
55+
);
56+
});
57+
58+
it("keeps strict guarded fetch mode when proxy env would not proxy the target", async () => {
59+
await withRemoteHttpResponse({
60+
url: "https://internal.corp.example/v1/embeddings",
61+
onResponse: async () => undefined,
62+
});
63+
64+
const call = fetchWithSsrFGuardMock.mock.calls[0]?.[0];
65+
expect(call).toBeDefined();
66+
expect(call).not.toHaveProperty("mode");
67+
});
68+
});

packages/memory-host-sdk/src/host/remote-http.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { fetchWithSsrFGuard, GUARDED_FETCH_MODE } from "../../../../src/infra/net/fetch-guard.js";
2-
import { hasProxyEnvConfigured } from "../../../../src/infra/net/proxy-env.js";
2+
import { shouldUseEnvHttpProxyForUrl } from "../../../../src/infra/net/proxy-env.js";
33
import type { SsrFPolicy } from "../../../../src/infra/net/ssrf.js";
44

55
export function buildRemoteBaseUrlPolicy(baseUrl: string): SsrFPolicy | undefined {
@@ -32,9 +32,9 @@ export async function withRemoteHttpResponse<T>(params: {
3232
init: params.init,
3333
policy: params.ssrfPolicy,
3434
auditContext: params.auditContext ?? "memory-remote",
35-
// FIX: When env proxy is configured, skip local DNS resolution and route through the proxy
36-
// This fixes remote embedding/copilot memory search failing with "fetch failed" in proxy environments
37-
mode: hasProxyEnvConfigured() ? GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY : undefined,
35+
...(shouldUseEnvHttpProxyForUrl(params.url)
36+
? { mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY }
37+
: {}),
3838
});
3939
try {
4040
return await params.onResponse(response);

src/infra/net/fetch-guard.ssrf.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ describe("fetchWithSsrFGuard hardening", () => {
133133
expectEnvProxy: boolean;
134134
}): Promise<void> {
135135
clearProxyEnv();
136-
vi.stubEnv("HTTP_PROXY", "http://127.0.0.1:7890");
136+
vi.stubEnv("http_proxy", "http://127.0.0.1:7890");
137137
(globalThis as Record<string, unknown>)[TEST_UNDICI_RUNTIME_DEPS_KEY] = {
138138
Agent: agentCtor,
139139
EnvHttpProxyAgent: envHttpProxyAgentCtor,
@@ -1018,6 +1018,57 @@ describe("fetchWithSsrFGuard hardening", () => {
10181018
});
10191019
});
10201020

1021+
it("keeps DNS pinning in trusted proxy mode when only ALL_PROXY is configured", async () => {
1022+
clearProxyEnv();
1023+
vi.stubEnv("ALL_PROXY", "http://127.0.0.1:7890");
1024+
(globalThis as Record<string, unknown>)[TEST_UNDICI_RUNTIME_DEPS_KEY] = {
1025+
Agent: agentCtor,
1026+
EnvHttpProxyAgent: envHttpProxyAgentCtor,
1027+
ProxyAgent: proxyAgentCtor,
1028+
fetch: vi.fn(async () => okResponse()),
1029+
};
1030+
const lookupFn = createPublicLookup();
1031+
const fetchImpl = vi.fn(async () => okResponse());
1032+
1033+
const result = await fetchWithSsrFGuard({
1034+
url: "https://public.example/resource",
1035+
fetchImpl,
1036+
lookupFn,
1037+
mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY,
1038+
});
1039+
1040+
expect(envHttpProxyAgentCtor).not.toHaveBeenCalled();
1041+
expect(agentCtor).toHaveBeenCalled();
1042+
expect(lookupFn).toHaveBeenCalledWith("public.example", { all: true });
1043+
await result.release();
1044+
});
1045+
1046+
it("keeps DNS pinning in trusted proxy mode for NO_PROXY targets", async () => {
1047+
clearProxyEnv();
1048+
vi.stubEnv("HTTPS_PROXY", "http://127.0.0.1:7890");
1049+
vi.stubEnv("NO_PROXY", "public.example");
1050+
(globalThis as Record<string, unknown>)[TEST_UNDICI_RUNTIME_DEPS_KEY] = {
1051+
Agent: agentCtor,
1052+
EnvHttpProxyAgent: envHttpProxyAgentCtor,
1053+
ProxyAgent: proxyAgentCtor,
1054+
fetch: vi.fn(async () => okResponse()),
1055+
};
1056+
const lookupFn = createPublicLookup();
1057+
const fetchImpl = vi.fn(async () => okResponse());
1058+
1059+
const result = await fetchWithSsrFGuard({
1060+
url: "https://public.example/resource",
1061+
fetchImpl,
1062+
lookupFn,
1063+
mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY,
1064+
});
1065+
1066+
expect(envHttpProxyAgentCtor).not.toHaveBeenCalled();
1067+
expect(agentCtor).toHaveBeenCalled();
1068+
expect(lookupFn).toHaveBeenCalledWith("public.example", { all: true });
1069+
await result.release();
1070+
});
1071+
10211072
it("applies explicit timeoutMs to guarded direct dispatchers", async () => {
10221073
(globalThis as Record<string, unknown>)[TEST_UNDICI_RUNTIME_DEPS_KEY] = {
10231074
Agent: agentCtor,

src/infra/net/fetch-guard.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Dispatcher } from "undici";
22
import { logWarn } from "../../logger.js";
33
import { captureHttpExchange } from "../../proxy-capture/runtime.js";
44
import { buildTimeoutAbortSignal } from "../../utils/fetch-timeout.js";
5-
import { hasProxyEnvConfigured } from "./proxy-env.js";
5+
import { shouldUseEnvHttpProxyForUrl } from "./proxy-env.js";
66
import { retainSafeHeadersForCrossOriginRedirect as retainSafeRedirectHeaders } from "./redirect-headers.js";
77
import {
88
fetchWithRuntimeDispatcher,
@@ -355,7 +355,8 @@ export async function fetchWithSsrFGuard(params: GuardedFetchOptions): Promise<G
355355
);
356356
await assertExplicitProxyAllowed(params.dispatcherPolicy, params.lookupFn, params.policy);
357357
const canUseTrustedEnvProxy =
358-
mode === GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY && hasProxyEnvConfigured();
358+
mode === GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY &&
359+
shouldUseEnvHttpProxyForUrl(parsedUrl.toString());
359360
const timeoutMs = resolveDispatcherTimeoutMs(params.timeoutMs);
360361
if (canUseTrustedEnvProxy) {
361362
dispatcher = createHttp1EnvHttpProxyAgent(undefined, timeoutMs);

src/infra/net/proxy-env.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
hasProxyEnvConfigured,
55
matchesNoProxy,
66
resolveEnvHttpProxyUrl,
7+
shouldUseEnvHttpProxyForUrl,
78
} from "./proxy-env.js";
89

910
describe("hasProxyEnvConfigured", () => {
@@ -233,3 +234,55 @@ describe("matchesNoProxy", () => {
233234
expect(matchesNoProxy(url, env)).toBe(expected);
234235
});
235236
});
237+
238+
describe("shouldUseEnvHttpProxyForUrl", () => {
239+
it.each([
240+
{
241+
name: "uses HTTPS_PROXY for https URLs",
242+
url: "https://api.example.com/v1",
243+
env: { HTTPS_PROXY: "http://proxy.test:8080" } as NodeJS.ProcessEnv,
244+
expected: true,
245+
},
246+
{
247+
name: "falls back to HTTP_PROXY for https URLs",
248+
url: "https://api.example.com/v1",
249+
env: { HTTP_PROXY: "http://proxy.test:8080" } as NodeJS.ProcessEnv,
250+
expected: true,
251+
},
252+
{
253+
name: "uses HTTP_PROXY for http URLs",
254+
url: "http://api.example.com/v1",
255+
env: { HTTP_PROXY: "http://proxy.test:8080" } as NodeJS.ProcessEnv,
256+
expected: true,
257+
},
258+
{
259+
name: "ignores ALL_PROXY-only environments",
260+
url: "https://api.example.com/v1",
261+
env: { ALL_PROXY: "http://proxy.test:8080" } as NodeJS.ProcessEnv,
262+
expected: false,
263+
},
264+
{
265+
name: "keeps strict mode for NO_PROXY matches",
266+
url: "https://internal.corp.example/v1",
267+
env: {
268+
HTTPS_PROXY: "http://proxy.test:8080",
269+
NO_PROXY: "corp.example",
270+
} as NodeJS.ProcessEnv,
271+
expected: false,
272+
},
273+
{
274+
name: "keeps strict mode for non-http URLs",
275+
url: "file:///tmp/input.txt",
276+
env: { HTTPS_PROXY: "http://proxy.test:8080" } as NodeJS.ProcessEnv,
277+
expected: false,
278+
},
279+
{
280+
name: "keeps strict mode for malformed URLs",
281+
url: "not-a-url",
282+
env: { HTTPS_PROXY: "http://proxy.test:8080" } as NodeJS.ProcessEnv,
283+
expected: false,
284+
},
285+
])("$name", ({ url, env, expected }) => {
286+
expect(shouldUseEnvHttpProxyForUrl(url, env)).toBe(expected);
287+
});
288+
});

src/infra/net/proxy-env.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ export function hasEnvHttpProxyConfigured(
5454
return resolveEnvHttpProxyUrl(protocol, env) !== undefined;
5555
}
5656

57+
export function shouldUseEnvHttpProxyForUrl(
58+
targetUrl: string,
59+
env: NodeJS.ProcessEnv = process.env,
60+
): boolean {
61+
let protocol: "http" | "https";
62+
try {
63+
const parsed = new URL(targetUrl);
64+
if (parsed.protocol === "http:") {
65+
protocol = "http";
66+
} else if (parsed.protocol === "https:") {
67+
protocol = "https";
68+
} else {
69+
return false;
70+
}
71+
} catch {
72+
return false;
73+
}
74+
75+
return hasEnvHttpProxyConfigured(protocol, env) && !matchesNoProxy(targetUrl, env);
76+
}
77+
5778
/**
5879
* Check whether a target URL should bypass the HTTP proxy per NO_PROXY env var.
5980
*

src/media-understanding/shared.test.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
22

3-
const { fetchWithSsrFGuardMock, hasEnvHttpProxyConfiguredMock, matchesNoProxyMock } = vi.hoisted(
4-
() => ({
5-
fetchWithSsrFGuardMock: vi.fn(),
6-
hasEnvHttpProxyConfiguredMock: vi.fn(() => false),
7-
matchesNoProxyMock: vi.fn(() => false),
8-
}),
9-
);
3+
const { fetchWithSsrFGuardMock, shouldUseEnvHttpProxyForUrlMock } = vi.hoisted(() => ({
4+
fetchWithSsrFGuardMock: vi.fn(),
5+
shouldUseEnvHttpProxyForUrlMock: vi.fn(() => false),
6+
}));
107

118
vi.mock("../infra/net/fetch-guard.js", async () => {
129
const actual = await vi.importActual<typeof import("../infra/net/fetch-guard.js")>(
@@ -24,8 +21,7 @@ vi.mock("../infra/net/proxy-env.js", async () => {
2421
);
2522
return {
2623
...actual,
27-
hasEnvHttpProxyConfigured: hasEnvHttpProxyConfiguredMock,
28-
matchesNoProxy: matchesNoProxyMock,
24+
shouldUseEnvHttpProxyForUrl: shouldUseEnvHttpProxyForUrlMock,
2925
};
3026
});
3127

@@ -42,8 +38,7 @@ import {
4238
} from "./shared.js";
4339

4440
beforeEach(() => {
45-
hasEnvHttpProxyConfiguredMock.mockReturnValue(false);
46-
matchesNoProxyMock.mockReturnValue(false);
41+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(false);
4742
});
4843

4944
afterEach(() => {
@@ -417,7 +412,7 @@ describe("fetchWithTimeoutGuarded", () => {
417412
});
418413

419414
it("does not set a guarded fetch mode when no HTTP proxy env is configured", async () => {
420-
hasEnvHttpProxyConfiguredMock.mockReturnValue(false);
415+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(false);
421416
fetchWithSsrFGuardMock.mockResolvedValue({
422417
response: new Response(null, { status: 200 }),
423418
finalUrl: "https://example.com",
@@ -432,7 +427,7 @@ describe("fetchWithTimeoutGuarded", () => {
432427
});
433428

434429
it("auto-selects trusted env proxy mode when HTTP proxy env is configured", async () => {
435-
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
430+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(true);
436431
fetchWithSsrFGuardMock.mockResolvedValue({
437432
response: new Response(null, { status: 200 }),
438433
finalUrl: "https://api.minimax.io",
@@ -454,7 +449,7 @@ describe("fetchWithTimeoutGuarded", () => {
454449
});
455450

456451
it("respects an explicit mode from the caller when HTTP proxy env is configured", async () => {
457-
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
452+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(true);
458453
fetchWithSsrFGuardMock.mockResolvedValue({
459454
response: new Response(null, { status: 200 }),
460455
finalUrl: "https://api.example.com",
@@ -473,7 +468,7 @@ describe("fetchWithTimeoutGuarded", () => {
473468
});
474469

475470
it("auto-upgrades transcription requests to trusted env proxy when proxy env is configured", async () => {
476-
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
471+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(true);
477472
fetchWithSsrFGuardMock.mockResolvedValue({
478473
response: new Response(null, { status: 200 }),
479474
finalUrl: "https://api.openai.com",
@@ -495,7 +490,7 @@ describe("fetchWithTimeoutGuarded", () => {
495490
});
496491

497492
it("forwards an explicit mode override through postJsonRequest even when proxy env is configured", async () => {
498-
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
493+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(true);
499494
fetchWithSsrFGuardMock.mockResolvedValue({
500495
response: new Response(null, { status: 200 }),
501496
finalUrl: "https://api.example.com",
@@ -518,7 +513,7 @@ describe("fetchWithTimeoutGuarded", () => {
518513
});
519514

520515
it("forwards an explicit mode override through postTranscriptionRequest even when proxy env is configured", async () => {
521-
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
516+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(true);
522517
fetchWithSsrFGuardMock.mockResolvedValue({
523518
response: new Response(null, { status: 200 }),
524519
finalUrl: "https://api.example.com",
@@ -541,11 +536,11 @@ describe("fetchWithTimeoutGuarded", () => {
541536
});
542537

543538
it("does not auto-upgrade when only ALL_PROXY is configured (HTTP(S) proxy gate)", async () => {
544-
// ALL_PROXY is ignored by EnvHttpProxyAgent; `hasEnvHttpProxyConfigured`
539+
// ALL_PROXY is ignored by EnvHttpProxyAgent; the shared proxy URL helper
545540
// reflects that by returning false when only ALL_PROXY is set. Auto-upgrade
546541
// must NOT fire, otherwise the request would skip pinned-DNS/SSRF checks
547542
// and then be dispatched directly.
548-
hasEnvHttpProxyConfiguredMock.mockReturnValue(false);
543+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(false);
549544
fetchWithSsrFGuardMock.mockResolvedValue({
550545
response: new Response(null, { status: 200 }),
551546
finalUrl: "https://api.example.com",
@@ -568,7 +563,7 @@ describe("fetchWithTimeoutGuarded", () => {
568563
// Callers with custom proxy URL / proxyTls / connect options must keep
569564
// control over the dispatcher. Auto-upgrade would build an
570565
// EnvHttpProxyAgent that silently drops those overrides.
571-
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
566+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(true);
572567
fetchWithSsrFGuardMock.mockResolvedValue({
573568
response: new Response(null, { status: 200 }),
574569
finalUrl: "https://api.example.com",
@@ -595,8 +590,7 @@ describe("fetchWithTimeoutGuarded", () => {
595590
// for NO_PROXY matches, but in TRUSTED_ENV_PROXY mode fetchWithSsrFGuard
596591
// skips pinned-DNS checks — so auto-upgrading those targets would bypass
597592
// SSRF protection. Keep strict mode for NO_PROXY matches.
598-
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
599-
matchesNoProxyMock.mockReturnValue(true);
593+
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(false);
600594
fetchWithSsrFGuardMock.mockResolvedValue({
601595
response: new Response(null, { status: 200 }),
602596
finalUrl: "https://internal.corp.example",

0 commit comments

Comments
 (0)