This commit is contained in:
Christian Wesselhoeft 2026-04-21 18:24:47 +02:00 committed by GitHub
commit d0e12cc23d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 4 deletions

View file

@ -45,6 +45,18 @@ defmodule LiveSvelte do
doc: "Class to apply to the Svelte component",
examples: ["my-class", "my-class another-class"]
attr :csp_nonce, :string,
default: nil,
doc: "A Content-Security-Policy nonce for the generated <script> and <style> tags"
attr :csp_script_nonce, :string,
default: nil,
doc: "A Content-Security-Policy nonce for the generated <script> tag"
attr :csp_style_nonce, :string,
default: nil,
doc: "A Content-Security-Policy nonce for the generated <style> tag"
attr :ssr, :boolean,
default: true,
doc: "Whether to render the component via NodeJS on the server",
@ -124,6 +136,8 @@ defmodule LiveSvelte do
streams_diff = calculate_streams_diff(assigns, init or dead)
csp_attrs = if nonce = assigns.csp_nonce, do: [nonce: nonce]
assigns =
assigns
|> assign(:init, init)
@ -134,9 +148,17 @@ defmodule LiveSvelte do
|> assign(:use_diff, use_diff)
|> assign(:props_diff, props_diff)
|> assign(:streams_diff, streams_diff)
|> assign(
:csp_script_attrs,
csp_attrs || if(nonce = assigns.csp_script_nonce, do: [nonce: nonce], else: [])
)
|> assign(
:csp_style_attrs,
csp_attrs || if(nonce = assigns.csp_style_nonce, do: [nonce: nonce], else: [])
)
~H"""
<script>
<script {@csp_script_attrs}>
<%= raw(@ssr_render["head"]) %>
</script>
<div
@ -154,7 +176,7 @@ defmodule LiveSvelte do
>
<div id={"#{@svelte_id}-target"} data-svelte-target>
{raw(@ssr_render["head"])}
<style>
<style {@csp_style_attrs}>
<%= raw(@ssr_render["css"]["code"]) %>
</style>
{raw(@ssr_render["html"])}

View file

@ -16,8 +16,8 @@
},
"exports": {
".": {
"import": "./assets/js/live_svelte/index.ts",
"types": "./assets/js/live_svelte/types.d.ts"
"types": "./assets/js/live_svelte/types.d.ts",
"import": "./assets/js/live_svelte/index.ts"
},
"./vitePlugin": "./assets/js/live_svelte/vite_plugin.js"
},

60
test/csp_nonce_test.exs Normal file
View file

@ -0,0 +1,60 @@
defmodule LiveSvelte.CspNonceTest do
use ExUnit.Case, async: true
defp render_html(opts) do
%{
__changed__: nil,
socket: nil,
name: "TestComponent",
id: "test-csp",
key: nil,
props: %{},
ssr: false,
class: nil,
loading: [],
inner_block: [],
csp_nonce: opts[:csp_nonce],
csp_script_nonce: opts[:csp_script_nonce],
csp_style_nonce: opts[:csp_style_nonce]
}
|> LiveSvelte.svelte()
|> Phoenix.HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
end
setup do
%{nonce: System.unique_integer([:positive])}
end
test "no nonce attributes when none specified" do
refute render_html(%{}) =~ "nonce="
end
test "csp_nonce applies to both script and style", %{nonce: nonce} do
html = render_html(%{csp_nonce: nonce})
assert html =~ ~r/<script nonce="#{nonce}">/
assert html =~ ~r/<style nonce="#{nonce}">/
end
test "csp_script_nonce applies only to script", %{nonce: nonce} do
html = render_html(%{csp_script_nonce: nonce})
assert html =~ ~r/<script nonce="#{nonce}">/
refute html =~ ~r/<style nonce=/
end
test "csp_style_nonce applies only to style", %{nonce: nonce} do
html = render_html(%{csp_style_nonce: nonce})
assert html =~ ~r/<style nonce="#{nonce}">/
refute html =~ ~r/<script nonce=/
end
test "separate script and style nonces", %{nonce: nonce} do
html = render_html(%{csp_script_nonce: nonce, csp_style_nonce: nonce * 2})
assert html =~ ~r/<script nonce="#{nonce}">/
assert html =~ ~r/<style nonce="#{nonce * 2}">/
end
end