fix: limit anonymous request body (#5244)

* fix: limit anonymous request body (env ANONYMOUS_REQUEST_BODY_LIMIT_KB = 512)

* fix: allow disabling anonymous request body limit
This commit is contained in:
Seefs
2026-06-05 11:39:29 +08:00
committed by GitHub
parent 83068d115e
commit d2f7f9ee3a
5 changed files with 79 additions and 16 deletions
+1
View File
@@ -136,6 +136,7 @@ func initConstantEnv() {
constant.StreamScannerMaxBufferMB = GetEnvOrDefault("STREAM_SCANNER_MAX_BUFFER_MB", 128) constant.StreamScannerMaxBufferMB = GetEnvOrDefault("STREAM_SCANNER_MAX_BUFFER_MB", 128)
// MaxRequestBodyMB 请求体最大大小(解压后),用于防止超大请求/zip bomb导致内存暴涨 // MaxRequestBodyMB 请求体最大大小(解压后),用于防止超大请求/zip bomb导致内存暴涨
constant.MaxRequestBodyMB = GetEnvOrDefault("MAX_REQUEST_BODY_MB", 128) constant.MaxRequestBodyMB = GetEnvOrDefault("MAX_REQUEST_BODY_MB", 128)
constant.AnonymousRequestBodyLimitKB = GetEnvOrDefault("ANONYMOUS_REQUEST_BODY_LIMIT_KB", 512)
// ForceStreamOption 覆盖请求参数,强制返回usage信息 // ForceStreamOption 覆盖请求参数,强制返回usage信息
constant.ForceStreamOption = GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true) constant.ForceStreamOption = GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
constant.CountToken = GetEnvOrDefaultBool("CountToken", true) constant.CountToken = GetEnvOrDefaultBool("CountToken", true)
+13
View File
@@ -0,0 +1,13 @@
package common
import "github.com/QuantumNous/new-api/constant"
const defaultAnonymousRequestBodyLimitKB = 512
func GetAnonymousRequestBodyLimitBytes() int64 {
limitKB := constant.AnonymousRequestBodyLimitKB
if limitKB < 0 {
limitKB = defaultAnonymousRequestBodyLimitKB
}
return int64(limitKB) << 10
}
+1
View File
@@ -10,6 +10,7 @@ var GetMediaToken bool
var GetMediaTokenNotStream bool var GetMediaTokenNotStream bool
var UpdateTask bool var UpdateTask bool
var MaxRequestBodyMB int var MaxRequestBodyMB int
var AnonymousRequestBodyLimitKB int
var AzureDefaultAPIVersion string var AzureDefaultAPIVersion string
var NotifyLimitCount int var NotifyLimitCount int
var NotificationLimitDurationMinute int var NotificationLimitDurationMinute int
+47
View File
@@ -0,0 +1,47 @@
package middleware
import (
"bytes"
"io"
"net/http"
"github.com/QuantumNous/new-api/common"
"github.com/gin-gonic/gin"
)
func AnonymousRequestBodyLimit() gin.HandlerFunc {
return func(c *gin.Context) {
maxBytes := common.GetAnonymousRequestBodyLimitBytes()
if maxBytes <= 0 || c.Request.Body == nil {
c.Next()
return
}
originalBody := c.Request.Body
limitedBody, err := readAnonymousRequestBody(originalBody, maxBytes)
_ = originalBody.Close()
if err != nil {
if common.IsRequestBodyTooLargeError(err) {
c.AbortWithStatus(http.StatusRequestEntityTooLarge)
return
}
c.AbortWithStatus(http.StatusBadRequest)
return
}
c.Request.Body = io.NopCloser(bytes.NewReader(limitedBody))
c.Request.ContentLength = int64(len(limitedBody))
c.Next()
}
}
func readAnonymousRequestBody(body io.Reader, maxBytes int64) ([]byte, error) {
data, err := io.ReadAll(io.LimitReader(body, maxBytes+1))
if err != nil {
return nil, err
}
if int64(len(data)) > maxBytes {
return nil, common.ErrRequestBodyTooLarge
}
return data, nil
}
+17 -16
View File
@@ -17,9 +17,10 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.Use(gzip.Gzip(gzip.DefaultCompression)) apiRouter.Use(gzip.Gzip(gzip.DefaultCompression))
apiRouter.Use(middleware.BodyStorageCleanup()) // 清理请求体存储 apiRouter.Use(middleware.BodyStorageCleanup()) // 清理请求体存储
apiRouter.Use(middleware.GlobalAPIRateLimit()) apiRouter.Use(middleware.GlobalAPIRateLimit())
anonymousRequestBodyLimit := middleware.AnonymousRequestBodyLimit()
{ {
apiRouter.GET("/setup", controller.GetSetup) apiRouter.GET("/setup", controller.GetSetup)
apiRouter.POST("/setup", controller.PostSetup) apiRouter.POST("/setup", anonymousRequestBodyLimit, controller.PostSetup)
apiRouter.GET("/status", controller.GetStatus) apiRouter.GET("/status", controller.GetStatus)
apiRouter.GET("/uptime/status", controller.GetUptimeKumaStatus) apiRouter.GET("/uptime/status", controller.GetUptimeKumaStatus)
apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels) apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels)
@@ -40,39 +41,39 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.GET("/rankings", middleware.HeaderNavModuleAuth("rankings"), controller.GetRankings) apiRouter.GET("/rankings", middleware.HeaderNavModuleAuth("rankings"), controller.GetRankings)
apiRouter.GET("/verification", middleware.EmailVerificationRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) apiRouter.GET("/verification", middleware.EmailVerificationRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.ResetPassword)
// OAuth routes - specific routes must come before :provider wildcard // OAuth routes - specific routes must come before :provider wildcard
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode) apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
apiRouter.POST("/oauth/email/bind", middleware.CriticalRateLimit(), controller.EmailBind) apiRouter.POST("/oauth/email/bind", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.EmailBind)
// Non-standard OAuth (WeChat, Telegram) - keep original routes // Non-standard OAuth (WeChat, Telegram) - keep original routes
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth) apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
apiRouter.POST("/oauth/wechat/bind", middleware.CriticalRateLimit(), controller.WeChatBind) apiRouter.POST("/oauth/wechat/bind", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.WeChatBind)
apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin) apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin)
apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), controller.TelegramBind) apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), controller.TelegramBind)
// Standard OAuth providers (GitHub, Discord, OIDC, LinuxDO) - unified route // Standard OAuth providers (GitHub, Discord, OIDC, LinuxDO) - unified route
apiRouter.GET("/oauth/:provider", middleware.CriticalRateLimit(), controller.HandleOAuth) apiRouter.GET("/oauth/:provider", middleware.CriticalRateLimit(), controller.HandleOAuth)
apiRouter.GET("/ratio_config", middleware.CriticalRateLimit(), controller.GetRatioConfig) apiRouter.GET("/ratio_config", middleware.CriticalRateLimit(), controller.GetRatioConfig)
apiRouter.POST("/stripe/webhook", controller.StripeWebhook) apiRouter.POST("/stripe/webhook", anonymousRequestBodyLimit, controller.StripeWebhook)
apiRouter.POST("/creem/webhook", controller.CreemWebhook) apiRouter.POST("/creem/webhook", anonymousRequestBodyLimit, controller.CreemWebhook)
apiRouter.POST("/waffo/webhook", controller.WaffoWebhook) apiRouter.POST("/waffo/webhook", anonymousRequestBodyLimit, controller.WaffoWebhook)
// :env separates test vs prod URLs so the operator can register each // :env separates test vs prod URLs so the operator can register each
// in Pancake's matching webhook slot; handler enforces env match. // in Pancake's matching webhook slot; handler enforces env match.
apiRouter.POST("/waffo-pancake/webhook/:env", controller.WaffoPancakeWebhook) apiRouter.POST("/waffo-pancake/webhook/:env", anonymousRequestBodyLimit, controller.WaffoPancakeWebhook)
// Universal secure verification routes // Universal secure verification routes
apiRouter.POST("/verify", middleware.UserAuth(), middleware.CriticalRateLimit(), controller.UniversalVerify) apiRouter.POST("/verify", middleware.UserAuth(), middleware.CriticalRateLimit(), controller.UniversalVerify)
userRoute := apiRouter.Group("/user") userRoute := apiRouter.Group("/user")
{ {
userRoute.POST("/register", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Register) userRoute.POST("/register", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, middleware.TurnstileCheck(), controller.Register)
userRoute.POST("/login", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Login) userRoute.POST("/login", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, middleware.TurnstileCheck(), controller.Login)
userRoute.POST("/login/2fa", middleware.CriticalRateLimit(), controller.Verify2FALogin) userRoute.POST("/login/2fa", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.Verify2FALogin)
userRoute.POST("/passkey/login/begin", middleware.CriticalRateLimit(), controller.PasskeyLoginBegin) userRoute.POST("/passkey/login/begin", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.PasskeyLoginBegin)
userRoute.POST("/passkey/login/finish", middleware.CriticalRateLimit(), controller.PasskeyLoginFinish) userRoute.POST("/passkey/login/finish", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.PasskeyLoginFinish)
//userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog) //userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog)
userRoute.GET("/logout", controller.Logout) userRoute.GET("/logout", controller.Logout)
userRoute.POST("/epay/notify", controller.EpayNotify) userRoute.POST("/epay/notify", anonymousRequestBodyLimit, controller.EpayNotify)
userRoute.GET("/epay/notify", controller.EpayNotify) userRoute.GET("/epay/notify", controller.EpayNotify)
userRoute.GET("/groups", controller.GetUserGroups) userRoute.GET("/groups", controller.GetUserGroups)
@@ -176,10 +177,10 @@ func SetApiRouter(router *gin.Engine) {
} }
// Subscription payment callbacks (no auth) // Subscription payment callbacks (no auth)
apiRouter.POST("/subscription/epay/notify", controller.SubscriptionEpayNotify) apiRouter.POST("/subscription/epay/notify", anonymousRequestBodyLimit, controller.SubscriptionEpayNotify)
apiRouter.GET("/subscription/epay/notify", controller.SubscriptionEpayNotify) apiRouter.GET("/subscription/epay/notify", controller.SubscriptionEpayNotify)
apiRouter.GET("/subscription/epay/return", controller.SubscriptionEpayReturn) apiRouter.GET("/subscription/epay/return", controller.SubscriptionEpayReturn)
apiRouter.POST("/subscription/epay/return", controller.SubscriptionEpayReturn) apiRouter.POST("/subscription/epay/return", anonymousRequestBodyLimit, controller.SubscriptionEpayReturn)
optionRoute := apiRouter.Group("/option") optionRoute := apiRouter.Group("/option")
optionRoute.Use(middleware.RootAuth()) optionRoute.Use(middleware.RootAuth())
{ {