diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d1d0156a0..7beac896a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -57,7 +57,7 @@ lint-elixir:
     - export EXITVALUE=0
     - mix format --check-formatted --dry-run || export EXITVALUE=1
     - mix credo --strict -a || export EXITVALUE=1
-    - mix sobelow --config --skip || export EXITVALUE=1
+    - mix sobelow --config || export EXITVALUE=1
     - exit $EXITVALUE
 
 lint-front:
diff --git a/.sobelow-conf b/.sobelow-conf
index 06cca8933..fc0940908 100644
--- a/.sobelow-conf
+++ b/.sobelow-conf
@@ -7,6 +7,6 @@
   format: "txt",
   out: "",
   threshold: "medium",
-  ignore: ["Config.HTTPS"],
+  ignore: ["Config.HTTPS", "Config.CSP"],
   ignore_files: ["config/dev.1.secret.exs", "config/dev.2.secret.exs", "config/dev.3.secret.exs", "config/dev.secret.exs", "config/e2e.secret.exs", "config/prod.secret.exs", "config/test.secret.exs"]
 ]
diff --git a/.sobelow-skips b/.sobelow-skips
index 8a0f398e7..9e3133f2f 100644
--- a/.sobelow-skips
+++ b/.sobelow-skips
@@ -1,2 +1,2 @@
 
-AACA51671C4B3C803ACBCA3FADE84CDE
\ No newline at end of file
+752C0E897CA81ACD81F4BB215FA5F8E4
\ No newline at end of file
diff --git a/config/config.exs b/config/config.exs
index 23f3cb4ee..413108fb8 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -215,6 +215,24 @@ config :mobilizon, :maps,
     type: :openstreetmap
   ]
 
+config :mobilizon, :http_security,
+  enabled: true,
+  sts: false,
+  sts_max_age: 31_536_000,
+  csp_policy: [
+    script_src: [],
+    style_src: [],
+    connect_src: [],
+    font_src: [],
+    img_src: ["*.tile.openstreetmap.org"],
+    manifest_src: [],
+    media_src: [],
+    object_src: [],
+    frame_src: [],
+    frame_ancestors: []
+  ],
+  referrer_policy: "same-origin"
+
 config :mobilizon, :anonymous,
   participation: [
     allowed: true,
diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue
index 0187fe512..86304b3de 100644
--- a/js/src/views/Event/Edit.vue
+++ b/js/src/views/Event/Edit.vue
@@ -33,6 +33,7 @@
 
         <b-field horizontal :label="$t('Starts on…')" class="begins-on-field">
           <b-datetimepicker
+            class="datepicker starts-on"
             :placeholder="$t('Type or select a date…')"
             icon="calendar-today"
             :locale="$i18n.locale"
@@ -45,6 +46,7 @@
 
         <b-field horizontal :label="$t('Ends on…')">
           <b-datetimepicker
+            class="datepicker ends-on"
             :placeholder="$t('Type or select a date…')"
             icon="calendar-today"
             :locale="$i18n.locale"
@@ -381,6 +383,12 @@ h2.subtitle {
   }
 }
 
+.datepicker {
+  ::v-deep .dropdown-menu {
+    z-index: 200;
+  }
+}
+
 section {
   & > .container {
     padding: 2rem 1.5rem;
diff --git a/lib/web/endpoint.ex b/lib/web/endpoint.ex
index 8a0601be7..24ba70034 100644
--- a/lib/web/endpoint.ex
+++ b/lib/web/endpoint.ex
@@ -6,6 +6,7 @@ defmodule Mobilizon.Web.Endpoint do
   use Absinthe.Phoenix.Endpoint
 
   plug(Mobilizon.Web.Plugs.SetLocalePlug)
+  plug(Mobilizon.Web.Plugs.HTTPSecurityPlug)
 
   # For e2e tests
   if Application.get_env(:mobilizon, :sql_sandbox) do
@@ -75,4 +76,9 @@ defmodule Mobilizon.Web.Endpoint do
   )
 
   plug(Mobilizon.Web.Router)
+
+  @spec websocket_url :: String.t()
+  def websocket_url do
+    String.replace_leading(url(), "http", "ws")
+  end
 end
diff --git a/lib/web/plugs/http_security_plug.ex b/lib/web/plugs/http_security_plug.ex
new file mode 100644
index 000000000..8a5f2161d
--- /dev/null
+++ b/lib/web/plugs/http_security_plug.ex
@@ -0,0 +1,116 @@
+# Portions of this file are derived from Pleroma:
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
+  @moduledoc """
+  A plug to setup some HTTP security-related headers, like CSP
+  """
+
+  alias Mobilizon.Config
+  import Plug.Conn
+
+  require Logger
+
+  def init(opts), do: opts
+
+  def call(conn, _options) do
+    if Config.get([:http_security, :enabled]) do
+      conn
+      |> merge_resp_headers(headers())
+      |> maybe_send_sts_header(Config.get([:http_security, :sts]))
+    else
+      conn
+    end
+  end
+
+  defp headers do
+    referrer_policy = Config.get([:http_security, :referrer_policy])
+
+    [
+      {"referrer-policy", referrer_policy},
+      {"content-security-policy", csp_string()}
+    ]
+  end
+
+  @static_csp_rules [
+    "default-src 'none'",
+    "base-uri 'self'",
+    "manifest-src 'self'"
+  ]
+
+  @csp_start [Enum.join(@static_csp_rules, ";") <> ";"]
+
+  defp csp_string do
+    scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
+    static_url = Mobilizon.Web.Endpoint.static_url()
+    websocket_url = Mobilizon.Web.Endpoint.websocket_url()
+
+    img_src =
+      ["img-src 'self' data: blob: "] ++ Config.get([:http_security, :csp_policy, :img_src])
+
+    media_src = ["media-src 'self' "] ++ Config.get([:http_security, :csp_policy, :media_src])
+
+    connect_src =
+      ["connect-src 'self' blob: ", static_url, ?\s, websocket_url] ++
+        Config.get([:http_security, :csp_policy, :connect_src])
+
+    script_src =
+      if Config.get(:env) == :dev do
+        "script-src 'self' 'unsafe-eval' 'unsafe-inline' "
+      else
+        "script-src 'self' "
+      end
+
+    script_src = [script_src] ++ Config.get([:http_security, :csp_policy, :script_src])
+
+    style_src =
+      if Config.get(:env) == :dev do
+        "style-src 'self' 'unsafe-inline' "
+      else
+        "style-src 'self' "
+      end
+
+    style_src = [style_src] ++ Config.get([:http_security, :csp_policy, :style_src])
+
+    font_src = ["font-src 'self' "] ++ Config.get([:http_security, :csp_policy, :font_src])
+
+    frame_src = if Config.get(:env) == :dev, do: "frame-src 'self' ", else: "frame-src 'none' "
+    frame_src = [frame_src] ++ Config.get([:http_security, :csp_policy, :frame_src])
+
+    frame_ancestors =
+      if Config.get(:env) == :dev, do: "frame-ancestors 'self' ", else: "frame-ancestors 'none' "
+
+    frame_ancestors =
+      [frame_ancestors] ++ Config.get([:http_security, :csp_policy, :frame_ancestors])
+
+    insecure = if scheme == "https", do: "upgrade-insecure-requests"
+
+    @csp_start
+    |> add_csp_param(script_src)
+    |> add_csp_param(style_src)
+    |> add_csp_param(connect_src)
+    |> add_csp_param(img_src)
+    |> add_csp_param(media_src)
+    |> add_csp_param(font_src)
+    |> add_csp_param(frame_src)
+    |> add_csp_param(frame_ancestors)
+    |> add_csp_param(insecure)
+    |> :erlang.iolist_to_binary()
+  end
+
+  defp add_csp_param(csp_iodata, nil), do: csp_iodata
+
+  defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
+
+  defp maybe_send_sts_header(conn, true) do
+    max_age_sts = Config.get([:http_security, :sts_max_age])
+
+    merge_resp_headers(conn, [
+      {"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"}
+    ])
+  end
+
+  defp maybe_send_sts_header(conn, _), do: conn
+end
diff --git a/lib/web/router.ex b/lib/web/router.ex
index 75aaa638d..96fafe635 100644
--- a/lib/web/router.ex
+++ b/lib/web/router.ex
@@ -4,12 +4,6 @@ defmodule Mobilizon.Web.Router do
   """
   use Mobilizon.Web, :router
 
-  @csp if Application.fetch_env!(:mobilizon, :env) != :dev,
-         do: "default-src 'self';",
-         else:
-           "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
-  @headers %{"content-security-policy" => @csp}
-
   pipeline :graphql do
     #    plug(:accepts, ["json"])
     plug(Mobilizon.Web.Auth.Pipeline)
@@ -36,7 +30,7 @@ defmodule Mobilizon.Web.Router do
 
   pipeline :activity_pub_and_html do
     plug(:accepts, ["html", "activity-json"])
-    plug(:put_secure_browser_headers, @headers)
+    plug(:put_secure_browser_headers)
 
     plug(Cldr.Plug.AcceptLanguage,
       cldr_backend: Mobilizon.Cldr
@@ -44,7 +38,7 @@ defmodule Mobilizon.Web.Router do
   end
 
   pipeline :atom_and_ical do
-    plug(:put_secure_browser_headers, @headers)
+    plug(:put_secure_browser_headers)
     plug(:accepts, ["atom", "ics", "html"])
   end
 
@@ -56,7 +50,7 @@ defmodule Mobilizon.Web.Router do
     )
 
     plug(:accepts, ["html"])
-    plug(:put_secure_browser_headers, @headers)
+    plug(:put_secure_browser_headers)
   end
 
   pipeline :remote_media do