Suppressing / Muting the "server" response header in Phoenix

I know I figured this out at one point, but in Phoenix, how can you easily disable the “server: Cowboy” response header? I’d prefer not to disclose extra details about our server and/or app.

I think I see where that header is set inside deps/cowboy/src/cowboy_req.erl… I thought I could take advantage of the register_before_send/2 function

defmodule MyApp.ObfuscationPlug do

  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    register_before_send(conn, fn conn ->
      delete_resp_header(conn, "server")
    end)
  end
end

I tried adding that plug to a couple pipelines, but the callback never gets called – I’ve tried logging from the callback and setting additional headers, but I can’t seem to make it work.

Any ideas? Thanks!

Ack… if I had a nickel for every time I forgot to overwrite the variable with the modified version… I’ve updated the example so the callback is properly firing, but I still can’t get that server thing to go away.

It seems that it is not possible, you can at most overwrite it with your value. Just out of the interest - what for?

def call(conn, _opts), do: conn |> put_resp_header("server", "foo")

Remember to add the plug to your Router.

1 Like

I’ve heard this type of “what for” question many times, and I confess I never understand it, but I’ll try to explain.

The simplest answer is that response headers should be entirely controllable by the app. I don’t have a problem with Cowboy adding in sensible default headers, but it seems silly that it’s impossible to override that behavior.

The more nuanced answer deals with security: by disclosing that header, you are announcing to the world that you are running Cowboy (and likely a Phoenix app). Sniffing lots of sites en mass will quickly yield a nice database of who is running what, and there are tools that will quickly cross-check that with known exploits or will alert you when vulnerabilities are discovered. When I got my ethical hacking certification, we built exactly this type of sniffer and the takeaway was to not disclose extra data unless you need to. As far as I can tell, there is no need to return the “server” header.

8 Likes

I discovered that you can add this type of plug to your endpoint.ex, but it has to appear BEFORE the Router plug (I’m not sure I follow exactly why that’s the case, however).

I filed a feature request for Cowboy: https://github.com/ninenines/cowboy/issues/1440

Only way of doing this is deleting/commenting the line that’s assigning the server name, for this to happen we’ve to modify the Cowboy source code(it’s an open source code, so we’re allowed to do whatever we wish to do, just kidding :slight_smile: )

Take a look at this line https://github.com/ninenines/cowboy/blob/master/src/cowboy_req.erl#L918

Just comment out that line in your deps folder of your local mix project ie., find/look for the that particular line in My_Project/deps/cowboy/src/cowboy_req.erl and recompile the mix project i.e., mix deps.compile. This will remove the server header.

I wish the author of the Cowboy isn’t that stubborn, I also wish he would listen more to the requirements of the users who are using his software… when I last spoke with him(Loïc Hoguin) over IRC it gave me such a vibe. He is a wonderful and hardworking guy though.

Note: This is just a hack though, when you update your deps, you’ve to redo the process again though, but it’ll achieve what you want to do :slight_smile:

In the response to the Github feature I added, he said "You can remove [the Server header] via stream handlers… modules finishing with _h in the tests have some examples of these, but wrapping my head around how to modify this is way beyond my current abilities, so if someone figures it out or can share an example of it, it would help educate the community.

1 Like

I just had a look at modules ending with _h. It’s a lot of code to look into… I’m afraid to say this but it takes a lot of mental/brain power to understand it. He should have provided an example rather than telling us to look into docs/source-code/_h-files. If possible, please convince him to provide an example ie., how to do it using stream handlers… It would really help the community who are using Cowboy. That’s the only advice I can give.

I know the intentions here are good, but it’s not the way open source works. Sure, it would be nice to have an example of this in the docs, but the author has no obligation to it. We get to use Cowboy for free and we build on top of it, thanks to the author’s (and contributors’) work. It’s absolutely ok to propose features and ask for support of course, but it’s also perfectly ok for the maintainer to decline the feature or to not be able/willing to provide support.

I don’t want to sound harsh, I just think we are all getting so much from open source, and maintaining a popular open source project is often thankless work. We cannot expect the maintainer to be the only source of support and information, we need to be willing to do our part.

4 Likes

By the way, a web search for “cowboy stream handlers” points me to this blog post, which is exactly about stripping the Server header.

I hope it helps :slight_smile:

4 Likes

well, I agree… I’ve no intension of offending anyone. If you get that vibe, I apologize. Thanks for the link though.

2 Likes

Blog post you had linked, solved the issues with normal http requests, but when there is a websocket upgrade request I still see the server header, how can we remove header even when there is a connection upgrade?

HTTP/1.1 101 Switching Protocols
cache-control: max-age=0, private, must-revalidate
connection: Upgrade
date: XXXX XXX
sec-websocket-accept: VmcZDLx2TP5aKLFproPFiyryze4=
server: Cowboy
upgrade: websocket

I too am experiencing this issue when there is a websocket upgrade request.

there was a couple of pattern matches missing here is an updated plug

defmodule MyApp.Plugs.StripHeaderHandler do
  @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, body}, state) do
    headers = Map.drop(headers, ["server"])
    :cowboy_stream.info(stream_id, {:response, status, headers, body}, state)
  end

  def info(stream_id, {:switch_protocol, 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, info, state), do: :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

Actually this is a very valid point, why do most servers echo the technology behind them? I wonder if there is any real usage to this beside getting sniffed and fingerprinted…

you need to add stream handlers under prod http

config :web, Web.Endpoint,
  url: [host: "example.com", port: 80],
  http: [stream_handlers: [:cowboy_compress_h, Web.Plugs.StripHeaderHandler, :cowboy_stream_h]],
  check_origin: false,
  orce_ssl: [rewrite_on: [:x_forwarded_proto]],
  cache_static_manifest: "priv/static/cache_manifest.json",
  socket_options: [:inet6],
  force_ssl: [
    host: nil,
    rewrite_on: [:x_forwarded_port, :x_forwarded_proto],
    # maybe true when we use this for real
    hsts: false
  ]

module


defmodule Web.Plugs.StripHeaderHandler do
  @behaviour :cowboy_stream
  @server_name "my server name"
  @websocket_name :websocket

  def info(stream_id, {:response, status, headers, body}, state) do
    #    headers = Map.drop(headers, ["server"])
    headers = Map.put(headers, "server", @server_name)
    :cowboy_stream.info(stream_id, {:response, status, headers, body}, state)
  end

  def info(stream_id, {:inform, status, headers, _body}, state) do
    status = Map.put(headers, "server", @server_name)
    headers = @websocket_name 
    :cowboy_stream.info(stream_id, {:inform, status, headers}, state)
  end

  def info(stream_id, {:switch_protocol, status, headers, _body}, state) do

    status = Map.put(status, "server", @server_name)
    headers = @websocket_name
    :cowboy_stream.info(stream_id, {:switch_protocol, status, headers}, state)
  end

  def info(stream_id, info, state), do: :cowboy_stream.info(stream_id, info, state)

  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