Skip to content

Commit 2a915fb

Browse files
cluster2600steipete
authored andcommitted
fix(net): skip DNS pinning before trusted env proxy dispatch
1 parent b4034b3 commit 2a915fb

3 files changed

Lines changed: 43 additions & 4 deletions

File tree

CHANGELOG.md

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

99
- 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.
10+
- 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.
1011

1112
## 2026.4.7-1
1213

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ async function expectRedirectFailure(params: {
8181
}
8282

8383
describe("fetchWithSsrFGuard hardening", () => {
84+
const PROXY_ENV_KEYS = [
85+
"HTTP_PROXY",
86+
"HTTPS_PROXY",
87+
"ALL_PROXY",
88+
"http_proxy",
89+
"https_proxy",
90+
"all_proxy",
91+
] as const;
92+
8493
type LookupFn = NonNullable<Parameters<typeof fetchWithSsrFGuard>[0]["lookupFn"]>;
8594
const CROSS_ORIGIN_REDIRECT_STRIPPED_HEADERS = [
8695
"authorization",
@@ -100,10 +109,17 @@ describe("fetchWithSsrFGuard hardening", () => {
100109
const createPublicLookup = (): LookupFn =>
101110
vi.fn(async () => [{ address: "93.184.216.34", family: 4 }]) as unknown as LookupFn;
102111

112+
function clearProxyEnv(): void {
113+
for (const key of PROXY_ENV_KEYS) {
114+
vi.stubEnv(key, "");
115+
}
116+
}
117+
103118
async function runProxyModeDispatcherTest(params: {
104119
mode: (typeof GUARDED_FETCH_MODE)[keyof typeof GUARDED_FETCH_MODE];
105120
expectEnvProxy: boolean;
106121
}): Promise<void> {
122+
clearProxyEnv();
107123
vi.stubEnv("HTTP_PROXY", "http://127.0.0.1:7890");
108124
(globalThis as Record<string, unknown>)[TEST_UNDICI_RUNTIME_DEPS_KEY] = {
109125
Agent: agentCtor,
@@ -1032,4 +1048,26 @@ describe("fetchWithSsrFGuard hardening", () => {
10321048
).rejects.toThrow(/blocked/i);
10331049
expect(fetchImpl).not.toHaveBeenCalled();
10341050
});
1051+
1052+
it("falls back to DNS pinning in trusted proxy mode when no proxy env var is configured", async () => {
1053+
clearProxyEnv();
1054+
const lookupFn = createPublicLookup();
1055+
const fetchImpl = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
1056+
const requestInit = init as RequestInit & { dispatcher?: unknown };
1057+
expect(requestInit.dispatcher).toBeDefined();
1058+
expect(getDispatcherClassName(requestInit.dispatcher)).not.toBe("EnvHttpProxyAgent");
1059+
return okResponse();
1060+
});
1061+
1062+
const result = await fetchWithSsrFGuard({
1063+
url: "https://public.example/resource",
1064+
fetchImpl,
1065+
lookupFn,
1066+
mode: GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY,
1067+
});
1068+
1069+
expect(fetchImpl).toHaveBeenCalledTimes(1);
1070+
expect(lookupFn).toHaveBeenCalledOnce();
1071+
await result.release();
1072+
});
10351073
});

src/infra/net/fetch-guard.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,17 +313,17 @@ export async function fetchWithSsrFGuard(params: GuardedFetchOptions): Promise<G
313313
try {
314314
assertExplicitProxySupportsPinnedDns(parsedUrl, params.dispatcherPolicy, params.pinDns);
315315
await assertExplicitProxyAllowed(params.dispatcherPolicy, params.lookupFn, params.policy);
316-
const pinned = await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
317-
lookupFn: params.lookupFn,
318-
policy: params.policy,
319-
});
320316
const canUseTrustedEnvProxy =
321317
mode === GUARDED_FETCH_MODE.TRUSTED_ENV_PROXY && hasProxyEnvConfigured();
322318
if (canUseTrustedEnvProxy) {
323319
dispatcher = createHttp1EnvHttpProxyAgent();
324320
} else if (params.pinDns === false) {
325321
dispatcher = createPolicyDispatcherWithoutPinnedDns(params.dispatcherPolicy);
326322
} else {
323+
const pinned = await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
324+
lookupFn: params.lookupFn,
325+
policy: params.policy,
326+
});
327327
dispatcher = createPinnedDispatcher(pinned, params.dispatcherPolicy, params.policy);
328328
}
329329

0 commit comments

Comments
 (0)