Reviewing your CSP policies and included scripts

by Sahil Gadimbayli, Founder / Senior Engineer

Like many engineers, we sometimes overlook the importance of carefully managing Content Security Policy (CSP) settings and the scripts we include when developing web applications.

When building features quickly, it's easy to let CSP policies become too permissive or to include scripts on pages where they aren't actually needed. Over time, this can weaken your application's security.

With the recent changes introduced in PCI DSS 4.0.1, I took the opportunity to review our CSP policies and the scripts we allow. In this article, I'll share some key findings and best practices that can help keep your application secure and compliant.

What is CSP?

Content Security Policy (CSP) is a security standard that lets you control which resources (like JavaScript, CSS, and images) your application is allowed to load. By specifying trusted sources, CSP helps prevent attacks such as cross-site scripting (XSS) and data injection.

What is PCI DSS?

PCI DSS is a set of security standards designed to ensure that organizations that handle credit card information maintain a secure environment.

What's New in PCI DSS 4.0.1?

  • Requirement 6.4.3
  • Requirement 11.6.1

These requirements are focused on comprehensive management of client side scripts and policies surrounding their loading and integrity.

Even if you're not in the payments industry, it's always smart to be selective about the resources your application loads. This helps to minimize potential security risks.

Getting Started

The examples below use Ruby on Rails, but the concepts apply to any web application framework.

Setting Up a CSP Policy in Rails

You can define a CSP policy in your application.rb file:

# application.rb
config.content_security_policy do |policy|
  policy.default_src :self, "https://example.com"
end

Alternatively, you can use the Secure Headers gem:

# config/initializers/secure_headers.rb
SecureHeaders::Configuration.default do |config|
  config.csp = {
    default_src: %w['self' https://example.com]
  }
end

What happens next?

As development progresses, you might need to add exceptions to your CSP for third-party libraries. Sometimes these libraries require more permissive policies, such as allowing inline scripts or loading resources from additional domains.

The common pitfall: these exceptions often get added to the global CSP, even if they're only needed for a single page. This can make your policy unnecessarily broad and less secure.

What can we do to improve?

The Secure Headers gem supports named policies, allowing you to define page-specific CSP rules. This keeps your global policy strict, while only relaxing it where absolutely necessary.

# config/initializers/secure_headers.rb

# Default CSP policy
SecureHeaders::Configuration.default do |config|
  config.csp = {
    default_src: %w['self' https://example.com]
  }
end

# Named policy for the checkout page
SecureHeaders::Configuration.named_append(:CHECKOUT_PAGE_POLICY) do |config|
  config.csp = {
    default_src: %w[https://somethirdparty.library.com]
  }
end

Then, in your controller:

class CheckoutController < ApplicationController
  def show
    use_content_security_policy_named_append(:CHECKOUT_PAGE_POLICY)
  end
end

For readability purposes, configuration options for CSPs are not fully shown. Check the Secure Headers gem documentation or MDN for more details. You should ideally have very strict default CSP, disallowing everything except your own application's resources.

What are the benefits?

  • Your global CSP stays clean, strict, and easy to manage.

  • Third-party scripts are only allowed where needed.

  • You minimize unnecessary exposure by limiting exceptions to specific pages.

You can use tools like securityheaders.com to review your application's headers and get feedback on your CSP configuration.

Additional steps to secure your application's client side

  • Add a reporting URI: Configure your CSP to report violations. Services like Sentry or Report URI can help you monitor and respond to policy breaches.

  • Use Subresource Integrity (SRI): Ensure external scripts haven't been tampered with by specifying cryptographic hashes. This is most effective for scripts with fixed versions.

  • Implement nonces: Use unique, random values for each request to allow only authorized inline scripts to execute.

  • Keep inventory of all the scripts. These include third-party libraries included through CDNs, as well as javascript packages you use in your application which are part of your application's client side bundle.

  • Start with report-only mode: If you're unsure about how to build your policy, use CSP's report-only mode to log violations without blocking resources. This lets you fine-tune your policy before enforcing it.

Report-only mode uses the same configuration options as your enforced policy. Learn more on MDN.

More articles

Monitoring Application Built Fully on JavaScript

Building a desktop monitoring system with Electron, WebRTC, and Node.js

Read more

Structuring dynamic columns in relational databases

Even though the concepts of relational database and dynamic columns are contradictory to each other, there are situations where it might be needed to be in database structure.

Read more

Tell us about your project

lightfulweb OÜ

  • Tallin
    Sepapaja tn 6
    15551 Tallin, Estonia