defp headers do
referrer_policy = Config.get([:http_security, :referrer_policy])
+ report_uri = Config.get([:http_security, :report_uri])
- [
+ headers = [
{"x-xss-protection", "1; mode=block"},
{"x-permitted-cross-domain-policies", "none"},
{"x-frame-options", "DENY"},
{"x-download-options", "noopen"},
{"content-security-policy", csp_string() <> ";"}
]
+
+ if report_uri do
+ report_group = %{
+ "group" => "csp-endpoint",
+ "max-age" => 10_886_400,
+ "endpoints" => [
+ %{"url" => report_uri}
+ ]
+ }
+
+ headers ++ [{"reply-to", Jason.encode!(report_group)}]
+ else
+ headers
+ end
end
defp csp_string do
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
static_url = Pleroma.Web.Endpoint.static_url()
websocket_url = Pleroma.Web.Endpoint.websocket_url()
+ report_uri = Config.get([:http_security, :report_uri])
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
"script-src 'self'"
end
- [
+ main_part = [
"default-src 'none'",
"base-uri 'self'",
"frame-ancestors 'none'",
"font-src 'self'",
"manifest-src 'self'",
connect_src,
- script_src,
- if scheme == "https" do
- "upgrade-insecure-requests"
- end
+ script_src
]
+
+ report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: []
+
+ insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: []
+
+ (main_part ++ report ++ insecure)
|> Enum.join("; ")
end
alias Pleroma.Config
alias Plug.Conn
- test "it sends CSP headers when enabled", %{conn: conn} do
- Config.put([:http_security, :enabled], true)
-
- conn =
- conn
- |> get("/api/v1/instance")
-
- refute Conn.get_resp_header(conn, "x-xss-protection") == []
- refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
- refute Conn.get_resp_header(conn, "x-frame-options") == []
- refute Conn.get_resp_header(conn, "x-content-type-options") == []
- refute Conn.get_resp_header(conn, "x-download-options") == []
- refute Conn.get_resp_header(conn, "referrer-policy") == []
- refute Conn.get_resp_header(conn, "content-security-policy") == []
- end
+ describe "http security enabled" do
+ setup do
+ enabled = Config.get([:http_securiy, :enabled])
- test "it does not send CSP headers when disabled", %{conn: conn} do
- Config.put([:http_security, :enabled], false)
+ Config.put([:http_security, :enabled], true)
- conn =
- conn
- |> get("/api/v1/instance")
+ on_exit(fn ->
+ Config.put([:http_security, :enabled], enabled)
+ end)
- assert Conn.get_resp_header(conn, "x-xss-protection") == []
- assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
- assert Conn.get_resp_header(conn, "x-frame-options") == []
- assert Conn.get_resp_header(conn, "x-content-type-options") == []
- assert Conn.get_resp_header(conn, "x-download-options") == []
- assert Conn.get_resp_header(conn, "referrer-policy") == []
- assert Conn.get_resp_header(conn, "content-security-policy") == []
- end
+ :ok
+ end
- test "it sends STS headers when enabled", %{conn: conn} do
- Config.put([:http_security, :enabled], true)
- Config.put([:http_security, :sts], true)
+ test "it sends CSP headers when enabled", %{conn: conn} do
+ conn = get(conn, "/api/v1/instance")
- conn =
- conn
- |> get("/api/v1/instance")
+ refute Conn.get_resp_header(conn, "x-xss-protection") == []
+ refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
+ refute Conn.get_resp_header(conn, "x-frame-options") == []
+ refute Conn.get_resp_header(conn, "x-content-type-options") == []
+ refute Conn.get_resp_header(conn, "x-download-options") == []
+ refute Conn.get_resp_header(conn, "referrer-policy") == []
+ refute Conn.get_resp_header(conn, "content-security-policy") == []
+ end
- refute Conn.get_resp_header(conn, "strict-transport-security") == []
- refute Conn.get_resp_header(conn, "expect-ct") == []
- end
+ test "it sends STS headers when enabled", %{conn: conn} do
+ Config.put([:http_security, :sts], true)
- test "it does not send STS headers when disabled", %{conn: conn} do
- Config.put([:http_security, :enabled], true)
- Config.put([:http_security, :sts], false)
+ conn = get(conn, "/api/v1/instance")
- conn =
- conn
- |> get("/api/v1/instance")
+ refute Conn.get_resp_header(conn, "strict-transport-security") == []
+ refute Conn.get_resp_header(conn, "expect-ct") == []
+ end
- assert Conn.get_resp_header(conn, "strict-transport-security") == []
- assert Conn.get_resp_header(conn, "expect-ct") == []
- end
+ test "it does not send STS headers when disabled", %{conn: conn} do
+ Config.put([:http_security, :sts], false)
+
+ conn = get(conn, "/api/v1/instance")
+
+ assert Conn.get_resp_header(conn, "strict-transport-security") == []
+ assert Conn.get_resp_header(conn, "expect-ct") == []
+ end
+
+ test "referrer-policy header reflects configured value", %{conn: conn} do
+ conn = get(conn, "/api/v1/instance")
+
+ assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
- test "referrer-policy header reflects configured value", %{conn: conn} do
- Config.put([:http_security, :enabled], true)
+ Config.put([:http_security, :referrer_policy], "no-referrer")
- conn =
- conn
- |> get("/api/v1/instance")
+ conn =
+ build_conn()
+ |> get("/api/v1/instance")
- assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
+ assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
+ end
- Config.put([:http_security, :referrer_policy], "no-referrer")
+ test "it sends `report-to` & `report-uri` CSP response headers" do
+ conn =
+ build_conn()
+ |> get("/api/v1/instance")
- conn =
- build_conn()
- |> get("/api/v1/instance")
+ [csp] = Conn.get_resp_header(conn, "content-security-policy")
- assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
+ assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;|
+
+ [reply_to] = Conn.get_resp_header(conn, "reply-to")
+
+ assert reply_to ==
+ "{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}"
+ end
+ end
+
+ test "it does not send CSP headers when disabled", %{conn: conn} do
+ enabled = Config.get([:http_securiy, :enabled])
+
+ Config.put([:http_security, :enabled], false)
+
+ on_exit(fn ->
+ Config.put([:http_security, :enabled], enabled)
+ end)
+
+ conn = get(conn, "/api/v1/instance")
+
+ assert Conn.get_resp_header(conn, "x-xss-protection") == []
+ assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
+ assert Conn.get_resp_header(conn, "x-frame-options") == []
+ assert Conn.get_resp_header(conn, "x-content-type-options") == []
+ assert Conn.get_resp_header(conn, "x-download-options") == []
+ assert Conn.get_resp_header(conn, "referrer-policy") == []
+ assert Conn.get_resp_header(conn, "content-security-policy") == []
end
end