fix(security): limit API key creation body to 4 KiB (INPUT-2) (#3978)

Wrap req.Body with http.MaxBytesReader before io.ReadAll in
CreateAPIKey. Requests with bodies larger than MaxAPIKeyBodySize
(4 KiB) now return HTTP 413 instead of buffering arbitrary data.

Add the MaxAPIKeyBodySize constant, update the Swagger @Failure
annotation to document 413, and add a unit test.

Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
This commit is contained in:
Ramkumar Chinchani
2026-04-18 10:39:08 -07:00
committed by GitHub
parent 35c29b95e4
commit eadc9b65ed
6 changed files with 44 additions and 4 deletions
+3 -1
View File
@@ -22,7 +22,9 @@ const (
MaxManifestDigestQueryTags = (8192 - 2048) / (len("tag=") + 128 + 1)
// MaxManifestBodySize is the maximum number of bytes accepted for a manifest PUT request body.
// OCI manifest JSON is always small metadata; 4 MiB is well above any realistic manifest.
MaxManifestBodySize = 4 * 1024 * 1024
MaxManifestBodySize = 4 * 1024 * 1024
// MaxAPIKeyBodySize is the maximum number of bytes accepted for an API-key creation request body.
MaxAPIKeyBodySize = 4 * 1024
BlobUploadUUID = "Blob-Upload-UUID"
DefaultMediaType = "application/json"
BinaryMediaType = "application/octet-stream"
+9 -3
View File
@@ -2342,15 +2342,21 @@ func (rh *RouteHandler) GetAPIKeys(resp http.ResponseWriter, req *http.Request)
// @Success 201 {string} string "created"
// @Failure 400 {string} string "bad request"
// @Failure 401 {string} string "unauthorized"
// @Failure 413 {string} string "request entity too large"
// @Failure 500 {string} string "internal server error"
// @Router /zot/auth/apikey [post].
func (rh *RouteHandler) CreateAPIKey(resp http.ResponseWriter, req *http.Request) {
var payload APIKeyPayload
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(http.MaxBytesReader(resp, req.Body, constants.MaxAPIKeyBodySize))
if err != nil {
rh.c.Log.Error().Msg("failed to read request body")
resp.WriteHeader(http.StatusInternalServerError)
var mbe *http.MaxBytesError
if errors.As(err, &mbe) {
resp.WriteHeader(http.StatusRequestEntityTooLarge)
} else {
rh.c.Log.Error().Msg("failed to read request body")
resp.WriteHeader(http.StatusInternalServerError)
}
return
}
+16
View File
@@ -1604,6 +1604,22 @@ func TestRoutes(t *testing.T) {
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
})
Convey("CreateAPIKey body exceeds MaxAPIKeyBodySize returns 413", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("test")
ctx := userAc.DeriveContext(context.Background())
oversized := make([]byte, constants.MaxAPIKeyBodySize+1)
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(oversized))
response := httptest.NewRecorder()
rthdlr.CreateAPIKey(response, request)
resp := response.Result()
defer resp.Body.Close()
So(resp.StatusCode, ShouldEqual, http.StatusRequestEntityTooLarge)
})
Convey("CreateAPIKey bad request body", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("test")
+6
View File
@@ -1099,6 +1099,12 @@ const docTemplate = `{
"type": "string"
}
},
"413": {
"description": "request entity too large",
"schema": {
"type": "string"
}
},
"500": {
"description": "internal server error",
"schema": {
+6
View File
@@ -1091,6 +1091,12 @@
"type": "string"
}
},
"413": {
"description": "request entity too large",
"schema": {
"type": "string"
}
},
"500": {
"description": "internal server error",
"schema": {
+4
View File
@@ -1004,6 +1004,10 @@ paths:
description: unauthorized
schema:
type: string
"413":
description: request entity too large
schema:
type: string
"500":
description: internal server error
schema: