From 09f5f0ba349835e2f21f8f41da22a8409eaad583 Mon Sep 17 00:00:00 2001 From: JT <45159255+TameTheGame@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:21:27 -0400 Subject: [PATCH] feat: limit request body size Wrap every request body in http.MaxBytesReader so oversized bodies are cut off before they are read completely. The limit defaults to 10 MiB and can be changed with GOTIFY_SERVER_MAX_BODY_BYTES (0 disables it). --- config/config.go | 3 +++ config/keys.go | 1 + gotify-server.env.example | 6 ++++++ router/router.go | 6 ++++++ router/router_test.go | 17 +++++++++++++++++ 5 files changed, 33 insertions(+) diff --git a/config/config.go b/config/config.go index f539d2619..6fec8f86a 100644 --- a/config/config.go +++ b/config/config.go @@ -50,6 +50,7 @@ type Server struct { Cors Cors TrustedProxies []string SecureCookie bool + MaxBodyBytes int } type Database struct { @@ -111,6 +112,7 @@ func Get() (*Configuration, []FutureLog) { Stream: Stream{ PingPeriodSeconds: 45, }, + MaxBodyBytes: 10 * 1024 * 1024, }, Database: Database{ Dialect: "sqlite3", @@ -174,6 +176,7 @@ func Get() (*Configuration, []FutureLog) { add(parseList(&c.Server.TrustedProxies, EnvServerTrustedProxies)) add(parseBool(&c.Server.SecureCookie, EnvServerSecureCookie)) + add(parseInt(&c.Server.MaxBodyBytes, EnvServerMaxBodyBytes)) add(parseString(&c.Database.Dialect, EnvDatabaseDialect)) add(parseString(&c.Database.Connection, EnvDatabaseConnection)) diff --git a/config/keys.go b/config/keys.go index b828885a9..18be3af77 100644 --- a/config/keys.go +++ b/config/keys.go @@ -24,6 +24,7 @@ const ( EnvServerCorsAllowHeaders = "GOTIFY_SERVER_CORS_ALLOWHEADERS" EnvServerTrustedProxies = "GOTIFY_SERVER_TRUSTEDPROXIES" EnvServerSecureCookie = "GOTIFY_SERVER_SECURECOOKIE" + EnvServerMaxBodyBytes = "GOTIFY_SERVER_MAX_BODY_BYTES" EnvDatabaseDialect = "GOTIFY_DATABASE_DIALECT" EnvDatabaseConnection = "GOTIFY_DATABASE_CONNECTION" EnvDefaultUserName = "GOTIFY_DEFAULTUSER_NAME" diff --git a/gotify-server.env.example b/gotify-server.env.example index aff3ffbce..2343a6499 100644 --- a/gotify-server.env.example +++ b/gotify-server.env.example @@ -133,6 +133,12 @@ # Type: boolean # GOTIFY_SERVER_SECURECOOKIE=false +# Maximum size in bytes of a request body. Requests with a larger body are +# rejected. Set to 0 to disable the limit. +# +# Type: number +# GOTIFY_SERVER_MAX_BODY_BYTES=10485760 + # Allowed origins (regex) for cross-origin requests. Setting any CORS_* value # enables CORS handling. # diff --git a/router/router.go b/router/router.go index 89a72ca41..7f71ffaeb 100644 --- a/router/router.go +++ b/router/router.go @@ -43,6 +43,12 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co g.Use(accessLogger(), gin.Recovery(), gerror.Handler(), location.Default()) g.NoRoute(gerror.NotFound()) + if conf.Server.MaxBodyBytes > 0 { + g.Use(func(ctx *gin.Context) { + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, int64(conf.Server.MaxBodyBytes)) + }) + } + if conf.Server.SSL.Enabled && conf.Server.SSL.RedirectToHTTPS { g.Use(func(ctx *gin.Context) { if ctx.Request.TLS != nil { diff --git a/router/router_test.go b/router/router_test.go index 9ef4d040f..d4dbb8a89 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -43,6 +43,7 @@ func (s *IntegrationSuite) BeforeTest(string, string) { s.db.GormDatabase, &model.VersionInfo{Version: "1.0.0", BuildDate: "2018-02-20-17:30:47", Commit: "asdasds"}, &config.Configuration{ + Server: config.Server{MaxBodyBytes: 1024}, PassStrength: 5, LocalAuthEnabled: true, OIDC: config.OIDC{IDPName: "Company XYZ SSO"}, @@ -361,6 +362,22 @@ func (s *IntegrationSuite) TestSendMessage() { assert.Equal(s.T(), token.ID, msg.ApplicationID) } +func (s *IntegrationSuite) TestMaxBodyBytes() { + s.db.User(2).AppWithToken(1, "Aapp") + + // 1024 bytes, exactly the configured limit. + req := s.newRequest("POST", "message", `{"message":"`+strings.Repeat("a", 1010)+`"}`) + req.Header.Add("X-Gotify-Key", "Aapp") + res, err := client.Do(req) + assert.Nil(s.T(), err) + assert.Equal(s.T(), 200, res.StatusCode) + + // 1025 bytes, one byte over the limit. + req = s.newRequest("POST", "message", `{"message":"`+strings.Repeat("a", 1011)+`"}`) + req.Header.Add("X-Gotify-Key", "Aapp") + doRequestAndExpect(s.T(), req, 413, `{"error":"Request Entity Too Large", "errorCode":413, "errorDescription":"http: request body too large"}`) +} + func (s *IntegrationSuite) TestPluginLoadFail_expectPanic() { db := testdb.NewDBWithDefaultUser(s.T()) defer db.Close()