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:
@@ -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', () => {
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user