{"record":{"id":"6e5660e077e03e0c","repo":"grpc/grpc-go","slug":"external-processor-returned-invalid-body-mutation","errorCode":null,"errorMessage":"external processor returned invalid body mutation in body response","messagePattern":"external processor returned invalid body mutation in body response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1363,"sourceCode":"\t\t\tif err = cs.applyMutations(trailer.GetHeaderMutation(), cs.responseTrailers); err != nil {\n\t\t\t\tcs.failProcStream(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Signal that the response trailer is modified and ready to be sent to\n\t\t\t// the client.\n\t\t\tcs.fireResponseTrailerReady()\n\t\t}\n\t}\n}\n\nfunc (cs *clientStream) validateBodyResponse(bodyResp *v3procservicepb.BodyResponse) (*v3procservicepb.StreamedBodyResponse, bool) {\n\tif status := bodyResp.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {\n\t\tcs.failProcStream(fmt.Errorf(\"external processor returned unexpected status %v for body response, expected %v\", status, v3procservicepb.CommonResponse_CONTINUE))\n\t\treturn nil, false\n\t}\n\tstreamedResp := bodyResp.GetResponse().GetBodyMutation().GetStreamedResponse()\n\tif streamedResp == nil {\n\t\tcs.failProcStream(fmt.Errorf(\"external processor returned invalid body mutation in body response\"))\n\t\treturn nil, false\n\t}\n\tif streamedResp.GetGrpcMessageCompressed() {\n\t\tcs.failProcStream(fmt.Errorf(\"external processor returned compressed grpc message which is not supported\"))\n\t\treturn nil, false\n\t}\n\treturn streamedResp, true\n}\n\nfunc (cs *clientStream) applyMutations(mutation *v3procservicepb.HeaderMutation, md metadata.MD) error {\n\tif mutation == nil {\n\t\treturn nil\n\t}\n\tif err := cs.config.mutationRules.ApplyAdditions(mutation.GetSetHeaders(), md); err != nil {\n\t\treturn err\n\t}\n\treturn cs.config.mutationRules.ApplyRemovals(mutation.GetRemoveHeaders(), md)\n}","sourceCodeStart":1345,"sourceCodeEnd":1381,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/extproc/ext_proc.go#L1345-L1381","documentation":"validateBodyResponse extracts the streamed body from the BodyMutation in the body response (ext_proc.go:1361). If BodyMutation or its StreamedResponse field is nil, the filter has no valid chunk to apply and fails the proc stream.","triggerScenarios":"The extproc server sends a body ProcessingResponse where BodyMutation is absent or does not populate the StreamedResponse oneof (e.g. it left BodyMutation at its zero value or set the wrong oneof).","commonSituations":"Server intends to pass the body through unchanged but constructs an empty BodyMutation; server uses the deprecated/cleared mutation variant; protobuf field rename/oneof mismatch between server and client.","solutions":["On the extproc server, always populate bodyMutation.streamed_response (a StreamedBodyResponse with the chunk to forward) when sending a body response.","If the goal is no-op, still echo the received body bytes in StreamedResponse rather than sending an empty mutation.","Confirm the server's protobuf definitions match go-control-plane's envoy.service.ext_proc.v3."],"exampleFix":"// before: body mutation left empty\nresp.Response.BodyMutation = &procservicepb.BodyMutation{}\n\n// after: populate streamed response\nresp.Response.BodyMutation = &procservicepb.BodyMutation{\n    BodyMutation: &procservicepb.BodyMutation_StreamedResponse{\n        StreamedResponse: &procservicepb.StreamedBodyResponse{Chunk: chunk},\n    },\n}","handlingStrategy":"validation","validationCode":"// Server side: ensure StreamedResponse is populated before sending\nif bodyResp.GetResponse().GetBodyMutation().GetStreamedResponse() == nil {\n    bodyResp.Response.BodyMutation = &procservicepb.BodyMutation{BodyMutation: &procservicepb.BodyMutation_StreamedResponse{StreamedResponse: &procservicepb.StreamedBodyResponse{Chunk: chunk}}}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never send a body response with an empty BodyMutation.","Unit-test the server's body-response builder to confirm StreamedResponse is set.","Match the envoy.service.ext_proc.v3 protobuf oneof exactly."],"tags":["extproc","grpc","xds","protocol","go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}