Repository navigation
fix(branding): bound the favicon fetch timeout - #1656
SiriusTL666 wants to merge 1 commit into
Conversation
GetPicByUrl used http.Get, which goes through http.DefaultClient. The zero value of http.Client has no timeout, so a branding URL pointing at a host that is slow or blackholed blocks the handler goroutine until the OS gives up. /favicon.ico calls this for every anonymous request, so an unreachable favicon or square icon URL accumulates stuck goroutines and connections with no upper bound — a cheap way to exhaust the server from the outside. - Use a package-level client with a 15s timeout, matching the existing remoteVersion fetch in dashboard_service. - Treat non-200 responses as empty instead of returning the error body as if it were image data. - Cap the read at 5MB so a misconfigured or hostile URL cannot exhaust memory. - Add tests covering success, error status, the timeout, and that the client keeps a non-zero timeout.
|
A couple of notes that may make review easier. Is the favicon URL attacker-controlled, or only operator-controlled? Only the operator sets
So a single unresponsive or hanging favicon host turns each anonymous Why I also capped the body at 5MB
Why I did not fix the SSRF surface An anonymous request can make the server issue a GET to whatever host the favicon URL names, including internal addresses. The clean fix is to fetch and cache the icon when the setting is saved, instead of per request — but that changes when the fetch happens (and what happens on failure at save time), so it is a behaviour decision rather than a bug fix. Flagging it rather than deciding unilaterally. Verification recap
Happy to adjust scope, split the change, or add an issue first if that is the preferred workflow. |
Description
htmltext.GetPicByUrlusedhttp.Get, which goes throughhttp.DefaultClient. The zero value ofhttp.Clienthas no timeout, so a slow or blackholed host blocks the calling goroutine until the OS gives up.This is reachable by anyone, not just an admin action.
internal/router/ui.goserves/favicon.icolike this:The branding URL is operator-configured, but the route is unauthenticated. If that URL points at an unresponsive host, every anonymous request to
/favicon.icoparks a handler goroutine and a connection with no upper bound — a cheap way to exhaust the server from the outside without a valid account.Changes
picClientwith a 15s timeout, matching the existingremoteVersionfetch indashboard_service.go.io.LimitReader, so a misconfigured or hostile URL cannot exhaust memory.Verification
Built and tested with Go 1.25.0:
Four new tests: success body, non-200 handling, the timeout itself, and a guard asserting the client keeps a non-zero timeout.
The timeout test was verified in both directions — it is a real regression test, not just a green check:
http.Get(url)(before)--- FAIL: TestGetPicByUrlTimesOut (10.00s)— "did not honour the client timeout"picClient.Get(url)(after)--- PASS: TestGetPicByUrlTimesOut (0.10s)Notes
No existing issue covers this; I found it while auditing Apache projects for unbounded outbound requests. Happy to open an issue first if that is preferred.
A related observation I did not change, since it is a behaviour question rather than a bug: this route also means an anonymous visitor can make the server issue an outbound request to whatever host the favicon URL names (SSRF surface for internal addresses). Fetching and caching the icon at configuration time, instead of per request, would remove both the latency and the SSRF surface. Out of scope here — happy to follow up if maintainers want it.