Repository: phoenixframework/phoenix
Pull request #4677 (2022-02-02 18:57:33)

Update security practices
* Include same_site: "lax". Albeit the default,
  it is not the default in Safari

* x-xss-protection has been removed in most browsers
  in favor of Content Security Policy

* cross-origin-window-policy was not actualy implemented
  in browsers and SameSite handles more cases

josevalim: @GriffinMB, can you please double check if those look good for you?
josevalim: Thanks! The same_site was added to generated new app code, so we can only test the generated code itself and I am not sure that will add much. :)

Patch:
diff --git a/installer/templates/phx_web/endpoint.ex b/installer/templates/phx_web/endpoint.ex
index 2acb2880c..1f42e1d2f 100644
--- a/installer/templates/phx_web/endpoint.ex
+++ b/installer/templates/phx_web/endpoint.ex
@@ -7,7 +7,8 @@ defmodule <%= @endpoint_module %> do
   @session_options [
     store: :cookie,
     key: "_<%= @web_app_name %>_key",
-    signing_salt: "<%= @signing_salt %>"
+    signing_salt: "<%= @signing_salt %>",
+    same_site: "Lax"
   ]
 
   <%= if !(@dashboard || @live) do %><%= "# " %><% end %>socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
diff --git a/lib/phoenix/controller.ex b/lib/phoenix/controller.ex
index e8155d84b..067fa5048 100644
--- a/lib/phoenix/controller.ex
+++ b/lib/phoenix/controller.ex
@@ -1081,20 +1081,17 @@ defmodule Phoenix.Controller do
 
   It sets the following headers:
 
+    * `referrer-policy` - only send origin on cross origin requests
     * `x-frame-options` - set to SAMEORIGIN to avoid clickjacking
       through iframes unless in the same origin
     * `x-content-type-options` - set to nosniff. This requires
       script and style tags to be sent with proper content type
-    * `x-xss-protection` - set to "1; mode=block" to improve XSS
-      protection on both Chrome and IE
     * `x-download-options` - set to noopen to instruct the browser
       not to open a download directly in the browser, to avoid
       HTML files rendering inline and accessing the security
       context of the application (like critical domain cookies)
     * `x-permitted-cross-domain-policies` - set to none to restrict
       Adobe Flash Player’s access to data
-    * `cross-origin-window-policy` - set to deny to avoid window
-      control attacks
 
   A custom headers map may also be given to be merged with defaults.
   It is recommended for custom header keys to be in lowercase, to avoid sending
@@ -1113,12 +1110,14 @@ defmodule Phoenix.Controller do
   end
   defp put_secure_defaults(conn) do
     merge_resp_headers(conn, [
-      {"x-frame-options", "SAMEORIGIN"},
-      {"x-xss-protection", "1; mode=block"},
+      # Below is the default from November 2020 but not yet in Safari as in Jan/2022.
+      # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
+      {"referrer-policy", "strict-origin-when-cross-origin"},
       {"x-content-type-options", "nosniff"},
+      # Applies only to Internet Explorer, can safely be removed in the future.
       {"x-download-options", "noopen"},
-      {"x-permitted-cross-domain-policies", "none"},
-      {"cross-origin-window-policy", "deny"}
+      {"x-frame-options", "SAMEORIGIN"},
+      {"x-permitted-cross-domain-policies", "none"}
     ])
   end
 


Test patch:
diff --git a/test/phoenix/controller/controller_test.exs b/test/phoenix/controller/controller_test.exs
index 97ecf081b..2ebe628e6 100644
--- a/test/phoenix/controller/controller_test.exs
+++ b/test/phoenix/controller/controller_test.exs
@@ -589,19 +589,15 @@ defmodule Phoenix.Controller.ControllerTest do
   test "put_secure_browser_headers/2" do
     conn = conn(:get, "/") |> put_secure_browser_headers()
     assert get_resp_header(conn, "x-frame-options") == ["SAMEORIGIN"]
-    assert get_resp_header(conn, "x-xss-protection") == ["1; mode=block"]
     assert get_resp_header(conn, "x-content-type-options") == ["nosniff"]
     assert get_resp_header(conn, "x-download-options") == ["noopen"]
     assert get_resp_header(conn, "x-permitted-cross-domain-policies") == ["none"]
-    assert get_resp_header(conn, "cross-origin-window-policy") == ["deny"]
 
     custom_headers = %{"x-frame-options" => "custom", "foo" => "bar"}
     conn = conn(:get, "/") |> put_secure_browser_headers(custom_headers)
     assert get_resp_header(conn, "x-frame-options") == ["custom"]
-    assert get_resp_header(conn, "x-xss-protection") == ["1; mode=block"]
     assert get_resp_header(conn, "x-download-options") == ["noopen"]
     assert get_resp_header(conn, "x-permitted-cross-domain-policies") == ["none"]
-    assert get_resp_header(conn, "cross-origin-window-policy") == ["deny"]
     assert get_resp_header(conn, "foo") == ["bar"]
   end