Grouping Content Security Policy configs

The content_security_policy config can become unwieldy when supporting multiple 3rd party services. For example, the Intercom CSP v2 config is quite extensive.

I’m currently monkey-patching ActionDispatch::ContentSecurityPolicy to add an append method allowing me to merge CSP directives.

Rails.application.config.content_security_policy do |policy|
  policy.script_src :self
  # ...

  # Youtube
  policy.append do |youtube_policy|
    youtube_policy.script_src "www.youtube.com"
    # ...
  end

  # Google Analytics
  policy.append do |ga_policy|
    ga_policy.script_src "www.google-analytics.com"
    # ...
  end

  # Intercom
  # https://www.intercom.com/help/en/articles/3894-using-intercom-with-content-security-policy
  policy.append do |intercom_policy|
    intercom_policy.script_src "*.intercom.io", "*.intercomcdn.com"
    # ...
  end
end

It also makes it easier to make conditional directives.

if Rails.env.production?
  # CDN
  policy.append do |cdn_policy|
    cdn_policy.script_src ENV["RAILS_ASSET_HOST"]
  end
end

Here’s the monkey patch.

class ActionDispatch::ContentSecurityPolicy
  def append(&block)
    sub_policy = ActionDispatch::ContentSecurityPolicy.new(&block)
    sub_policy.directives.each do |key, sources|
      if @directives[key]
        @directives[key] = @directives[key].concat(sources).uniq
      else
        @directives[key] = sources
      end
    end
  end
end

Is this something that would fit in Rails core? If so I can turn it into a PR.

5 Likes

Securing Rails Applications — Ruby on Rails Guides can solve your problem? I think around_action callback can solve to all of actions and dynamic controll.

From my understanding that is only for overriding the content security policy on a per-controller or action basis.

What I’m proposing is a way to organize the global content security policy. It does not change the final result, it is just for internal organization.

1 Like

My understanding is same as your understanding. inheritance or including module will solve all of controllers.

however I think it is good to provide option as well.

Quite neat, I would do:

policy.append do |p|
  p.script_src "https://example.com"
end