diff --git a/lib/who_need_help_web/security_headers.ex b/lib/who_need_help_web/security_headers.ex index 963ee70..41b997f 100644 --- a/lib/who_need_help_web/security_headers.ex +++ b/lib/who_need_help_web/security_headers.ex @@ -9,15 +9,14 @@ defmodule WhoNeedHelpWeb.SecurityHeaders do |> Application.fetch_env!(:map_tile_url) |> origin() - websocket_origins = - [ - origin(%URI{scheme: "ws", host: conn.host, port: conn.port}), - origin(%URI{scheme: "wss", host: conn.host, port: default_https_port(conn)}) - ] - |> Enum.reject(&is_nil/1) - |> Enum.uniq() + websocket_origin = + case conn.scheme do + :http -> origin(%URI{scheme: "ws", host: conn.host, port: conn.port}) + :https -> origin(%URI{scheme: "wss", host: conn.host, port: conn.port}) + _other -> nil + end - connect_sources = sources(["'self'", tile_origin | websocket_origins]) + connect_sources = sources(["'self'", tile_origin, websocket_origin]) image_sources = sources(["'self'", "data:", "blob:", tile_origin]) policy = @@ -39,9 +38,6 @@ defmodule WhoNeedHelpWeb.SecurityHeaders do put_resp_header(conn, "content-security-policy", policy) end - defp default_https_port(%Plug.Conn{port: 443}), do: 443 - defp default_https_port(_conn), do: nil - defp sources(values), do: values |> Enum.reject(&is_nil/1) |> Enum.uniq() |> Enum.join(" ") diff --git a/test/who_need_help_web/controllers/page_controller_test.exs b/test/who_need_help_web/controllers/page_controller_test.exs index 4a2cbb2..d74232e 100644 --- a/test/who_need_help_web/controllers/page_controller_test.exs +++ b/test/who_need_help_web/controllers/page_controller_test.exs @@ -13,6 +13,8 @@ defmodule WhoNeedHelpWeb.PageControllerTest do refute content_security_policy =~ "script-src 'self' 'unsafe-inline'" assert content_security_policy =~ "frame-ancestors 'none'" assert content_security_policy =~ "https://tile.openstreetmap.org" + assert content_security_policy =~ "ws://www.example.com" + refute content_security_policy =~ "wss://www.example.com" assert get_resp_header(conn, "permissions-policy") == [ "geolocation=(self), camera=(), microphone=(), payment=(), usb=()" @@ -38,6 +40,20 @@ defmodule WhoNeedHelpWeb.PageControllerTest do ) == 1 end + test "HTTPS content security policy permits only secure WebSockets", %{conn: conn} do + conn = + conn + |> Map.put(:scheme, :https) + |> Map.put(:host, "help.example") + |> Map.put(:port, 443) + |> WhoNeedHelpWeb.SecurityHeaders.put_content_security_policy([]) + + [content_security_policy] = get_resp_header(conn, "content-security-policy") + + assert content_security_policy =~ "wss://help.example" + refute content_security_policy =~ "ws://help.example" + end + test "Phoenix logs filter authentication and handover secrets" do assert Phoenix.Logger.filter_values(%{ "password" => "not-logged",