diff --git a/cli/src/clients/skillhub-client.ts b/cli/src/clients/skillhub-client.ts index 8c6a36d67..bc258e3c8 100644 --- a/cli/src/clients/skillhub-client.ts +++ b/cli/src/clients/skillhub-client.ts @@ -242,7 +242,12 @@ export class SkillHubClient { // Keep the configured registry prefix, as direct download() does. const relativeUrl = downloadUrl.startsWith('/api/') ? downloadUrl.slice(1) : downloadUrl const registryBase = `${this.registry.replace(/\/+$/, '')}/` - response = await this.fetchImpl(new URL(relativeUrl, registryBase).toString(), { headers: this.headers() }) + const url = new URL(relativeUrl, registryBase) + const registryApi = new URL('api/', registryBase) + const headers = url.origin === registryApi.origin && url.pathname.startsWith(registryApi.pathname) + ? this.headers() + : {} + response = await this.fetchImpl(url.toString(), { headers }) } catch { throw new CliError('registry unreachable', EXIT.network, { registry: this.registry, next: 'check network or pass --registry' }) } diff --git a/cli/test/unit/clients/skillhub-client.test.ts b/cli/test/unit/clients/skillhub-client.test.ts index 765e98663..d0752993e 100644 --- a/cli/test/unit/clients/skillhub-client.test.ts +++ b/cli/test/unit/clients/skillhub-client.test.ts @@ -23,6 +23,30 @@ describe('SkillHubClient', () => { expect(requests).toEqual([expected]) }) + test.each([ + ['https://registry.test', 'https://cdn.test/packages/skill.zip?signature=test'], + ['https://registry.test', '//cdn.test/api/packages/skill.zip'], + ['https://registry.test', 'https://registry.test.cdn.test/api/skill.zip'], + ['https://registry.test', 'http://registry.test/api/skill.zip'], + ['https://registry.test', 'https://registry.test:8443/api/skill.zip'], + ['https://registry.test', 'https://registry.test/packages/skill.zip'], + ['https://registry.test/skillhub', 'https://registry.test/api/skill.zip'], + ['https://registry.test/skillhub', 'https://registry.test/skillhub/api-other/skill.zip'] + ])('omits registry credentials when %s downloads outside its API: %s', async (registry, downloadUrl) => { + const requests: Array<{ url: string; authorization: string | null }> = [] + const fetchImpl = (async (input: URL | RequestInfo, init?: RequestInit) => { + const authorization = new Headers(init?.headers).get('Authorization') + requests.push({ url: String(input), authorization }) + return authorization + ? new Response('InvalidArgument', { status: 400 }) + : new Response('package-bytes') + }) as unknown as typeof fetch + const client = new SkillHubClient(registry, 'test-token', fetchImpl) + + expect(await (await client.downloadFromUrl(downloadUrl)).text()).toBe('package-bytes') + expect(requests).toEqual([{ url: new URL(downloadUrl, registry).toString(), authorization: null }]) + }) + test('uses the provided multipart file name when publishing', async () => { const fetchImpl = (async (_input: URL | RequestInfo, init?: RequestInit) => { const formData = init?.body as FormData