fix: default web_fetch SSRF policy for fake-IP proxy DNS (#1065)

This commit is contained in:
paisley
2026-05-25 18:28:10 +08:00
committed by GitHub
parent e79a4f923e
commit 8635f0f5eb
2 changed files with 103 additions and 2 deletions

View File

@@ -1894,6 +1894,53 @@ export async function syncGatewayTokenToConfig(token: string): Promise<void> {
});
}
/**
* Default web_fetch SSRF policy for fake-IP / transparent-proxy environments
* (e.g. Clash/Surge resolving public hostnames into 198.18.0.0/15). OpenClaw's
* web_fetch tool does not read browser.ssrfPolicy — it uses tools.web.fetch only.
*/
function ensureWebFetchSsrfPolicyInConfig(config: Record<string, unknown>): boolean {
const tools = (
config.tools && typeof config.tools === 'object'
? { ...(config.tools as Record<string, unknown>) }
: {}
) as Record<string, unknown>;
const web = (
tools.web && typeof tools.web === 'object'
? { ...(tools.web as Record<string, unknown>) }
: {}
) as Record<string, unknown>;
const fetch = (
web.fetch && typeof web.fetch === 'object'
? { ...(web.fetch as Record<string, unknown>) }
: {}
) as Record<string, unknown>;
const ssrfPolicy = (
fetch.ssrfPolicy && typeof fetch.ssrfPolicy === 'object'
? { ...(fetch.ssrfPolicy as Record<string, unknown>) }
: {}
) as Record<string, unknown>;
let changed = false;
if (ssrfPolicy.allowRfc2544BenchmarkRange === undefined) {
ssrfPolicy.allowRfc2544BenchmarkRange = true;
changed = true;
}
if (ssrfPolicy.allowIpv6UniqueLocalRange === undefined) {
ssrfPolicy.allowIpv6UniqueLocalRange = true;
changed = true;
}
if (!changed) return false;
fetch.ssrfPolicy = ssrfPolicy;
web.fetch = fetch;
tools.web = web;
config.tools = tools;
return true;
}
/**
* Ensure browser automation is enabled in ~/.openclaw/openclaw.json.
*/
@@ -1931,11 +1978,13 @@ export async function syncBrowserConfigToOpenClaw(): Promise<void> {
changed = true;
}
changed = ensureWebFetchSsrfPolicyInConfig(config) || changed;
if (!changed) return;
config.browser = browser;
await writeOpenClawJson(config);
console.log('Synced browser config to openclaw.json');
console.log('Synced browser and web_fetch config to openclaw.json');
});
}
@@ -2051,6 +2100,11 @@ export async function batchSyncConfigFields(token: string): Promise<void> {
modified = true;
}
// ── web_fetch SSRF policy (fake-IP / transparent-proxy environments) ──
if (ensureWebFetchSsrfPolicyInConfig(config)) {
modified = true;
}
// ── Session idle minutes ──
const session = (
config.session && typeof config.session === 'object'
@@ -2069,7 +2123,7 @@ export async function batchSyncConfigFields(token: string): Promise<void> {
if (modified) {
await writeOpenClawJson(config);
console.log('Synced gateway token, browser config, and session idle to openclaw.json');
console.log('Synced gateway token, browser config, web_fetch SSRF policy, and session idle to openclaw.json');
}
});
}

View File

@@ -1820,3 +1820,50 @@ describe('ensureOpenClawProviderAgentRuntimePins', () => {
expect(after).toEqual(before);
});
});
describe('batchSyncConfigFields', () => {
beforeEach(async () => {
vi.resetModules();
vi.restoreAllMocks();
await rm(testHome, { recursive: true, force: true });
await rm(testUserData, { recursive: true, force: true });
});
it('seeds web_fetch SSRF policy for fake-IP proxy environments', async () => {
await writeOpenClawJson({ gateway: { auth: { mode: 'token', token: 'old' } } });
const { batchSyncConfigFields } = await import('@electron/utils/openclaw-auth');
await batchSyncConfigFields('new-token');
const config = await readOpenClawJson();
const fetch = (config.tools as Record<string, unknown>).web as Record<string, unknown>;
const ssrfPolicy = (fetch.fetch as Record<string, unknown>).ssrfPolicy as Record<string, unknown>;
expect(ssrfPolicy.allowRfc2544BenchmarkRange).toBe(true);
expect(ssrfPolicy.allowIpv6UniqueLocalRange).toBe(true);
});
it('does not override explicit web_fetch SSRF policy opt-outs', async () => {
await writeOpenClawJson({
gateway: { auth: { mode: 'token', token: 'old' } },
tools: {
web: {
fetch: {
ssrfPolicy: {
allowRfc2544BenchmarkRange: false,
allowIpv6UniqueLocalRange: false,
},
},
},
},
});
const { batchSyncConfigFields } = await import('@electron/utils/openclaw-auth');
await batchSyncConfigFields('new-token');
const config = await readOpenClawJson();
const fetch = (config.tools as Record<string, unknown>).web as Record<string, unknown>;
const ssrfPolicy = (fetch.fetch as Record<string, unknown>).ssrfPolicy as Record<string, unknown>;
expect(ssrfPolicy.allowRfc2544BenchmarkRange).toBe(false);
expect(ssrfPolicy.allowIpv6UniqueLocalRange).toBe(false);
});
});