CSP Maker
CSP Maker is a micro gem in Ruby for defining a Content-Security-Policy, extracted from Rails so it can be used standalone or in Rack apps.
The code is extracted from ActionDispatch::ContentSecurityPolicy in Action Pack, with some minor modifications. The DSL as described in Rails docs should be usable 1:1 here. There is also an optional Rack middleware updated to not rely on Rails internals.
Installation
Add the gem to your app's Gemfile:
gem 'csp_maker', '~> 0.1'
Then, require it in your server code:
require 'csp_maker'
CSP Maker supports Ruby 3.0 and newer.
Basic usage
The simplest way is through the CSPMaker.build_policy method:
headers['Content-Security-Policy'] = CSPMaker.build_policy do |p|
p.default_src :none
p.script_src :self, 'https://github.com', 'https://example.com'
p.style_src :self
p.font_src :self
p.img_src :self, :https, :data
p.connect_src :self, 'https://api.bsky.app'
p.base_uri :none
p.object_src :none
end
This creates a ContentSecurityPolicy definition object and then immediately encodes it into the final string, which you can assign to the target header.
Alternatively, you can also pass a block without parameters and call the DSL methods on an implicit self like this:
headers['Content-Security-Policy'] = CSPMaker.build_policy do
default_src :none
script_src :self, 'https://github.com', 'https://example.com'
style_src :self
font_src :self
img_src :self, :https, :data
connect_src :self, 'https://api.bsky.app'
base_uri :none
object_src :none
end
Note, in this case (block without parameters) the block is run through instance_eval in the context of the policy object, so helper methods and instance vars from outside the block won't be accessible inside; if you want to use those, use the version with a parameter, or call the ContentSecurityPolicy constructor directly.
If you want to use nonces, you can generate a nonce yourself and pass it to the DSL using a new nonce() DSL method like this:
asset_nonce = ...
headers['Content-Security-Policy'] = CSPMaker.build_policy do
default_src :none
script_src :self, nonce(asset_nonce)
end
A second approach is to use CSPMaker.define_policy to create a policy definition once, and then reuse it on each request:
CSP = CSPMaker.define_policy do
default_src :none
script_src :self, 'https://github.com', 'https://example.com'
style_src :self
...
end
You encode the policy into the header string by calling #build(context, nonce, nonce_directives). The parameters (all optional) are:
context– you can pass a Proc instead of a String/Symbol to a DSL directive, and that Proc will be called with this context object asself(it can be e.g. some kind of controller or request object)nonce– a nonce to be added to the designated directivesnonce_directives– array of directives to which nonces should be added; if nil, the list is read from a global settingCSPMaker.nonce_directives, or the default['script-src', 'style-src']
So it can look like this:
asset_nonce = ...
headers['Content-Security-Policy'] = CSP.build(nil, asset_nonce, ['script-src'])
Rack middleware
A third way is to use the Rack middleware. The middleware class is also taken from the Rails code, but simplified to not rely on Rails internals.
Require the csp_maker/rack file instead of csp_maker:
require 'csp_maker/rack'
Define a policy, and pass it to the CSPMaker::Middleware when installing it into the Rack stack:
policy = CSPMaker.define_policy {
...
}
use CSPMaker::Middleware, policy
The middleware will:
- automatically generate a nonce, append it to the directives and store it in the Rack
env, if a nonce generator is configured (see below) - store the generated nonce in the
envundercsp_maker.nonce(CSPMaker::Middleware::NONCE_ENV_KEY) - create a
Rack::Requestobject and use it as the block context for any Procs in the DSL - encode the policy into a result string
- assign it to the
content-security-policyheader automatically
Using nonces
When using the Rack middleware, if you want it to append nonces, you need to assign a nonce generator Proc to CSPMaker.nonce_generator (equivalent of config.content_security_policy_nonce_generator in Rails), or pass it as a nonce_generator: option to the Middleware initializer. You can use CSPMaker.default_generator, which calls SecureRandom.base64(16):
CSPMaker.nonce_generator = CSPMaker.default_generator
# or:
CSPMaker.nonce_generator = -> { ... }
Use CSPMaker.nonce_directives (equivalent of config.content_security_policy_nonce_directives in Rails) or nonce_directives: option in Middleware to configure which directives should have a nonce added to them (default is ['script-src', 'style-src']):
CSPMaker.nonce_directives = ['script-src']
# or:
use CSPMaker::Middleware, policy, nonce_generator: -> { ... }, nonce_directives: ['style-src']
The generated nonce is stored in the env hash under csp_maker.nonce (CSPMaker::Middleware::NONCE_ENV_KEY). You will need it for the view template code to add it to the inline script / style tags, so you could add a helper like:
def asset_nonce
request.env[CSPMaker::Middleware::NONCE_ENV_KEY]
end
When using build_policy or define_policy standalone without a middleware, generating and storing the nonce is up to you – and you either pass it to nonce(...) inside a build_policy block, or as the second argument to #build on a CSPMaker::ContentSecurityPolicy object returned from define_policy. However, you can also assign CSPMaker.nonce_generator and call CSPMaker.make_nonce to run it if you want:
CSPMaker.nonce_generator = CSPMaker.default_generator
policy = CSPMaker.define_policy { ... }
nonce = CSPMaker.make_nonce
headers['content-security-policy'] = policy.build(nil, nonce)
Other options
You can also set CSPMaker.report_only (equivalent of config.content_security_policy_report_only in Rails) or report_only: option to Middleware to assign the policy to the content-security-policy-report-only header instead of content-security-policy; this makes the browser only report errors to the URL configured via report_uri in the DSL, but not actually enforce the policy in the web app (i.e. not block any scripts & styles from loading).
Full DSL API
Directives:
- directives that accept an array of sources:
base_uri,child_src,connect_src,default_src,font_src,form_action,frame_ancestors,frame_src,img_src,manifest_src,media_src,object_src,prefetch_src,require_trusted_types_for,script_src,script_src_attr,script_src_elem,style_src,style_src_attr,style_src_elem,trusted_types,worker_src block_all_mixed_content(enabled = true)plugin_types(*types)report_uri(uri)require_sri_for(*types)sandbox(enabled = true)orsandbox(*values)upgrade_insecure_requests(enabled = true)
Source lists:
- special values:
:allow_duplicates,:none,:report_sample,:script,:self,:strict_dynamic,:unsafe_eval,:unsafe_hashes,:unsafe_inline,:wasm_unsafe_eval - protocols:
:http,:https,:data,:mediastream,:blob,:filesystem,:ws,:wss - content hashes:
"sha256-...","sha384-...","sha512-..." nonce(nonce_value)
Credits
The original content_security_policy.rb was added to Rails ActionPack code by Andrew White in 2017, and had some updates since then by others.
Modifications for the purposes of this gem are © 2026 Kuba Suder (@mackuba.eu).
The code is available under the terms of the MIT license.
Bug reports and pull requests are welcome :)