Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support CSP nonce in script and style tags #102

Open
Darth-Knoppix opened this issue Jan 9, 2024 · 4 comments
Open

Support CSP nonce in script and style tags #102

Darth-Knoppix opened this issue Jan 9, 2024 · 4 comments

Comments

@Darth-Knoppix
Copy link

Context

The svelte Live View component renders <style> and <script> tags inline. This can cause issues when using a content security policy because it's inline.

Opportunity

If additional attributes could be added to these tags, it would allow for a more strict CSP header, specifically adding a nonce-* attribute, reference.

Example output

Header

Content-Security-Policy: style-src 'nonce-imk7oIUnJE8r5ResWI1Rq-TsrtoDqZWTVYQIX9Xf9iU' 'self'; script-src 'nonce-8IBTHwOdqNKAWeKl7plt8g==';

Svelte render output

<script nonce="8IBTHwOdqNKAWeKl7plt8g==">/* some head scripts here */</script>
<div id="MyComponent-17506" data-name="MyComponent" data-props="{}" data-live-json="{}" data-slots="{}" phx-update="ignore" phx-hook="SvelteHook" class="">
  <style nonce="imk7oIUnJE8r5ResWI1Rq-TsrtoDqZWTVYQIX9Xf9iU">/* some styles here*/</style>
  {"var1":null}
  <p>component content</p>
</div>
@camstuart
Copy link

camstuart commented Jun 7, 2024

+1 to this. My app is complaining rather verbosely in the chrome console when I load a page with a live svelte component on it

@woutdp
Copy link
Owner

woutdp commented Jun 7, 2024

Couple of question:

  • How would you generate these nonce values in Elixir?
  • These values would be different on every render?
  • Would just implementing arbitrary attributes on style and script tags (might as well do the main div tag too) solve this issue? This would mean you'd have to pass it manually every time for every Svelte component

@camstuart
Copy link

Good questions, generating a nonce seems easy enough with:

# endpoint.ex
defmodule MyApp.Endpoint do
  use Phoenix.Endpoint, otp_app: :myapp

  defp generate_nonce() do
    :crypto.strong_rand_bytes(16) |> Base.encode64()
  end

  plug Plug.Static,
    at: "/",
    from: :myapp,
    gzip: false,
    only: MyAppWeb.static_paths(),
    headers: %{
      "content-security-policy" =>
        "default-src 'self'; style-src 'self' 'nonce-" <> generate_nonce() <> "';"
    }
end

but passing that to the svelte component is another matter. I wonder though, can it be assigned to the socket? which is always being passed in 🤔

I don't know why this has cropped up for me all of a sudden. I had my first test app with live_svelte, a fairly sophisticated svelte component doing an address search / autocomplete. I was really impressed, you have created a great tool that fills a gap IMO.

Then I brought that svelte component into a bigger, more serious app which is the one that is complaining. I am going to investigate if there is a package / configuration that has a higher level of paranoia.

In fact, the third party javascript I am calling in inside my svelte, won't even load:

The component:

<script>
  import { onMount } from 'svelte'
  import placekitAutocomplete from '@placekit/autocomplete-js'

  export let live
  export let apiKey
  export let placeHolder
  export let defaultValue

  let pk
  let input

  onMount(() => {
    if (!placeHolder) {
      placeHolder = 'Search...'
    }

    if (input) {
      pk = placekitAutocomplete(apiKey, {
        target: input,
        countries: ['au'],
        placeholder: 'Search for an address',
      })

      pk.configure({
        format: {
          value: item =>
            `${item.name} ${item.city}, ${item.administrative} ${item.zipcode}`,
        },
      })

      pk.on('pick', (value, location, index) => {
        console.log('full address data', location)
        live.pushEvent('geocoded-address', { location }, () => {})
      })
    }
  })
</script>

<input
  type="search"
  bind:this={input}
  class="mt-2 block w-full rounded-md text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 border-zinc-300 focus:border-zinc-400"
  placeholder={placeHolder}
  value={defaultValue}
/>

Gives me these errors in the console:

placekit-autocomplete.esm.mjs:47 Refused to connect to 'https://api.placekit.co/search' because it violates the following Content Security Policy directive: "default-src 'self'". Note that 'connect-src' was not explicitly set, so 'default-src' is used as a fallback.

As well as complaints about inline styles.

I'll keep digging and see what I can come up with

@camstuart
Copy link

In case anyone else has similar content security policy (CSP) issues, I was able to work around it like this:

I created a plug in myapp_web/plugs/csp_header.ex

defmodule MyAppWeb.Plugs.CSPHeader do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    csp_header =
      "default-src 'self'; connect-src 'self' https://api.placekit.co; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"

    conn
    |> put_resp_header("content-security-policy", csp_header)
  end
end

And then added it in router.ex to the browser pipeline:

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
    plug :protect_from_forgery

    plug :put_secure_browser_headers, %{
      "content-security-policy" => "default-src 'self'; img-src * data:;"
    }

    plug :fetch_current_user
    plug MyAppWeb.Plugs.CSPHeader
  end

The unsafe aspect is unfortunate, I will need to keep digging on this. But here is a workaround, such as it is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants