fix(memory-host-sdk): use TRUSTED_ENV_PROXY mode for remote embeddings in proxy environments (#71506)

* fix(memory-host-sdk): use TRUSTED_ENV_PROXY mode in withRemoteHttpResponse

When a HTTP/HTTPS proxy is configured via environment variables
(HTTPS_PROXY, HTTP_PROXY, ALL_PROXY), the withRemoteHttpResponse
function now passes mode=TRUSTED_ENV_PROXY to fetchWithSsrFGuard.

This causes DNS resolution to skip the local resolver and route
through the configured proxy, fixing 'fetch failed' errors for
remote memory embeddings (including GitHub Copilot embeddings) in
proxy environments (e.g. Clash TUN, corporate proxies).

Previously, without an explicit mode, fetchWithSsrFGuard defaulted
to STRICT mode which performs local DNS pre-resolution via
resolvePinnedHostnameWithPolicy(), failing in proxy environments
where DNS must go through the proxy.

Fixes: openclaw/openclaw#52162

* fix: harden memory env proxy guard (#71506) (thanks @DhtIsCoding)

---------

Co-authored-by: Dht <dht@openclaw.ai>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Donetta Flatley
2026-04-25 19:24:09 +08:00
committed by GitHub
parent f1470b52fb
commit f408bba9de
11 changed files with 294 additions and 51 deletions

View File

@@ -0,0 +1,68 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const { fetchWithSsrFGuardMock, shouldUseEnvHttpProxyForUrlMock } = vi.hoisted(() => ({
fetchWithSsrFGuardMock: vi.fn(),
shouldUseEnvHttpProxyForUrlMock: vi.fn(() => false),
}));
vi.mock("../../../../src/infra/net/fetch-guard.js", async () => {
const actual = await vi.importActual<typeof import("../../../../src/infra/net/fetch-guard.js")>(
"../../../../src/infra/net/fetch-guard.js",
);
return {
...actual,
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
};
});
vi.mock("../../../../src/infra/net/proxy-env.js", async () => {
const actual = await vi.importActual<typeof import("../../../../src/infra/net/proxy-env.js")>(
"../../../../src/infra/net/proxy-env.js",
);
return {
...actual,
shouldUseEnvHttpProxyForUrl: shouldUseEnvHttpProxyForUrlMock,
};
});
import { GUARDED_FETCH_MODE } from "../../../../src/infra/net/fetch-guard.js";
import { withRemoteHttpResponse } from "./remote-http.js";
describe("package withRemoteHttpResponse", () => {
beforeEach(() => {
vi.clearAllMocks();
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(false);
fetchWithSsrFGuardMock.mockResolvedValue({
response: new Response("ok", { status: 200 }),
finalUrl: "https://memory.example/v1",
release: vi.fn(async () => {}),
});
});
it("uses trusted env proxy mode when the target will use EnvHttpProxyAgent", async () => {
shouldUseEnvHttpProxyForUrlMock.mockReturnValue(true);
await withRemoteHttpResponse({
url: "https://memory.example/v1/embeddings",
onResponse: async () => undefined,
});
expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://memory.example/v1/embeddings",
mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY,
}),
);
});
it("keeps strict guarded fetch mode when proxy env would not proxy the target", async () => {
await withRemoteHttpResponse({
url: "https://internal.corp.example/v1/embeddings",
onResponse: async () => undefined,
});
const call = fetchWithSsrFGuardMock.mock.calls[0]?.[0];
expect(call).toBeDefined();
expect(call).not.toHaveProperty("mode");
});
});

View File

@@ -1,4 +1,5 @@
import { fetchWithSsrFGuard } from "../../../../src/infra/net/fetch-guard.js";
import { fetchWithSsrFGuard, GUARDED_FETCH_MODE } from "../../../../src/infra/net/fetch-guard.js";
import { shouldUseEnvHttpProxyForUrl } from "../../../../src/infra/net/proxy-env.js";
import type { SsrFPolicy } from "../../../../src/infra/net/ssrf.js";
export function buildRemoteBaseUrlPolicy(baseUrl: string): SsrFPolicy | undefined {
@@ -31,6 +32,9 @@ export async function withRemoteHttpResponse<T>(params: {
init: params.init,
policy: params.ssrfPolicy,
auditContext: params.auditContext ?? "memory-remote",
...(shouldUseEnvHttpProxyForUrl(params.url)
? { mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY }
: {}),
});
try {
return await params.onResponse(response);