Problem
safeFetch calls net.fetch(url, { signal }) without setting redirect. Electron's net.fetch follows HTTP redirects (3xx responses) by default, and the SSRF guard only runs once against the initial URL. A public-facing HTTPS endpoint controlled by an attacker can respond with 302 Location: http://127.0.0.1/admin (or any internal host), and the second request goes through completely unchecked — the guard is bypassed. Protocol downgrade (HTTPS → HTTP) is also allowed for the same reason.
Location
File: packages/desktop/src/main/net/safe-fetch.ts:12-27
export async function safeFetch(url: string, opts: SafeFetchOptions = {}): Promise<Buffer> {
const parsed = new URL(url);
if (parsed.protocol !== 'https:') throw new Error('HTTPS required');
await assertNotPrivateHost(parsed.hostname);
// ...
const res = await net.fetch(url, { signal: controller.signal }); // ← follows redirects blindly
Fix Approach
Pick one of:
- Preferred: pass
redirect: 'error' to net.fetch so any 3xx response throws. This is the simplest and safest option; image URLs generally should not redirect through untrusted hops.
- If redirects must be supported: pass
redirect: 'manual', inspect the Location header, validate the new URL against the HTTPS-only check AND assertNotPrivateHost, then re-issue the fetch. Bound the redirect chain to a small maximum (e.g. 3 hops) to prevent redirect loops.
Verification
- Run
pnpm check — must pass.
- Unit test: mock
net.fetch to return a 302 Location: http://127.0.0.1/ response; confirm safeFetch rejects.
- Unit test: mock a 302 → public HTTPS redirect; confirm it either rejects (option 1) or re-validates (option 2).
Context
- WG: Observability & DX (security-critical)
- Priority: Medium (same attack surface as the DNS rebinding bug)
- Estimated effort: 30-60 minutes
Problem
safeFetchcallsnet.fetch(url, { signal })without settingredirect. Electron'snet.fetchfollows HTTP redirects (3xx responses) by default, and the SSRF guard only runs once against the initial URL. A public-facing HTTPS endpoint controlled by an attacker can respond with302 Location: http://127.0.0.1/admin(or any internal host), and the second request goes through completely unchecked — the guard is bypassed. Protocol downgrade (HTTPS → HTTP) is also allowed for the same reason.Location
File:
packages/desktop/src/main/net/safe-fetch.ts:12-27Fix Approach
Pick one of:
redirect: 'error'tonet.fetchso any 3xx response throws. This is the simplest and safest option; image URLs generally should not redirect through untrusted hops.redirect: 'manual', inspect theLocationheader, validate the new URL against the HTTPS-only check ANDassertNotPrivateHost, then re-issue the fetch. Bound the redirect chain to a small maximum (e.g. 3 hops) to prevent redirect loops.Verification
pnpm check— must pass.net.fetchto return a302 Location: http://127.0.0.1/response; confirmsafeFetchrejects.Context