Added Permissions-Policy header with browsing-topics=() to suppress console warnings about unrecognized Privacy Sandbox API features. Header is now applied in all environments (dev and prod) while other security headers remain production-only. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
60 lines
2 KiB
Elixir
60 lines
2 KiB
Elixir
defmodule ToweropsWeb.Plugs.SecurityHeaders do
|
|
@moduledoc """
|
|
Adds security headers to all responses in production.
|
|
|
|
Headers added:
|
|
- Content-Security-Policy: Restricts resource loading to prevent XSS
|
|
- X-Frame-Options: Prevents clickjacking attacks
|
|
- X-Content-Type-Options: Prevents MIME type sniffing
|
|
- Referrer-Policy: Controls referrer information leakage
|
|
- Permissions-Policy: Disables browsing-topics API
|
|
|
|
These headers are only applied in production to avoid interfering with
|
|
development tools like LiveReload.
|
|
"""
|
|
|
|
import Plug.Conn
|
|
|
|
def init(opts), do: opts
|
|
|
|
def call(conn, _opts) do
|
|
conn
|
|
|> put_resp_header("permissions-policy", "browsing-topics=()")
|
|
|> maybe_add_prod_headers()
|
|
end
|
|
|
|
defp maybe_add_prod_headers(conn) do
|
|
if Application.get_env(:towerops, :env) == :prod do
|
|
conn
|
|
|> put_resp_header("content-security-policy", csp_header())
|
|
|> put_resp_header("x-frame-options", "SAMEORIGIN")
|
|
|> put_resp_header("x-content-type-options", "nosniff")
|
|
|> put_resp_header("referrer-policy", "strict-origin-when-cross-origin")
|
|
else
|
|
conn
|
|
end
|
|
end
|
|
|
|
# Content Security Policy
|
|
# - default-src 'self': Only load resources from same origin by default
|
|
# - script-src: Allow inline scripts (needed for LiveView), eval, and self
|
|
# - style-src: Allow inline styles (needed for Tailwind), unsafe-hashes, and self
|
|
# - img-src: Allow images from self, data URIs, and https
|
|
# - font-src: Allow fonts from self and data URIs
|
|
# - connect-src: Allow WebSocket connections for LiveView
|
|
# - frame-ancestors: Same as X-Frame-Options (SAMEORIGIN)
|
|
defp csp_header do
|
|
Enum.join(
|
|
[
|
|
"default-src 'self'",
|
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
|
|
"style-src 'self' 'unsafe-inline' 'unsafe-hashes'",
|
|
"img-src 'self' data: https:",
|
|
"font-src 'self' data:",
|
|
"connect-src 'self' ws: wss:",
|
|
"frame-ancestors 'self'"
|
|
],
|
|
"; "
|
|
)
|
|
end
|
|
end
|