From 872dc8c795944dd2992837ce2d1de0c2271741e2 Mon Sep 17 00:00:00 2001 From: aiirvizionz Date: Thu, 23 Jul 2026 12:33:50 -0600 Subject: [PATCH] fix(ai-atlascloud): require clean api base urls --- packages/ai/atlascloud/src/index.test.ts | 16 ++++++++++++++++ packages/ai/atlascloud/src/index.ts | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/ai/atlascloud/src/index.test.ts b/packages/ai/atlascloud/src/index.test.ts index 5e811386..8b9364ae 100644 --- a/packages/ai/atlascloud/src/index.test.ts +++ b/packages/ai/atlascloud/src/index.test.ts @@ -81,6 +81,22 @@ describe('AtlasCloud OpenAI-compatible generation', () => { expect(url).toBe('https://proxy.example.com/v1/chat/completions'); }); + it.each([ + ['missing scheme', 'proxy.example.com'], + ['unsupported scheme', 'ftp://proxy.example.com'], + ['credentials', 'https://user:pass@proxy.example.com'], + ['query string', 'https://proxy.example.com?debug=true'], + ['fragment', 'https://proxy.example.com#v1'], + ])('rejects unclean configured base URLs: %s', async (_case, baseUrl) => { + const fetchMock = vi.fn(); + vi.stubGlobal('fetch', fetchMock); + + await expect(adapter.generate(ctx(), 'hello', {}, { baseUrl })).rejects.toThrow( + /AtlasCloud baseUrl/, + ); + expect(fetchMock).not.toHaveBeenCalled(); + }); + it('includes status and redacted response body excerpt on errors', async () => { const apiKey = 'test-key-crossing-truncation-boundary'; const prefix = 'x'.repeat(190); diff --git a/packages/ai/atlascloud/src/index.ts b/packages/ai/atlascloud/src/index.ts index 9a3b9ffe..2b0190a0 100644 --- a/packages/ai/atlascloud/src/index.ts +++ b/packages/ai/atlascloud/src/index.ts @@ -7,7 +7,23 @@ interface Config { const DEFAULT_BASE = 'https://api.atlascloud.ai'; function chatCompletionsUrl(baseUrl?: string): string { - return `${(baseUrl ?? DEFAULT_BASE).replace(/\/+$/, '')}/v1/chat/completions`; + return `${cleanBaseUrl(baseUrl ?? DEFAULT_BASE)}/v1/chat/completions`; +} + +function cleanBaseUrl(value: string): string { + let url: URL; + try { + url = new URL(value); + } catch { + throw new Error('AtlasCloud baseUrl must be a valid URL'); + } + if (url.protocol !== 'https:' && url.protocol !== 'http:') { + throw new Error('AtlasCloud baseUrl must use http or https'); + } + if (url.username || url.password || url.search || url.hash) { + throw new Error('AtlasCloud baseUrl must be a clean API base without credentials, query, or hash'); + } + return url.toString().replace(/\/+$/, ''); } function redact(value: string, apiKey: string): string {