Stack guide
Rails secure_headers — the gem that handles every header Scorifya scans
Rails 7+ ships its own `content_security_policy` DSL in `config/initializers/content_security_policy.rb`, but for everything beyond CSP — HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy — the `secure_headers` gem (originally from Twitter, now community-maintained) is still the cleanest choice. This guide covers both: the gem-managed path (recommended for most apps) and the bare Rails 7 DSL when you only need CSP.
1. Install secure_headers and configure the defaults
Add `gem 'secure_headers'` to your Gemfile, run `bundle install`, then create `config/initializers/secure_headers.rb` with the defaults block. The gem ships with sensible production defaults (HSTS 1-year + includeSubDomains, X-Frame-Options DENY, Referrer-Policy strict-origin-when-cross-origin, X-Content-Type-Options nosniff). Override only what you need — the goal is fewer custom lines, not more.
HSTS missing — nginx (server / location) (nginx)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # If TLS terminates at a CDN, set the header there instead.
HSTS missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(helmet.hsts({ maxAge: 31_536_000, includeSubDomains: true }));HSTS missing — Apache (vhost) (apache)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options missing — nginx (nginx)
add_header X-Frame-Options "DENY" always; # Use SAMEORIGIN instead of DENY only if same-origin framing is required.
X-Frame-Options missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(helmet.frameguard({ action: "deny" }));X-Frame-Options missing — Apache (apache)
Header set X-Frame-Options "DENY"
X-Content-Type-Options missing — nginx (nginx)
add_header X-Content-Type-Options "nosniff" always;
X-Content-Type-Options missing — Express (helmet) (typescript)
import helmet from "helmet"; app.use(helmet.noSniff());
X-Content-Type-Options missing — Apache (apache)
Header set X-Content-Type-Options "nosniff"
Related Scorifya checks: missing_hsts, missing_xfo, missing_xcto, missing_referrer_policy, missing_permissions_policy
2. CSP — start in report-only, then enforce
secure_headers exposes both `csp` (enforced) and `csp_report_only` keys. Configure `csp_report_only` first with a permissive policy plus a `report_uri`, deploy, watch the report stream for a release cycle, then move the same config into `csp` and remove the report-only block. Per-controller overrides use `use_secure_headers_override(:override_name)` after defining the override in the initializer.
CSP missing — nginx (nginx)
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'" always;
CSP missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
baseUri: ["'self'"],
formAction: ["'self'"],
},
}),
);CSP missing — Apache (apache)
Header set Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'"
Weak CSP — nginx (tighten script-src) (nginx)
# Drop 'unsafe-inline' / wildcards in production; use nonces or hashes for any inline script. add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'" always;
Weak CSP — Express (helmet) (typescript)
import helmet from "helmet";
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
objectSrc: ["'none'"],
},
}),
);Weak CSP — Apache (apache)
# Replace permissive policy with explicit hosts and no unsafe-inline where possible. Header set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'"
Related Scorifya checks: missing_csp, weak_csp
3. Rails 7 native CSP DSL (if you only need CSP)
If you don't want a gem, Rails 7 ships `Rails.application.config.content_security_policy` in `config/initializers/content_security_policy.rb`. It's the same idea — `policy.default_src :self` etc. — but doesn't cover the other headers. Most Rails teams end up adding secure_headers anyway for the full set, but the native DSL is fine for CSP-only apps.
Background
What is Content Security Policy (CSP)? A practical explainer →
An accessible explanation of Content Security Policy: what it does, why it exists, the directives that matter, and how to roll one out without breaking your app.
Read more
Verify with a fresh scan
After deploy, run the scanner on the affected hostname. Headers and TLS settings update on the very next request, so you should see the score move within seconds.