Module: ConcernsOnRails::Controllers::SecureHeadable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/concerns_on_rails/controllers/secure_headable.rb
Overview
Adds modern security response headers and wires Rails’ native Content-Security-Policy DSL. Defense-in-depth on top of output escaping —this does NOT scrub request params (context-blind and lossy) and never re-enables the deprecated X-XSS-Protection auditor.
class ApplicationController < ActionController::Base
include ConcernsOnRails::Controllers::SecureHeadable
# Apply preset headers, plus any custom "Header-Name" => "value" pairs:
secure_headers :nosniff, :sameorigin_frame, :no_referrer_leak, :disable_legacy_xss
secure_headers "Permissions-Policy" => "geolocation=()"
# Delegates to Rails' native CSP DSL — roll out report-only FIRST:
content_security_policy_for(report_only: true) do |policy|
policy.default_src :self
policy.script_src :self
policy.object_src :none
end
end
The headers mitigate clickjacking / MIME-sniffing and (via CSP) XSS as defense-in-depth — they are NOT a standalone XSS fix; output escaping remains the primary defense. Per-controller CSP overrides the global initializer for that controller. CSP nonce generation (content_security_policy_nonce_generator / _nonce_directives) is app-wide initializer configuration and intentionally stays out of this concern.
Header presets (the ‘secure_headers` arguments):
:nosniff — X-Content-Type-Options: nosniff
:sameorigin_frame — X-Frame-Options: SAMEORIGIN
:deny_frame — X-Frame-Options: DENY
:no_referrer_leak — Referrer-Policy: strict-origin-when-cross-origin
:no_cross_domain — X-Permitted-Cross-Domain-Policies: none
:disable_legacy_xss — X-XSS-Protection: 0 (the only correct modern value)
Constant Summary collapse
- PRESETS =
Frozen, string-only header presets, each “Header-Name” => “value”. :disable_legacy_xss emits “0” deliberately — the legacy browser XSS auditor was itself exploitable and is gone from modern browsers (Rails 7+ ships “0”), so “0” is the only correct value.
{ nosniff: %w[X-Content-Type-Options nosniff], sameorigin_frame: %w[X-Frame-Options SAMEORIGIN], deny_frame: %w[X-Frame-Options DENY], no_referrer_leak: %w[Referrer-Policy strict-origin-when-cross-origin], no_cross_domain: %w[X-Permitted-Cross-Domain-Policies none], disable_legacy_xss: %w[X-XSS-Protection 0] }.freeze
Instance Method Summary collapse
-
#apply_secure_headers ⇒ Object
Public so subclasses can override; guarded exactly like Paginatable so it no-ops cleanly when there is no response object.
Instance Method Details
#apply_secure_headers ⇒ Object
Public so subclasses can override; guarded exactly like Paginatable so it no-ops cleanly when there is no response object.
98 99 100 101 102 |
# File 'lib/concerns_on_rails/controllers/secure_headable.rb', line 98 def apply_secure_headers return unless respond_to?(:response) && response self.class.secure_headable_headers.each { |name, value| response.set_header(name, value) } end |