{"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":1529,"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":1511,"sourceCodeEnd":1547,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extproc/ext_proc.go#L1511-L1547","documentation":"Raised by validateBodyResponse (ext_proc.go:1529) when the body mutation in a body response is not the streamed_response variant (GetBodyMutation().GetStreamedResponse() is nil). This ext_proc implementation only supports streamed gRPC body mutations; other body_mutation oneofs are rejected. failProcStream fails the RPC unless failure_mode_allow bypasses it.","triggerScenarios":"Triggered when the server's body response sets body_mutation to the raw bytes variant (BodyMutation.Body) instead of BodyMutation.StreamedResponse, or leaves body_mutation unset (ext_proc.go:1527-1528).","commonSituations":"Server follows the full Envoy ext_proc spec (which allows the plain body oneof) rather than the gRPC-Go subset, a handler that writes body_mutation.body directly, or a copy-paste from an Envoy filter example.","solutions":["On the server, wrap body bytes in BodyMutation.StreamedResponse{Body: bytes} rather than BodyMutation.Body.","Enable failure_mode_allow so the client bypasses ext_proc when the server uses an unsupported mutation shape.","Regenerate/validate your server's BodyResponse construction against the gRPC-Go ext_proc subset.","Add a server-side helper buildBodyMutation(bytes) that always returns the streamed_response oneof."],"exampleFix":"// before: server uses the plain body oneof (unsupported here)\nBodyMutation: &procpb.BodyMutation{BodyMutation: &procpb.BodyMutation_Body{Body: chunk}}\n\n// after: use the streamed_response oneof\nBodyMutation: &procpb.BodyMutation{BodyMutation: &procpb.BodyMutation_StreamedResponse{\n  StreamedResponse: &procpb.StreamedBodyResponse{Body: chunk},\n}}","handlingStrategy":"fallback","validationCode":"// On the ext_proc SERVER: build body mutations using the streamed_response oneof\n// supported by the gRPC-Go ext_proc client.\nfunc buildBodyMutation(chunk []byte) *procpb.BodyMutation {\n    return &procpb.BodyMutation{BodyMutation: &procpb.BodyMutation_StreamedResponse{\n        StreamedResponse: &procpb.StreamedBodyResponse{Body: chunk},\n    }}\n}","typeGuard":null,"tryCatchPattern":"filter.failure_mode_allow = true\nif st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&\n    strings.Contains(st.Message(), \"invalid body mutation in body response\") {\n    // server used an unsupported body_mutation oneof (e.g. plain body bytes)\n}","preventionTips":["Server: always wrap body bytes in BodyMutation.StreamedResponse{Body: ...}; do not use the plain Body oneof.","Provide a single buildBodyMutation helper and use it everywhere.","Enable failure_mode_allow so unsupported mutation shapes degrade gracefully.","Validate server responses against the gRPC-Go ext_proc subset in CI."],"tags":["grpc","xds","extproc","envoy","protocol-violation","body","body-mutation"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}