[golang filter] Envoy returns 200 OK despite SendLocalReply is done with a different status code in EncodeData callback
Bug in EncodeData callback of golang filter, Envoy returns 200 OK in EncodeData and not the status code set with SendLocalReply
Description: We found out that Envoy does not apply the Status code set with SendLocalReply by a golang filter, but sends 200 OK. This only happens in the EncodeData callback, (i.e. ResponseBody Phase), in all other callbacks the correct status code is returned. I would expect the response code set by the golang plugin in the access_logs and on the client side.
Repro steps: To reproduce you can use the attached shared object and load it in the envoy config like this:
[...]
http_filters:
- name: envoy.filters.http.golang
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config
library_id: simple
library_path: "./simple_filter.so"
plugin_name: "simple_filter"
[...]
we also need a backend that returns some data, otherwise the phase is not executed in the golang filter, I used httpbin:
docker run -p 8080:80 kennethreitz/httpbin
now when we send a request to /bytes/10 (which returns 10 bytes), instead of 403 status we get 200:
curl http://localhost:8443/bytes/10 -v
< HTTP/1.1 200 OK
< server: envoy
< date: Fri, 06 Jun 2025 14:37:39 GMT
< content-type: application/octet-stream
< content-length: 10
< access-control-allow-origin: *
< access-control-allow-credentials: true
< x-envoy-upstream-service-time: 1
<
* end of response with 10 bytes missing
* closing connection #0
curl: (18) end of response with 10 bytes missing
It's also visible in the access_log:
[2025-06-06T14:37:39.126Z] "GET /bytes/10 HTTP/1.1" 200 - 0 0 2 1 "-" "curl/8.13.0" "799404a9-6cbe-4b31-b768-9aa6c71b756c" "localhost:8443" "[::1]:8080"
Complete config:
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 8443
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
http_filters:
- name: envoy.filters.http.golang
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config
library_id: simple
library_path: "./lib/simple_filter.so"
plugin_name: "simple_filter"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: httpbin
clusters:
- name: httpbin
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: httpbin
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: localhost
port_value: 8080
Filter Code: The Golang Plugin does nothing except returning http.StatusForbidden in the EncodeData callback. (Note: I tested it in all other functions, the problem is only in this specific callback)
func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType {
api.LogInfo("DECODE HEADERS")
return api.Continue
}
func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
return api.Continue
}
func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType {
return api.Continue
}
func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType {
return api.Continue
}
func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
f.callbacks.EncoderFilterCallbacks().SendLocalReply(http.StatusForbidden, "", map[string][]string{}, 0, "")
return api.LocalReply
}
func (f *filter) EncodeTrailers(trailers api.ResponseTrailerMap) api.StatusType {
return api.Continue
}
It's not a bug, since response header is already sent to downstream after returing api.Continue in EncodeHeaders.
you could try return api.StopAndBuffer in EncodeHeaders, it will stop sent response header to downstream.
okay thanks for pointing that out.. but does this also apply to upstream? as far as i can tell no, i.e despite calling api.continue no headers arrive at the upstream server when i send a local reply in the body phase... Is it intended to behave that differently?
SendLocalReply will response to client in DecodeHeader/Data, not data sent to upstream.
This issue has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in the next 7 days unless it is tagged "help wanted" or "no stalebot" or other activity occurs. Thank you for your contributions.
This issue has been automatically closed because it has not had activity in the last 37 days. If this issue is still valid, please ping a maintainer and ask them to label it as "help wanted" or "no stalebot". Thank you for your contributions.