fix(net): skip DNS pinning before trusted env proxy dispatch

This commit is contained in:
Maxime Grenu
2026-04-01 14:45:43 +02:00
committed by Peter Steinberger
parent b4034b32c3
commit d7c3210cd6
3 changed files with 43 additions and 4 deletions

View File

@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Slack: honor ambient HTTP(S) proxy settings for Socket Mode WebSocket connections, including NO_PROXY exclusions, so proxy-only deployments can connect without a monkey patch. (#62878) Thanks @mjamiv.
- Network/fetch guard: skip target DNS pinning when trusted env-proxy mode is active so proxy-only sandboxes can let the trusted proxy resolve outbound hosts. (#59007) Thanks @cluster2600.
## 2026.4.7-1

View File

@@ -81,6 +81,15 @@ async function expectRedirectFailure(params: {
}
describe("fetchWithSsrFGuard hardening", () => {
const PROXY_ENV_KEYS = [
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"http_proxy",
"https_proxy",
"all_proxy",
] as const;
type LookupFn = NonNullable<Parameters<typeof fetchWithSsrFGuard>[0]["lookupFn"]>;
const CROSS_ORIGIN_REDIRECT_STRIPPED_HEADERS = [
"authorization",
@@ -100,10 +109,17 @@ describe("fetchWithSsrFGuard hardening", () => {
const createPublicLookup = (): LookupFn =>
vi.fn(async () => [{ address: "93.184.216.34", family: 4 }]) as unknown as LookupFn;
function clearProxyEnv(): void {
for (const key of PROXY_ENV_KEYS) {
vi.stubEnv(key, "");
}
}
async function runProxyModeDispatcherTest(params: {
mode: (typeof GUARDED_FETCH_MODE)[keyof typeof GUARDED_FETCH_MODE];
expectEnvProxy: boolean;
}): Promise<void> {
clearProxyEnv();
vi.stubEnv("HTTP_PROXY", "http://127.0.0.1:7890");
(globalThis as Record<string, unknown>)[TEST_UNDICI_RUNTIME_DEPS_KEY] = {
Agent: agentCtor,
@@ -1032,4 +1048,26 @@ describe("fetchWithSsrFGuard hardening", () => {
).rejects.toThrow(/blocked/i);
expect(fetchImpl).not.toHaveBeenCalled();
});
it("falls back to DNS pinning in trusted proxy mode when no proxy env var is configured", async () => {
clearProxyEnv();
const lookupFn = createPublicLookup();
const fetchImpl = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
const requestInit = init as RequestInit & { dispatcher?: unknown };
expect(requestInit.dispatcher).toBeDefined();
expect(getDispatcherClassName(requestInit.dispatcher)).not.toBe("EnvHttpProxyAgent");
return okResponse();
});
const result = await fetchWithSsrFGuard({
url: "https://public.example/resource",
fetchImpl,
lookupFn,
mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY,
});
expect(fetchImpl).toHaveBeenCalledTimes(1);
expect(lookupFn).toHaveBeenCalledOnce();
await result.release();
});
});

View File

@@ -313,10 +313,6 @@ export async function fetchWithSsrFGuard(params: GuardedFetchOptions): Promise<G
try {
assertExplicitProxySupportsPinnedDns(parsedUrl, params.dispatcherPolicy, params.pinDns);
await assertExplicitProxyAllowed(params.dispatcherPolicy, params.lookupFn, params.policy);
const pinned = await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
lookupFn: params.lookupFn,
policy: params.policy,
});
const canUseTrustedEnvProxy =
mode === GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY && hasProxyEnvConfigured();
if (canUseTrustedEnvProxy) {
@@ -324,6 +320,10 @@ export async function fetchWithSsrFGuard(params: GuardedFetchOptions): Promise<G
} else if (params.pinDns === false) {
dispatcher = createPolicyDispatcherWithoutPinnedDns(params.dispatcherPolicy);
} else {
const pinned = await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
lookupFn: params.lookupFn,
policy: params.policy,
});
dispatcher = createPinnedDispatcher(pinned, params.dispatcherPolicy, params.policy);
}