{"record":{"id":"4273cb6fc2c8cc26","repo":"grpc/grpc-go","slug":"external-processor-sent-response-headers-before-re","errorCode":null,"errorMessage":"external processor sent response headers before response headers were sent to it","messagePattern":"external processor sent response headers before response headers were sent to it","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1307,"sourceCode":"\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\n\t\t\theader := resp.GetResponseHeaders()\n\t\t\t// Check if the status in the header response is CONTINUE; if not, fail\n\t\t\t// the stream.\n\t\t\tif status := header.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor returned unexpected status %v for response headers, expected %v\", status, v3procservicepb.CommonResponse_CONTINUE))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err = cs.applyMutations(header.GetResponse().GetHeaderMutation(), cs.responseHeader); err != nil {\n\t\t\t\tcs.failProcStream(err)\n\t\t\t\treturn\n\t\t\t}","sourceCodeStart":1289,"sourceCodeEnd":1325,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/extproc/ext_proc.go#L1289-L1325","documentation":"Raised when the server sends response_headers before the client has sent response headers to it (responseHeaderSent is false). The ext-proc server must only mutate headers it has first received; responding with header mutations unsolicited is out of order and fails the proc stream.","triggerScenarios":"The server emits a response_headers ProcessingResponse without first receiving a response_headers ProcessingRequest from the client.","commonSituations":"Server pushes response-header mutations proactively (e.g. to inject headers) instead of waiting for the client's response_headers message. Misuse of a server-side-initiated pattern that the protocol does not allow.","solutions":["Make the server wait to receive response_headers from the client before sending any response_headers response.","If the intent is request-side injection, do it in a request_headers response instead.","Enable failure_mode_allow to bypass during remediation."],"exampleFix":"// before: server pushes response headers unsolicited\nfunc Process(stream) {\n  stream.Send(&pb.ProcessingResponse{ResponseHeaders: inject})\n}\n\n// after: wait for client to send response headers first\nfunc Process(stream) {\n  for {\n    req, _ := stream.Recv()\n    if req.GetResponseHeaders() != nil {\n      stream.Send(&pb.ProcessingResponse{ResponseHeaders: mutate(req)})\n    }\n  }\n}","handlingStrategy":"validation","validationCode":"// Server: only send response_headers after receiving response_headers from client.\nseenRespHeaders := false\n// for each req from Recv: if req.GetResponseHeaders()!=nil { seenRespHeaders=true }\n// only emit response_headers resp when seenRespHeaders","typeGuard":null,"tryCatchPattern":"// Client: failure_mode_allow -> bypass.","preventionTips":["Never push response_headers unsolicited; always react to the client's message.","For request-side injection, use a request_headers response."],"tags":["extproc","protocol-violation","ordering","response-headers","server-bug"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}