My updated version, removing the server
header without breaking websocket initialisation. In the :switch_protocol
headers are in the 2nd element of the tuple. Tested with cowboy 2.12, plug_cowboy 2.7.1, phoenix 1.7.14, phoenix_live_dashboard 0.8.4.
defmodule MyApiWeb.Plugs.StripServerHeader do
@moduledoc """
Strip the Cowboy's `server` header from HTTP responses to minimise fingerprinting.
"""
@behaviour :cowboy_stream
def info(stream_id, {:response, status, headers, body}, state) do
headers = Map.drop(headers, ["server"])
:cowboy_stream.info(stream_id, {:response, status, headers, body}, state)
end
def info(stream_id, {:inform, status, headers}, state) do
headers = Map.drop(headers, ["server"])
:cowboy_stream.info(stream_id, {:inform, status, headers}, state)
end
def info(stream_id, {:switch_protocol, headers, status, body}, state) do
headers = Map.drop(headers, ["server"])
:cowboy_stream.info(stream_id, {:switch_protocol, headers, status, body}, state)
end
def info(stream_id, info, state) do
# IO.inspect(info, label: "strip-header-info")
:cowboy_stream.info(stream_id, info, state)
end
def init(stream_id, req, opts), do: :cowboy_stream.init(stream_id, req, opts)
def data(stream_id, is_fin, info, state),
do: :cowboy_stream.data(stream_id, is_fin, info, state)
def early_error(stream_id, reason, partial_req, resp, opts),
do: :cowboy_stream.early_error(stream_id, reason, partial_req, resp, opts)
def terminate(stream_id, reason, state), do: :cowboy_stream.terminate(stream_id, reason, state)
end
The endpoint configuration example:
config :ov_api, MyApiWeb.Endpoint,
url: [host: host],
http: [
ip: {127, 0, 0, 1},
port: port,
stream_handlers: [:cowboy_compress_h, MyApiWeb.Plugs.StripServerHeader, :cowboy_stream_h]
],
# ...