From 355b31a6a9c3ffbf0cc3128f7f18d431ea894615 Mon Sep 17 00:00:00 2001 From: famfo Date: Tue, 12 May 2026 03:38:02 +0200 Subject: [PATCH] fix(federation): verify host header of requsets --- routers/api/v1/activitypub/reqsignature.go | 21 +++++++++++++++++-- .../api_federation_httpsig_test.go | 15 +++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go index 100378b5f1..470d6443aa 100644 --- a/routers/api/v1/activitypub/reqsignature.go +++ b/routers/api/v1/activitypub/reqsignature.go @@ -5,6 +5,7 @@ package activitypub import ( "net/http" + "net/url" "forgejo.org/modules/log" "forgejo.org/modules/setting" @@ -15,6 +16,22 @@ import ( ) func verifyHTTPSignature(ctx app_context.APIContext) (authenticated bool, err error) { + // Verify that the canonical domain for federation is accessed regardless of + // if signatures are actually checked or not. + + // This check does not cover the instance actor but that does not contain any + // potentially private information which should only be accessed via the + // canonical instance domain. + appURL, err := url.Parse(setting.AppURL) + if err != nil { + return false, err + } + + if ctx.Req.Host != appURL.Host { + log.Error("%s", ctx.Req.Host) + return false, nil + } + if !setting.Federation.SignatureEnforced { return true, nil } @@ -48,9 +65,9 @@ func ReqHTTPSignature() func(ctx *app_context.APIContext) { return func(ctx *app_context.APIContext) { if authenticated, err := verifyHTTPSignature(*ctx); err != nil { log.Warn("verifyHttpSignature failed: %v", err) - ctx.Error(http.StatusBadRequest, "reqSignature", "request signature verification failed") + ctx.Error(http.StatusBadRequest, "reqSignature", "request verification failed") } else if !authenticated { - ctx.Error(http.StatusForbidden, "reqSignature", "request signature verification failed") + ctx.Error(http.StatusForbidden, "reqSignature", "request verification failed") } } } diff --git a/tests/integration/api_federation_httpsig_test.go b/tests/integration/api_federation_httpsig_test.go index 431676acee..24c3f63680 100644 --- a/tests/integration/api_federation_httpsig_test.go +++ b/tests/integration/api_federation_httpsig_test.go @@ -32,7 +32,7 @@ func TestFederationHttpSigValidation(t *testing.T) { onApplicationRun(t, func(t *testing.T, u *url.URL) { userID := 2 - userURL := fmt.Sprintf("%sapi/v1/activitypub/user-id/%d", u, userID) + userURL := fmt.Sprintf("%sapi/v1/activitypub/user-id/%d", setting.AppURL, userID) user1 := unittest.AssertExistsAndLoadBean(t, &user.User{ID: 1}) @@ -94,6 +94,17 @@ func TestFederationHttpSigValidation(t *testing.T) { req := NewRequest(t, "GET", userURL) MakeRequest(t, req, http.StatusOK) }) + + // Request with wrong host header, this should always be checked, even if + // signature validation is disabled. + + // The URL passed from the testing function does point to the correct + // instance but with the wrong host (localhost vs 127.0.0.1). + wrongHostUserURL := fmt.Sprintf("%sapi/v1/activitypub/user-id/%d", u, userID) + t.Run("WrongHostHeader", func(t *testing.T) { + req := NewRequest(t, "GET", wrongHostUserURL) + MakeRequest(t, req, http.StatusForbidden) + }) }) } @@ -141,7 +152,7 @@ func TestFederationAllRoutesCovered(t *testing.T) { } resp := MakeRequest(t, req, http.StatusBadRequest) - assert.Contains(t, resp.Body.String(), "request signature verification failed") + assert.Contains(t, resp.Body.String(), "request verification failed") } }