avo icon indicating copy to clipboard operation
avo copied to clipboard

Need to explicitly add nonce attribute to script/stylesheet tags.

Open MrJoy opened this issue 1 year ago • 3 comments

Describe the bug

After upgrading to Rails 7.1, assets began failing to load, even with unsafe_inline in my CSP for script-src and style-src.

Chasing it down a bit, it seems that unsafe_inline is ignored when noncing is enabled.

Avo is adding the CSP tag (hooray!), but apparently not setting nonce attributes on <script> and <style> tags.

I was able to get things partially fixed by ejecting the layout, and adding this to all the relevant tags:

nonce: content_security_policy_nonce (a little more digging suggests nonce: true may be sufficient)

However, not all of the relevant tags are created from the layout, and I don't want to have to keep ejecting more and more things, and possibly monkey-patching Avo::AssetManager::JavascriptComponent.

Some of the errors seem... spurious? Like, I can't tell what's actually being prohibited from happening by looking at the page. Others are more serious (e.g. charts completely breaking).

Previously, I worked around CSP issues by adding unsafe-inline to the style-src and script-src tags, but that seems to not be Doing The Thing under Rails 7.1. I... do not know why. According to Chrome, it should never have worked, because unsafe-inline is ignored when a nonce tag is included.

Steps to Reproduce

Steps to reproduce the behavior:

  1. Create a Rails 7.1 application, with Avo
  2. Set the content security policy to be quite strict
  3. Load up an Avo page
  4. Look at the console

Expected behavior & Actual behavior

I would expect that enabling a strict CSP Just Works.

Models and resource files

Apologies for this being a bit sloppier than my usual repro efforts, but it should be sufficient to get you going:

This is our config/initializers/content_security_policy.rb:

webpack_urls = Rails.env.development? ? ["http://localhost:3035", "ws://localhost:3035"] : [] # rubocop:disable Lint/Env

SRC_URLS = ([
  :self,
  Rails.application.config.frontend_base_url,
  Rails.application.config.backend_base_url,
  Rails.application.config.short_link_url,
] + webpack_urls).compact.uniq.freeze

backend_domain = Rails.application.config.backend_base_url.split("//").last
PROFILE_URLS = [
  "https://s3.amazonaws.com/profile.#{backend_domain}/",
  "https://profile.#{backend_domain}/",
].freeze

EXTRA_STYLE_URLS =
  if Rails.env.local? || Rails.env.docker? || Rails.env.stage?
    [:unsafe_inline]
  else
    []
  end

FONT_SRC_URLS    = ([:data] + SRC_URLS).uniq.freeze
IMG_SRC_URLS     = ([:data] + SRC_URLS + PROFILE_URLS).uniq.freeze
SCRIPT_SRC_URLS  = SRC_URLS.uniq.freeze
STYLE_SRC_URLS   = (SRC_URLS + EXTRA_STYLE_URLS).uniq.freeze
CONNECT_SRC_URLS = ([:data] + SRC_URLS).uniq.freeze

Rails.application.configure do
  config.content_security_policy do |policy|
    policy.default_src(:self)
    policy.font_src(*FONT_SRC_URLS)
    policy.img_src(*IMG_SRC_URLS)
    policy.object_src(:none)
    policy.script_src(*SCRIPT_SRC_URLS)
    policy.style_src(*STYLE_SRC_URLS)
    policy.connect_src(*CONNECT_SRC_URLS)
  end

  # Generate session nonces for permitted importmap, inline scripts, and inline styles.
  config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
  config.content_security_policy_nonce_directives = %w[script-src style-src]

  config.middleware.insert_before(0, Rack::Cors) do
    allow do
      origins(*Rails.application.config.origins)

      resource("*", headers: :any, credentials: true, methods: %i[get post delete put patch options head])
    end
  end
end

Our config/initializers/avo.rb includes this:

Rails.application.config.to_prepare do
  Avo::ApplicationController.include(LaxContentSecurityPolicy)
end

And LaxContentSecurityPolicy, which exists to work around previous CSP issues with Avo is defined as:

module LaxContentSecurityPolicy
  extend ActiveSupport::Concern

  included do
    class_eval do
      content_security_policy do |policy|
        policy.default_src(:self)
        policy.font_src(*FONT_SRC_URLS)
        policy.img_src(*IMG_SRC_URLS)
        policy.object_src(:none)
        policy.script_src(*([:unsafe_inline] + SCRIPT_SRC_URLS))
        policy.style_src(*([:unsafe_inline] + SCRIPT_SRC_URLS))
        policy.connect_src(*CONNECT_SRC_URLS)
      end
    end
  end
end

System configuration

Avo version: 3.2.0 (also 3.1.7)

Rails version: 7.1.2

Ruby version: 3.2.2

License type:

  • [ ] Community
  • [ ] Pro
  • [x] Advanced

Are you using Avo monkey patches, overriding views or view components?

  • [x] Yes. If so, please post code samples.
  • [ ] No

Screenshots or screen recordings

Additional context

Impact

  • [ ] High impact (It makes my app un-usable.)
  • [x] Medium impact (I'm annoyed, but I'll live.)
  • [ ] Low impact (It's really a tiny thing that I could live with.)

Urgency

  • [ ] High urgency (I can't continue development without it.)
  • [x] Medium urgency (I found a workaround, but I'd love to have it fixed.)
  • [ ] Low urgency (It can wait. I just wanted you to know about it.)

(My workaround, at the moment, is to disable noncing -- so, not a workaround I'm happy to keep using indefinitely!)

MrJoy avatar Dec 21 '23 00:12 MrJoy

@MrJoy we don't have a lot of experience with CSP and I'm afraid we'll wonder blindly in the dark with this.

Would you be able to help out with a PR?

adrianthedev avatar Jan 06 '24 21:01 adrianthedev

@adrianthedev Unfortunately, I'm at a loss here. I can't identify what exactly is going wrong, so I don't know precisely what to fix.

My example should be easy to simplify into something reproducible. In this case, Rails.application.config.backend_base_url is just the link to the Rails app itself and the other URLs are pretty much irrelevant.

MrJoy avatar Jan 11 '24 01:01 MrJoy

Like, I can make a PR that just adds nonce: content_security_policy_nonce / nonce: true to all the script/style tags, but I'd just be doing so blindly without any real understanding of when that is/isn't appropriate.

MrJoy avatar Jan 11 '24 01:01 MrJoy

It seem like nonce attributes have been added, is this still an ongoing issue?

For unrelated reason I'm currently running an older version, and the monkey-patching presented above didn't cut it for me. Firefox essentially ignore unsafe_inline if nonce is set.

For posterity, if anyone end up here in the same situation as me, as a temporary fix I ended up disabling CSP completely for Avo:

module DisableContentSecurityPolicy
  extend ActiveSupport::Concern

  included do
    class_eval do
      content_security_policy false
    end
  end
end

Rails.application.config.to_prepare do
  Avo::ApplicationController.include(DisableContentSecurityPolicy)
end

oz-tal avatar Jun 19 '24 20:06 oz-tal

I think this is a good solution for now @oz-tal.

We don't know a lot of details about nonces and CSP so we can't make an educated decision around it. We'd welcome a PR that supports the necessary customization for other devs to apply to their apps and make this work.

Until then, I'll close the issue so we keep the queue clean.

adrianthedev avatar Jun 19 '24 20:06 adrianthedev