{"record":{"id":"5c28e879caeec084","repo":"hyperledger/fabric","slug":"form-contains-too-many-parts","errorCode":null,"errorMessage":"form contains too many parts","messagePattern":"form contains too many parts","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"orderer/common/channelparticipation/restapi.go","lineNumber":442,"sourceCode":"func (h *HTTPHandler) multipartFormDataBodyToBlock(params map[string]string, req *http.Request, resp http.ResponseWriter) *cb.Block {\n\tboundary := params[\"boundary\"]\n\treader := multipart.NewReader(\n\t\thttp.MaxBytesReader(resp, req.Body, int64(h.config.MaxRequestBodySize)),\n\t\tboundary,\n\t)\n\tform, err := reader.ReadForm(2 * int64(h.config.MaxRequestBodySize))\n\tif err != nil {\n\t\th.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, \"cannot read form from request body\"))\n\t\treturn nil\n\t}\n\n\tif _, exist := form.File[FormDataConfigBlockKey]; !exist {\n\t\th.sendResponseJsonError(resp, http.StatusBadRequest, errors.Errorf(\"form does not contains part key: %s\", FormDataConfigBlockKey))\n\t\treturn nil\n\t}\n\n\tif len(form.File) != 1 || len(form.Value) != 0 {\n\t\th.sendResponseJsonError(resp, http.StatusBadRequest, errors.New(\"form contains too many parts\"))\n\t\treturn nil\n\t}\n\n\tfileHeader := form.File[FormDataConfigBlockKey][0]\n\tfile, err := fileHeader.Open()\n\tif err != nil {\n\t\th.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, \"cannot open file part %s from request body\", FormDataConfigBlockKey))\n\t\treturn nil\n\t}\n\n\tblockBytes, err := io.ReadAll(file)\n\tif err != nil {\n\t\th.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, \"cannot read file part %s from request body\", FormDataConfigBlockKey))\n\t\treturn nil\n\t}\n\n\tblock := &cb.Block{}\n\terr = proto.Unmarshal(blockBytes, block)","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/common/channelparticipation/restapi.go#L424-L460","documentation":"This error is returned by the channel participation REST API when a POST /channel join request's multipart/form-data body contains extra parts beyond exactly one config-block file part and zero value parts. The endpoint is strict: the form must have exactly one file field named 'config-block' and no additional form values, otherwise the request is rejected with HTTP 400. It exists to prevent ambiguous joins where a client sends conflicting or unexpected payload fields.","triggerScenarios":"POSTing to /participation/v1/channels/{channel}/join (serveJoin) with multipart form data that has more than one file part, any additional non-file form fields (form.Value non-empty), or extra file fields alongside the 'config-block' part.","commonSituations":"Clients auto-adding CSRF or metadata fields to the form; submitting both a genesis block and a config-update file; curl scripts appending extra -F parameters (e.g. a stray submit=1 field); UI frameworks that append hidden inputs alongside the file input.","solutions":["Remove all form fields except the single 'config-block' file part from the request","Ensure the field is sent as a file part (multipart file), not a plain value field, and that no other value parts are present","If using curl, keep only: curl -F 'config-block=@genesis.block' and delete other -F/--form-string options","Inspect the client framework for automatically appended hidden fields and disable them for this request"],"exampleFix":"// before\ncurl -X POST .../join -F 'config-block=@genesis.block' -F 'submit=1'\n// after\ncurl -X POST .../join -F 'config-block=@genesis.block'","handlingStrategy":"validation","validationCode":"// Go: validate the form before sending\nw := multipart.NewWriter(&buf)\nif len(extraValues) != 0 || len(extraFiles) != 0 {\n    return errors.New(\"join request must contain only one 'config-block' file part and no value parts\")\n}\nfw, _ := w.CreateFormFile(\"config-block\", \"genesis.block\")\nfw.Write(blockBytes)\nw.Close()","typeGuard":"func isJoinFormValid(fileCount, valueCount int) bool {\n    return fileCount == 1 && valueCount == 0\n}","tryCatchPattern":null,"preventionTips":["Send exactly one file part named 'config-block' and nothing else","Disable auto-added hidden fields (CSRF tokens, submit buttons) for this request","Test the request with curl -F 'config-block=@genesis.block' before wiring UI code"],"tags":["http","fabric","multipart-form","request-validation"],"backgroundTag":"multipart-form-invalid","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}