From f1d012000ea6f3394249ee937e88c6cdb081cfe2 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Thu, 25 Jun 2026 16:38:57 -0400 Subject: [PATCH] fix(bskyweb): validate avatar URL scheme and bound fetch timeout - Add SSRF defense: reject avatar URLs not starting with https:// - Add per-fetch timeout (5s) instead of relying on http.DefaultClient - Document WebInviteWalletHero handler as unauthenticated by design --- bskyweb/cmd/bskyweb/invitepass.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bskyweb/cmd/bskyweb/invitepass.go b/bskyweb/cmd/bskyweb/invitepass.go index 74c7f603f4..2c113143ff 100644 --- a/bskyweb/cmd/bskyweb/invitepass.go +++ b/bskyweb/cmd/bskyweb/invitepass.go @@ -147,6 +147,10 @@ func (srv *Server) WebInviteWalletJWT(c echo.Context) error { return c.JSON(http.StatusOK, echo.Map{"jwt": jwt}) } +// WebInviteWalletHero renders a hero image for Google Wallet. Unauthenticated by +// design - Google fetches this image from their renderer. The DID + theme combo +// is unguessable and the data exposed (avatar + handle, both public) is already +// on the user's profile. func (srv *Server) WebInviteWalletHero(c echo.Context) error { did := c.QueryParam("did") theme := CoerceTheme(c.QueryParam("theme")) @@ -252,8 +256,13 @@ func (srv *Server) fetchProfile(ctx context.Context, did string) (handle string, } handle = pv.Handle if pv.Avatar != nil { + // SSRF defense: only fetch https:// URLs + if !strings.HasPrefix(*pv.Avatar, "https://") { + return handle, nil, nil + } req, _ := http.NewRequestWithContext(ctx, http.MethodGet, *pv.Avatar, nil) - resp, herr := http.DefaultClient.Do(req) + client := &http.Client{Timeout: 5 * time.Second} + resp, herr := client.Do(req) if herr == nil { defer resp.Body.Close() if resp.StatusCode == 200 {