From 02a49f05ddfa96fa09ecff68ebe393d60ed74add Mon Sep 17 00:00:00 2001 From: Peter <304086198+peter-research@users.noreply.github.com> Date: Wed, 14 Oct 2026 12:58:50 +0200 Subject: [PATCH 1/4] Reject malformed release download origins --- cli/release-core/launcher.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/release-core/launcher.js b/cli/release-core/launcher.js index d830cf074e..f82b79efcc 100644 --- a/cli/release-core/launcher.js +++ b/cli/release-core/launcher.js @@ -101,13 +101,18 @@ function resolveDownloadOrigin(configuredOrigin, { warn = () => {} } = {}) { const isHttp = parsed.protocol === 'http:' || parsed.protocol === 'https:' // Only an origin: credentials or a query would ride along on every request. const isBareOrigin = - !parsed.username && !parsed.password && !parsed.search && !parsed.hash + parsed.pathname === '/' && + !parsed.username && + !parsed.password && + !parsed.search && + !parsed.hash if (isHttp && isLocal && isBareOrigin) { return trimmed } if ( parsed.protocol === 'https:' && isReleaseOriginHost(parsed.hostname) && + !parsed.port && isBareOrigin ) { return trimmed From 7b6c7db3b6c8f6a5f63dd00f0e4fcfe27ef9201f Mon Sep 17 00:00:00 2001 From: Peter <304086198+peter-research@users.noreply.github.com> Date: Wed, 14 Oct 2026 12:58:52 +0200 Subject: [PATCH 2/4] Validate credentials and ports on release redirects --- cli/release-core/http.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cli/release-core/http.js b/cli/release-core/http.js index 14d411f746..3f4d45ffa3 100644 --- a/cli/release-core/http.js +++ b/cli/release-core/http.js @@ -60,7 +60,18 @@ function evaluateRedirect(currentUrl, location, options = {}) { } } - const originalHostname = new URL(options.originalUrl || currentUrl).hostname + const originalUrl = new URL(options.originalUrl || currentUrl) + const originalHostname = originalUrl.hostname + + if (target.username || target.password) { + return { ok: false, reason: 'Refusing redirect containing URL credentials' } + } + if (target.port !== originalUrl.port) { + return { + ok: false, + reason: `Refusing redirect to unexpected port ${target.port || '(default)'}`, + } + } if (!isRedirectHostAllowed(target.hostname, originalHostname)) { return { ok: false, From b124fc638153a080b9f1f00481a1d5eaa2f2f279 Mon Sep 17 00:00:00 2001 From: Peter <304086198+peter-research@users.noreply.github.com> Date: Wed, 14 Oct 2026 12:58:54 +0200 Subject: [PATCH 3/4] Test release origin override validation --- .../__tests__/release/wrapper-safety.test.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cli/src/__tests__/release/wrapper-safety.test.ts b/cli/src/__tests__/release/wrapper-safety.test.ts index 26ac6f9062..168480b2d2 100644 --- a/cli/src/__tests__/release/wrapper-safety.test.ts +++ b/cli/src/__tests__/release/wrapper-safety.test.ts @@ -166,7 +166,30 @@ for (const wrapper of wrappers) { describe('shared release launcher safety', () => { const launcherPath = join(repoRoot, 'cli/release-core/launcher.js') - const { createLauncher } = require(launcherPath) + const { createLauncher, resolveDownloadOrigin, DEFAULT_DOWNLOAD_ORIGIN } = + require(launcherPath) + + test('rejects release URL overrides with a path or nonstandard public port', () => { + const warnings: string[] = [] + + expect( + resolveDownloadOrigin('https://freebuff.com/api', { + warn: (message: string) => warnings.push(message), + }), + ).toBe(DEFAULT_DOWNLOAD_ORIGIN) + expect( + resolveDownloadOrigin('https://codebuff.com:8443', { + warn: (message: string) => warnings.push(message), + }), + ).toBe(DEFAULT_DOWNLOAD_ORIGIN) + expect(warnings).toHaveLength(2) + }) + + test('accepts a bare loopback origin with a custom port for local development', () => { + expect(resolveDownloadOrigin('http://127.0.0.1:43127')).toBe( + 'http://127.0.0.1:43127', + ) + }) test('stages an update before stopping the running process', () => { const source = readFileSync(launcherPath, 'utf8') From 8e66703404ecc05f0a5badc4d1cd701cf3e2c6b4 Mon Sep 17 00:00:00 2001 From: Peter <304086198+peter-research@users.noreply.github.com> Date: Wed, 14 Oct 2026 12:58:57 +0200 Subject: [PATCH 4/4] Test release redirect credential and port rejection --- .../__tests__/release/proxy-http-get.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cli/src/__tests__/release/proxy-http-get.test.ts b/cli/src/__tests__/release/proxy-http-get.test.ts index 13858cef41..be515ac560 100644 --- a/cli/src/__tests__/release/proxy-http-get.test.ts +++ b/cli/src/__tests__/release/proxy-http-get.test.ts @@ -334,6 +334,43 @@ for (const helperModule of helperModules) { expect(httpsGetCalls.every((call) => call.agent != null)).toBe(true) }) + test('refuses redirect credentials and unexpected ports', async () => { + for (const location of [ + 'https://user:pass@github.com/releases/freebuff.tar.gz', + 'https://github.com:444/releases/freebuff.tar.gz', + ]) { + let callCount = 0 + const { createReleaseHttpClient } = require(helperModule.path) + const client = createReleaseHttpClient({ + env: {}, + userAgent: 'release-test-agent', + requestTimeout: 2500, + httpsModule: { + get(_options: unknown, callback: (response: Readable) => void) { + callCount += 1 + queueMicrotask(() => { + callback(createResponse(302, { location })) + }) + return { + on() { + return this + }, + setTimeout() { + return this + }, + destroy() {}, + } + }, + }, + }) + + await expect( + client.httpGet('https://codebuff.com/api/releases/download/file'), + ).rejects.toMatchObject({ code: 'EREDIRECT_REFUSED' }) + expect(callCount).toBe(1) + } + }) + test('limits redirect chains', async () => { const { createReleaseHttpClient } = require(helperModule.path) let callCount = 0