# CSP Maker CSP Maker is a micro gem in Ruby for defining a [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy), extracted from Rails so it can be used standalone or in Rack apps. The code is extracted from [ActionDispatch::ContentSecurityPolicy](https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/http/content_security_policy.rb) 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: ```rb require 'csp_maker' ``` CSP Maker supports Ruby 3.0 and newer. ## Basic usage The simplest way is through the `CSPMaker.build_policy` method: ```rb 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: ```rb 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: ```rb 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: ```rb 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 as `self` (it can be e.g. some kind of controller or request object) - `nonce` – a nonce to be added to the designated directives - `nonce_directives` – array of directives to which nonces should be added; if nil, the list is read from a global setting `CSPMaker.nonce_directives`, or the default `['script-src', 'style-src']` So it can look like this: ```rb 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`: ```rb require 'csp_maker/rack' ``` Define a policy, and pass it to the `CSPMaker::Middleware` when installing it into the Rack stack: ```rb 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 `env` under `csp_maker.nonce` (`CSPMaker::Middleware::NONCE_ENV_KEY`) - create a `Rack::Request` object 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-policy` header 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)`: ```rb 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']`): ```rb 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: ```rb 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: ```rb 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)` or `sandbox(*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](https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr)). The code is available under the terms of the [MIT license](https://choosealicense.com/licenses/mit/). Bug reports and pull requests are welcome :)