From 5e6b26429c547a406be38a8c81e26ba70c8beef5 Mon Sep 17 00:00:00 2001 From: Aishanee Shah Date: Thu, 5 Mar 2026 20:55:40 +0000 Subject: [PATCH] fix(core): remove private IP rescue to address SSRF vulnerability and fix formatting --- packages/core/src/tools/web-fetch.test.ts | 19 +++++++------------ packages/core/src/tools/web-fetch.ts | 3 +-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/core/src/tools/web-fetch.test.ts b/packages/core/src/tools/web-fetch.test.ts index c33bebb3c3..bf92d0a7d6 100644 --- a/packages/core/src/tools/web-fetch.test.ts +++ b/packages/core/src/tools/web-fetch.test.ts @@ -463,7 +463,7 @@ describe('WebFetchTool', () => { expect(result.llmContent).toContain('rescued content'); }); - it('should rescue private URLs via fallback and merge with public results', async () => { + it('should NOT rescue private URLs via fallback but still fetch public ones', async () => { vi.mocked(fetchUtils.isPrivateIp).mockImplementation( (url) => url === 'https://private.com/', ); @@ -485,11 +485,6 @@ describe('WebFetchTool', () => { ], }); - // Mock fallback fetch for the private URL - mockFetch('https://private.com/', { - text: () => Promise.resolve('private rescued content'), - }); - const tool = new WebFetchTool(mockConfig, bus); const params = { prompt: 'fetch https://public.com and https://private.com', @@ -498,16 +493,16 @@ describe('WebFetchTool', () => { const result = await invocation.execute(new AbortController().signal); expect(result.llmContent).toContain('public content'); - expect(result.llmContent).toContain('--- Rescued Content ---'); - expect(result.llmContent).toContain('URL: https://private.com/'); - expect(result.llmContent).toContain('private rescued content'); + expect(result.llmContent).not.toContain('--- Rescued Content ---'); + expect(result.llmContent).not.toContain('URL: https://private.com/'); }); it('should return WEB_FETCH_FALLBACK_FAILED on fallback fetch failure', async () => { - vi.spyOn(fetchUtils, 'isPrivateIp').mockReturnValue(true); - mockFetch('https://private.ip/', new Error('fetch failed')); + vi.spyOn(fetchUtils, 'isPrivateIp').mockReturnValue(false); + mockGenerateContent.mockRejectedValue(new Error('primary fail')); + mockFetch('https://public.ip/', new Error('fallback fetch failed')); const tool = new WebFetchTool(mockConfig, bus); - const params = { prompt: 'fetch https://private.ip' }; + const params = { prompt: 'fetch https://public.ip' }; const invocation = tool.build(params); const result = await invocation.execute(new AbortController().signal); expect(result.error?.type).toBe(ToolErrorType.WEB_FETCH_FALLBACK_FAILED); diff --git a/packages/core/src/tools/web-fetch.ts b/packages/core/src/tools/web-fetch.ts index 57e3b4573d..c34df69364 100644 --- a/packages/core/src/tools/web-fetch.ts +++ b/packages/core/src/tools/web-fetch.ts @@ -606,7 +606,7 @@ Response: ${truncateString(rawResponseText, 10000, '\n\n... [Error response trun let llmContent = ''; let returnDisplay = ''; - const needsRescue: string[] = [...privateUrls]; + const needsRescue: string[] = []; if (publicUrls.length > 0) { const geminiClient = this.config.getGeminiClient(); @@ -619,7 +619,6 @@ Response: ${truncateString(rawResponseText, 10000, '\n\n... [Error response trun ); let responseText = getResponseText(response) || ''; - const urlContextMeta = response.candidates?.[0]?.urlContextMetadata as | UrlContextMetadata | undefined;