feat: retarget the auto-updater from Gitea to GitHub

The source repo moved to github.com/debont80/AeroFetch; the in-app
updater and release links still pointed at the old self-hosted Gitea
instance. Repoint RELEASE_API at the GitHub REST API, replace the
single-host download trust check with an allowlist (github.com plus
its release-asset CDN hosts, which GitHub 302s downloads through —
mirrors the existing FFMPEG_TRUSTED_HOSTS pattern), and add the
User-Agent header GitHub's API requires.

No bridge: installs on a Gitea-pointed build (<=0.7.3) won't
auto-discover releases published to GitHub and need one manual
download, matching the precedent set by the earlier releases-repo
migration.
This commit is contained in:
2026-07-16 12:47:09 -04:00
parent 79ab94248d
commit 44230c757e
8 changed files with 81 additions and 46 deletions
+22 -16
View File
@@ -69,35 +69,41 @@ describe('isAllowedLoginUrl (audit T4)', () => {
})
})
// --- app-updater: isTrustedDownloadUrl — host pin across redirect hops -------
// --- app-updater: isTrustedDownloadUrl — host allowlist across redirect hops -
describe('isTrustedDownloadUrl (app-updater host pin)', () => {
const onHost =
'https://gitea.netbird.zimspace.uk:5938/debont80/AeroFetch/releases/download/v1/AeroFetch-Setup.exe'
describe('isTrustedDownloadUrl (app-updater host allowlist)', () => {
const onHost = 'https://github.com/debont80/AeroFetch/releases/download/v1/AeroFetch-Setup.exe'
const onCdnHost =
'https://release-assets.githubusercontent.com/github-production-release-asset/123/abc'
it('accepts https URLs on the exact update host (incl. port)', () => {
it('accepts https URLs on github.com and its asset CDN hosts', () => {
expect(isTrustedDownloadUrl(onHost)).toBe(true)
expect(isTrustedDownloadUrl(onCdnHost)).toBe(true)
expect(
isTrustedDownloadUrl(
'https://objects.githubusercontent.com/github-production-release-asset/x'
)
).toBe(true)
})
it('rejects a different host — the redirect/MITM bounce vector', () => {
expect(isTrustedDownloadUrl('https://evil.example/AeroFetch-Setup.exe')).toBe(false)
})
it('rejects the right hostname on the wrong port', () => {
// host comparison includes the port, so :443 (default) is not the same origin
expect(isTrustedDownloadUrl('https://gitea.netbird.zimspace.uk/AeroFetch-Setup.exe')).toBe(
false
)
it('rejects a lookalike subdomain', () => {
expect(isTrustedDownloadUrl('https://github.com.evil.example/x.exe')).toBe(false)
})
it('rejects plain http even on the update host', () => {
expect(isTrustedDownloadUrl('http://gitea.netbird.zimspace.uk:5938/x.exe')).toBe(false)
it('rejects a trusted hostname on an unexpected port', () => {
expect(isTrustedDownloadUrl('https://github.com:1234/x.exe')).toBe(false)
})
it('is not fooled by the trusted host placed in the userinfo', () => {
expect(isTrustedDownloadUrl('https://gitea.netbird.zimspace.uk:5938@evil.example/x.exe')).toBe(
false
)
it('rejects plain http even on a trusted host', () => {
expect(isTrustedDownloadUrl('http://github.com/x.exe')).toBe(false)
})
it('is not fooled by a trusted host placed in the userinfo', () => {
expect(isTrustedDownloadUrl('https://github.com@evil.example/x.exe')).toBe(false)
})
it('rejects unparseable input', () => {
+17 -1
View File
@@ -73,7 +73,8 @@ import type { WebContents } from 'electron'
// --- helpers ------------------------------------------------------------------
const HOST = 'https://gitea.netbird.zimspace.uk:5938'
const HOST = 'https://github.com'
const CDN_HOST = 'https://release-assets.githubusercontent.com'
const ASSET = `${HOST}/debont80/AeroFetch/releases/download/v9.9.9/AeroFetch-Setup-9.9.9.exe`
const INSTALLER = Buffer.from('FAKE-INSTALLER-BYTES-'.repeat(64))
const GOOD_SHA = createHash('sha256').update(INSTALLER).digest('hex')
@@ -213,6 +214,21 @@ describe('downloadAppUpdate (CL4 behavior pins)', () => {
expect(madeRequests[1]!.redirectsFollowed).toBe(1)
})
it('follows a redirect to the allowlisted asset CDN host (github.com → githubusercontent.com)', async () => {
requestScripts.push(checksumOk(`${GOOD_SHA} AeroFetch-Setup-9.9.9.exe\n`))
requestScripts.push((req) => {
req.emit('redirect', 302, 'GET', `${CDN_HOST}/github-production-release-asset/abc123`)
const res = new FakeResponse(200, { 'content-length': String(INSTALLER.length) })
req.emit('response', res)
res.emit('data', INSTALLER)
res.emit('end')
})
const { wc } = fakeWc()
const r = await downloadAppUpdate(wc, ASSET)
expect(r.ok).toBe(true)
expect(madeRequests[1]!.redirectsFollowed).toBe(1)
})
it('refuses a checksum-fetch redirect off the trusted host', async () => {
requestScripts.push((req) => {
req.emit('redirect', 302, 'GET', 'https://evil.example/x.sha256')