fix: default web_fetch SSRF policy for fake-IP proxy DNS (#1065)
This commit is contained in:
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user