{"record":{"id":"2a9fcb2613cb967b","repo":"grpc/grpc-go","slug":"external-processor-unexpectedly-set-end-of-stream","errorCode":null,"errorMessage":"external processor unexpectedly set end of stream in response body mutation","messagePattern":"external processor unexpectedly set end of stream in response body mutation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1296,"sourceCode":"\t\t\t// RPC.\n\t\t\tif cs.config.processingModes.responseHeaderMode == modeSend && !cs.responseHeadersReady.HasFired() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor sent response body before sending response headers\"))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If mutated response trailers have been received before receiving the\n\t\t\t// response body message, fail the RPC.\n\t\t\tif cs.config.processingModes.responseTrailerMode == modeSend && cs.responseTrailerReady.HasFired() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor sent response body after response trailers were already processed\"))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tstreamedResp, ok := cs.validateBodyResponse(resp.GetResponseBody())\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif streamedResp.GetEndOfStream() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly set end of stream in response body mutation\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcs.mutatedRespBuffer.Put(streamedResp)\n\n\t\tcase resp.GetResponseHeaders() != nil:\n\t\t\tif cs.config.processingModes.responseHeaderMode == modeSkip {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly sent response headers when response header processing is disabled\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !cs.responseHeaderSent.Load() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor sent response headers before response headers were sent to it\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif cs.responseHeadersReady.HasFired() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly sent duplicate response headers after response headers were already processed\"))\n\t\t\t\treturn\n\t\t\t}\n","sourceCodeStart":1278,"sourceCodeEnd":1314,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/extproc/ext_proc.go#L1278-L1314","documentation":"Raised when the server's streamed response-body mutation sets EndOfStream=true. End of stream on the dataplane is driven by the dataplane itself, not by the ext-proc server, so a server-supplied EOS inside a streamed response body is treated as a protocol violation and fails the proc stream.","triggerScenarios":"validateBodyResponse receives a StreamedBodyResponse with EndOfStream set to true from the server's response_body.body_mutation.streamed_response.","commonSituations":"Server copied an Envoy pattern (where EOS in body mutation is allowed) into a gRPC-extproc implementation without reading the gRPC contract. Server attempts to signal end-of-response via EOS.","solutions":["Stop setting end_of_stream on streamed_response messages from the server.","Let the dataplane drive stream completion; the server should simply stop sending body messages when done.","Enable failure_mode_allow to bypass during remediation."],"exampleFix":"// before (server)\nstream.Send(&pb.ProcessingResponse{ResponseBody:&pb.BodyResponse{Response:&pb.CommonResponse{BodyMutation:&pb.BodyMutation{BodyMutation:&pb.BodyMutation_StreamedResponse{StreamedResponse:&pb.StreamedBodyResponse{Body: chunk, EndOfStream: true}}}}}})\n\n// after\nstream.Send(&pb.ProcessingResponse{ResponseBody:&pb.BodyResponse{Response:&pb.CommonResponse{BodyMutation:&pb.BodyMutation{BodyMutation:&pb.BodyMutation_StreamedResponse{StreamedResponse:&pb.StreamedBodyResponse{Body: chunk}}}}}})","handlingStrategy":"validation","validationCode":"// Server: never set EndOfStream on streamed responses.\nresp := &pb.StreamedBodyResponse{Body: chunk} // EndOfStream left false","typeGuard":null,"tryCatchPattern":"// Client: failure_mode_allow -> bypass.","preventionTips":["Do not copy Envoy's EOS-in-body pattern into a gRPC ext-proc server.","Let the dataplane drive end-of-stream."],"tags":["extproc","protocol-violation","response-body","end-of-stream","server-bug"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}