@@ -12,17 +12,26 @@ defmodule FlickWeb.Router do
1212 plug :put_root_layout , html: { FlickWeb.Layouts , :root }
1313 plug :protect_from_forgery
1414
15- # Tailwind uses SVG data URLs for icons,
16- # so we need to allow them with `img-src`.
17- # https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
15+ # Our Content Security Policy. Notes:
16+ #
17+ # - `img-src` allows `data:` URLs because Tailwind uses SVG data URLs for
18+ # icons.
19+ # - `style-src` allows `'unsafe-inline'` to avoid web console issues with
20+ # Phoenix Storybook.
1821 #
19- # To avoid web console issues with Phoenix Storybook we've added
20- # `style-src 'self' 'unsafe-inline'` which feels unfortunate and
21- # might be reconsidered.
22+ # `put_csp_nonce` below augments this static policy with a per-request nonce
23+ # so the inline Plausible analytics `<script>` in the root layout is allowed
24+ # without opening `script-src` up to `'unsafe-inline'`. We keep the full
25+ # policy here (rather than building it entirely in the plug) so Sobelow's
26+ # `Config.CSP` check can still verify a CSP is present.
27+ #
28+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
2229 plug :put_secure_browser_headers , % {
2330 "content-security-policy" =>
2431 "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' https://plausible.io; connect-src 'self' https://plausible.io"
2532 }
33+
34+ plug :put_csp_nonce
2635 end
2736
2837 pipeline :admin do
@@ -33,6 +42,19 @@ defmodule FlickWeb.Router do
3342 plug :accepts , [ "json" ]
3443 end
3544
45+ # Generates a per-request nonce, assigns it to the conn (so the root layout can
46+ # stamp it onto the inline Plausible `<script>`), and splices it into the
47+ # `script-src` directive of the CSP header set by `put_secure_browser_headers`.
48+ defp put_csp_nonce ( conn , _opts ) do
49+ nonce = 18 |> :crypto . strong_rand_bytes ( ) |> Base . encode64 ( )
50+
51+ conn
52+ |> assign ( :csp_nonce , nonce )
53+ |> update_resp_header ( "content-security-policy" , "" , fn csp ->
54+ String . replace ( csp , "script-src " , "script-src 'nonce-#{ nonce } ' " )
55+ end )
56+ end
57+
3658 scope "/" do
3759 storybook_assets ( )
3860 end
0 commit comments