Overview
Security headers are HTTP response headers sent by web servers to control how browsers handle content, execute scripts, load resources, and interact with web applications. These headers form a critical layer of defense-in-depth security strategy by instructing browsers to enforce specific security policies regardless of the application code.
Modern browsers support a range of security headers designed to prevent attacks such as cross-site scripting (XSS), clickjacking, man-in-the-middle attacks, and data injection. Each header addresses a specific vulnerability class by restricting browser behavior in ways that block common attack vectors. While application-level security remains essential, security headers provide an additional enforcement mechanism that operates at the browser level.
The effectiveness of security headers depends on proper configuration. Misconfigured headers can break application functionality, while missing headers leave applications vulnerable to preventable attacks. Security headers apply across all modern browsers, though implementation details and support vary by browser version.
# Basic security header response
HTTP/1.1 200 OK
Content-Type: text/html
Content-Security-Policy: default-src 'self'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000
Security headers have evolved from simple directives like X-Frame-Options to complex policy systems like Content Security Policy (CSP) that support granular control over resource loading and script execution. This evolution reflects the growing sophistication of web-based attacks and the need for more precise security controls.
Key Principles
Security headers operate on a principle of explicit permission rather than implicit trust. By default, browsers allow broad behavior including executing inline scripts, loading resources from any origin, and embedding content in frames. Security headers reverse this model by requiring explicit declaration of permitted actions.
The Content Security Policy (CSP) header defines allowed sources for scripts, styles, images, fonts, and other resources. CSP works by establishing a whitelist of trusted sources and blocking any content that doesn't match. This prevents XSS attacks where malicious scripts are injected into pages, as the injected scripts won't match the CSP whitelist and the browser will refuse to execute them.
# CSP blocks unauthorized scripts
Content-Security-Policy: script-src 'self' https://trusted-cdn.com
# Inline script blocked - doesn't match policy
<script>maliciousCode()</script>
# External script from trusted source - allowed
<script src="https://trusted-cdn.com/app.js"></script>
The Strict-Transport-Security (HSTS) header enforces HTTPS connections by instructing browsers to only connect via secure protocols. Once a browser receives an HSTS header, it automatically upgrades all subsequent requests to HTTPS and refuses to connect over plain HTTP. This prevents man-in-the-middle attacks that exploit the initial HTTP request before redirection to HTTPS.
Frame control headers prevent clickjacking attacks where malicious sites embed target pages in iframes and trick users into performing unintended actions. The X-Frame-Options header controls whether a page can be displayed in a frame, with options to deny framing entirely, allow only same-origin framing, or specify allowed domains.
# Prevent all framing
X-Frame-Options: DENY
# Allow only same-origin framing
X-Frame-Options: SAMEORIGIN
# Allow specific domain
X-Frame-Options: ALLOW-FROM https://trusted.example.com
The X-Content-Type-Options header prevents MIME type sniffing where browsers attempt to determine content types by inspecting file contents rather than trusting the declared Content-Type header. Setting this to nosniff forces browsers to respect declared content types, preventing scenarios where user-uploaded files are executed as scripts.
Referrer control headers manage what information browsers send in the Referer header when navigating between pages. The Referrer-Policy header controls whether full URLs, origins only, or no referrer information is sent, preventing information leakage through URLs that may contain sensitive parameters.
Permissions Policy (formerly Feature Policy) controls access to browser features like geolocation, camera, microphone, and payment APIs. This header prevents malicious scripts or third-party content from accessing powerful browser features without explicit permission.
Security Implications
Security headers create multiple defense layers that protect against different attack vectors. The primary security benefit comes from reducing the attack surface by restricting what browsers can do with content served by the application.
Cross-site scripting attacks inject malicious scripts into web pages viewed by other users. Without CSP, browsers execute any script found in page content, including attacker-injected scripts. A properly configured CSP prevents XSS by blocking inline scripts and restricting script sources to trusted domains. Even if an attacker successfully injects script tags into page content, the browser refuses to execute them.
# CSP configuration that blocks XSS
Content-Security-Policy: default-src 'self';
script-src 'self' https://trusted-scripts.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://trusted-fonts.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
Man-in-the-middle attacks intercept traffic between browsers and servers to steal credentials or inject malicious content. HSTS prevents these attacks by ensuring browsers only connect via HTTPS after the first successful connection. The includeSubDomains directive extends protection to all subdomains, and preload allows browsers to enforce HTTPS before the first connection by including the domain in browser preload lists.
Clickjacking tricks users into clicking elements on a hidden page overlaid on a visible page. Frame control headers prevent this by blocking the page from being embedded in frames. The Content Security Policy frame-ancestors directive provides more granular control than X-Frame-Options, allowing specification of multiple allowed origins.
MIME sniffing attacks upload files with misleading content types that browsers then execute as scripts. Setting X-Content-Type-Options: nosniff forces browsers to respect declared content types, preventing uploaded images from being executed as HTML or JavaScript.
# Prevent MIME sniffing on file uploads
def serve_upload
send_file uploaded_file.path,
type: uploaded_file.content_type,
disposition: 'attachment',
headers: {
'X-Content-Type-Options' => 'nosniff',
'Content-Security-Policy' => "default-src 'none'"
}
end
Cross-origin attacks exploit browser same-origin policy weaknesses to access data from other domains. CORS headers control cross-origin access, but security headers like CSP add additional restrictions on what resources can be loaded from which origins.
Information leakage through referrer headers exposes sensitive URL parameters and application structure to third parties. Configuring Referrer-Policy to strict-origin-when-cross-origin or no-referrer prevents leaking sensitive information while maintaining analytics capabilities.
Permission abuse occurs when malicious scripts or third-party content access browser features like camera, microphone, or geolocation without user consent. Permissions Policy restricts these capabilities to trusted contexts, preventing unauthorized access to sensitive device features.
Ruby Implementation
Rails applications implement security headers through middleware, controller actions, or application configuration. The secure_headers gem provides a comprehensive solution for managing security headers with sensible defaults and configuration options.
# Gemfile
gem 'secure_headers'
# config/initializers/secure_headers.rb
SecureHeaders::Configuration.default do |config|
config.csp = {
default_src: %w['self'],
script_src: %w['self' https://trusted-cdn.com],
style_src: %w['self' 'unsafe-inline'],
img_src: %w['self' data: https:],
font_src: %w['self' https://fonts.gstatic.com],
connect_src: %w['self' https://api.example.com],
frame_ancestors: %w['none'],
base_uri: %w['self'],
form_action: %w['self']
}
config.hsts = "max-age=#{1.year.to_i}; includeSubDomains; preload"
config.x_frame_options = "DENY"
config.x_content_type_options = "nosniff"
config.x_xss_protection = "0" # Disabled in modern browsers
config.referrer_policy = "strict-origin-when-cross-origin"
end
The secure_headers gem automatically applies configured headers to all responses. Per-controller or per-action customization uses the override_content_security_policy_directives method when specific pages require different policies.
class DashboardController < ApplicationController
# Allow inline scripts for this controller
content_security_policy do |policy|
policy.script_src :self, :unsafe_inline
end
def analytics_dashboard
# Additional directives for this action
append_content_security_policy_directives(
script_src: %w[https://analytics-provider.com],
connect_src: %w[https://analytics-api.com]
)
end
end
Rails 5.2+ includes built-in CSP configuration through application configuration and controller DSL. This provides CSP support without additional gems for applications with simpler requirements.
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https
# Generate nonce for inline scripts
policy.script_src :self, :https, :unsafe_inline unless Rails.env.production?
end
# Enable nonce generation
Rails.application.config.content_security_policy_nonce_generator =
->(request) { SecureRandom.base64(16) }
# Report violations
Rails.application.config.content_security_policy_report_only = false
Rack middleware provides security headers for non-Rails applications or when middleware-based configuration is preferred. Custom middleware applies headers to all responses with flexible configuration options.
# config.ru
require 'rack'
class SecurityHeaders
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers['Content-Security-Policy'] = csp_policy
headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
headers['X-Frame-Options'] = 'SAMEORIGIN'
headers['X-Content-Type-Options'] = 'nosniff'
headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
[status, headers, body]
end
private
def csp_policy
directives = {
'default-src' => ["'self'"],
'script-src' => ["'self'", 'https://trusted-cdn.com'],
'style-src' => ["'self'", "'unsafe-inline'"],
'img-src' => ["'self'", 'data:', 'https:']
}
directives.map { |k, v| "#{k} #{v.join(' ')}" }.join('; ')
end
end
use SecurityHeaders
run YourApp
Sinatra applications configure security headers through helpers or before filters that set headers on each request.
require 'sinatra'
helpers do
def set_security_headers
headers 'Content-Security-Policy' => "default-src 'self'",
'X-Frame-Options' => 'DENY',
'X-Content-Type-Options' => 'nosniff',
'Strict-Transport-Security' => 'max-age=31536000',
'Referrer-Policy' => 'no-referrer'
end
end
before do
set_security_headers
end
get '/' do
erb :index
end
CSP nonces provide a mechanism for allowing specific inline scripts while blocking others. Rails generates unique nonces per request and automatically injects them into script tags when using javascript_tag with the nonce: true option.
# View template
<%= javascript_tag nonce: true do %>
// This inline script allowed via nonce
console.log('Trusted inline script');
<% end %>
# Generated HTML includes nonce attribute
<script nonce="random-base64-value">
console.log('Trusted inline script');
</script>
# CSP header includes nonce
Content-Security-Policy: script-src 'self' 'nonce-random-base64-value'
Practical Examples
A public-facing marketing site requires strict security while allowing analytics and embedded videos. The configuration blocks unauthorized scripts while permitting trusted third-party resources.
# config/initializers/secure_headers.rb
SecureHeaders::Configuration.default do |config|
config.csp = {
default_src: %w['none'],
script_src: %w[
'self'
https://www.google-analytics.com
https://www.googletagmanager.com
],
style_src: %w['self' 'unsafe-inline'],
img_src: %w['self' https: data:],
font_src: %w['self' https://fonts.gstatic.com],
connect_src: %w['self' https://www.google-analytics.com],
frame_src: %w[https://www.youtube.com https://player.vimeo.com],
frame_ancestors: %w['none'],
base_uri: %w['self'],
form_action: %w['self']
}
config.hsts = "max-age=#{2.years.to_i}; includeSubDomains; preload"
config.x_frame_options = "DENY"
config.x_content_type_options = "nosniff"
config.referrer_policy = "strict-origin-when-cross-origin"
# Report CSP violations for monitoring
config.csp_report_only = Rails.env.production? ? false : true
config.report_uri = %w[https://csp-reporting.example.com/report]
end
A SaaS application dashboard with user-generated content requires strict CSP to prevent XSS while allowing necessary functionality. Different sections of the application need different policies based on their security requirements.
class ApplicationController < ActionController::Base
# Base security policy for all pages
content_security_policy do |policy|
policy.default_src :self
policy.script_src :self
policy.style_src :self, :unsafe_inline # Required for some UI frameworks
policy.img_src :self, :https, :data
policy.connect_src :self, 'https://api.example.com'
policy.frame_ancestors :none
end
end
class UserContentController < ApplicationController
# Stricter policy for user-generated content
content_security_policy do |policy|
policy.default_src :none
policy.img_src :self, :data
policy.style_src :self
policy.sandbox 'allow-same-origin'
end
def show
@content = UserContent.find(params[:id])
# Additional CSP for this specific content
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-Content-Type-Options'] = 'nosniff'
end
end
class AdminController < ApplicationController
# Admin interface needs additional resources
content_security_policy do |policy|
policy.script_src :self, 'https://admin-tools.example.com'
policy.connect_src :self, 'https://api.example.com', 'wss://realtime.example.com'
end
end
An API-only Rails application requires different security header configuration since it doesn't serve HTML content but still needs protection against certain attacks.
# config/initializers/api_security_headers.rb
Rails.application.config.middleware.use(
Rack::Attack
)
class ApiSecurityHeaders
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
# HSTS still important for API endpoints
headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
# Prevent MIME sniffing on API responses
headers['X-Content-Type-Options'] = 'nosniff'
# No framing needed for API
headers['X-Frame-Options'] = 'DENY'
# Prevent caching sensitive API responses
headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, private'
headers['Pragma'] = 'no-cache'
# API-specific CSP - no scripts needed
headers['Content-Security-Policy'] = "default-src 'none'; frame-ancestors 'none'"
[status, headers, body]
end
end
Rails.application.config.middleware.use ApiSecurityHeaders
A file upload service requires careful header configuration to prevent uploaded files from being executed as scripts or embedding the application in frames.
class UploadsController < ApplicationController
def show
@upload = Upload.find(params[:id])
send_file @upload.file_path,
type: @upload.content_type,
disposition: 'inline',
headers: {
# Prevent MIME sniffing
'X-Content-Type-Options' => 'nosniff',
# Prevent embedding uploaded content
'X-Frame-Options' => 'DENY',
# Strict CSP for uploaded content
'Content-Security-Policy' => "default-src 'none'; style-src 'unsafe-inline'; sandbox",
# Prevent caching user uploads
'Cache-Control' => 'private, no-cache, no-store, must-revalidate'
}
end
def download
@upload = Upload.find(params[:id])
send_file @upload.file_path,
type: @upload.content_type,
disposition: 'attachment', # Force download
filename: @upload.sanitized_filename,
headers: {
'X-Content-Type-Options' => 'nosniff',
'Content-Security-Policy' => "default-src 'none'; sandbox"
}
end
end
Common Pitfalls
The 'unsafe-inline' directive in CSP defeats the primary protection mechanism against XSS attacks. Many developers add this directive when inline scripts break, but this allows all inline scripts including injected malicious code. Use nonces or hashes instead to allow specific inline scripts.
# Incorrect - allows all inline scripts
Content-Security-Policy: script-src 'self' 'unsafe-inline'
# Correct - use nonces for specific inline scripts
Content-Security-Policy: script-src 'self' 'nonce-abc123'
# Or use script hashes
Content-Security-Policy: script-src 'self' 'sha256-hash-of-script'
The 'unsafe-eval' directive allows eval() and related functions that can execute strings as code. This undermines CSP protection and should be avoided. Most modern frameworks and libraries don't require eval(), and those that do can often be replaced with safer alternatives.
Missing the includeSubDomains directive in HSTS leaves subdomains vulnerable to downgrade attacks. An attacker can intercept traffic to subdomains that don't enforce HTTPS and use that as a pivot to compromise the main domain.
# Incomplete - subdomains not protected
Strict-Transport-Security: max-age=31536000
# Complete - all subdomains protected
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Setting HSTS with a short max-age value reduces protection effectiveness. Browsers only enforce HSTS for the duration specified in max-age. Using values less than one year means browsers may connect via HTTP if they haven't visited the site recently. Production applications should use one to two years.
The X-Frame-Options header conflicts with CSP frame-ancestors directive. When both are present, browsers prioritize CSP and ignore X-Frame-Options. Set either one or both for compatibility with older browsers, but ensure they don't contradict each other.
# Contradictory - CSP allows framing but X-Frame-Options denies
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'self'
# Consistent - both deny framing
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
Forgetting to set security headers in development and staging environments means violations are not discovered until production. Use report-only mode in non-production environments to identify issues without breaking functionality.
# Development environment
config.content_security_policy_report_only = true
config.content_security_policy_nonce_generator = ->(request) { SecureRandom.base64(16) }
# Production environment
config.content_security_policy_report_only = false
Overly permissive CSP policies using wildcards undermine security benefits. Directives like script-src https: allow scripts from any HTTPS source, which provides minimal protection against XSS.
# Too permissive - allows any HTTPS script
Content-Security-Policy: script-src https:
# Better - specific trusted domains
Content-Security-Policy: script-src 'self' https://trusted-cdn.com https://analytics.google.com
Not handling CSP violations means security issues go unnoticed. Configure CSP reporting to collect violation reports and monitor them regularly to identify attack attempts or configuration problems.
# Configure CSP reporting
config.csp = {
default_src: %w['self'],
script_src: %w['self'],
report_uri: %w[/csp-violation-report-endpoint]
}
# Controller to handle reports
class CspReportsController < ApplicationController
skip_before_action :verify_authenticity_token
def create
violation = JSON.parse(request.body.read)
Rails.logger.warn "CSP Violation: #{violation['csp-report']}"
# Store in database or send to monitoring service
CspViolation.create!(
document_uri: violation.dig('csp-report', 'document-uri'),
violated_directive: violation.dig('csp-report', 'violated-directive'),
blocked_uri: violation.dig('csp-report', 'blocked-uri'),
original_policy: violation.dig('csp-report', 'original-policy')
)
head :ok
end
end
Setting Referrer-Policy to no-referrer breaks analytics and breaks same-site navigation tracking. Use strict-origin-when-cross-origin to send origin information for cross-origin requests while sending full URLs for same-origin navigation.
Caching responses with security headers can cause stale policies to be served. Use appropriate cache control headers alongside security headers, especially for CSP configurations that may need updates.
Testing Approaches
Automated testing validates security header configuration to catch misconfigurations before deployment. Integration tests verify headers are present with correct values across different controllers and actions.
# spec/requests/security_headers_spec.rb
RSpec.describe "Security Headers", type: :request do
describe "GET /" do
before { get root_path }
it "sets Content-Security-Policy" do
expect(response.headers['Content-Security-Policy']).to include("default-src 'self'")
end
it "sets Strict-Transport-Security" do
hsts = response.headers['Strict-Transport-Security']
expect(hsts).to match(/max-age=\d+/)
expect(hsts).to include('includeSubDomains')
end
it "sets X-Frame-Options" do
expect(response.headers['X-Frame-Options']).to eq('DENY')
end
it "sets X-Content-Type-Options" do
expect(response.headers['X-Content-Type-Options']).to eq('nosniff')
end
it "sets Referrer-Policy" do
expect(response.headers['Referrer-Policy']).to eq('strict-origin-when-cross-origin')
end
end
describe "CSP nonce generation" do
it "generates unique nonces per request" do
get root_path
first_nonce = response.headers['Content-Security-Policy'][/nonce-([^']+)/, 1]
get root_path
second_nonce = response.headers['Content-Security-Policy'][/nonce-([^']+)/, 1]
expect(first_nonce).not_to eq(second_nonce)
end
end
end
Controller-specific tests verify that per-action CSP configurations apply correctly and don't conflict with global settings.
RSpec.describe DashboardController, type: :controller do
describe "GET #analytics" do
it "includes analytics script source in CSP" do
get :analytics
csp = response.headers['Content-Security-Policy']
expect(csp).to include('https://analytics-provider.com')
end
end
describe "GET #user_content" do
it "applies strict CSP for user content" do
get :user_content, params: { id: 1 }
csp = response.headers['Content-Security-Policy']
expect(csp).to include("default-src 'none'")
expect(csp).to include('sandbox')
end
end
end
Browser testing with tools like Selenium or Capybara verifies that CSP doesn't break application functionality while still providing protection.
# spec/system/security_spec.rb
RSpec.describe "Security policies", type: :system do
it "allows trusted scripts to execute" do
visit root_path
# Trusted script should execute
expect(page).to have_css('.js-initialized')
end
it "blocks inline scripts without nonces" do
visit page_with_inline_script_path
# Check console for CSP violations
logs = page.driver.browser.manage.logs.get(:browser)
csp_violations = logs.select { |log| log.message.include?('Content Security Policy') }
expect(csp_violations).to be_present
end
end
Manual testing with browser developer tools confirms proper header configuration and identifies CSP violations. Browser console displays CSP violations with details about the blocked resource and violated directive.
Security scanning tools automate header verification across multiple pages. Tools like Security Headers, Mozilla Observatory, or custom scripts check for missing or misconfigured headers.
# lib/tasks/security_audit.rake
namespace :security do
desc "Audit security headers across key pages"
task audit: :environment do
require 'net/http'
require 'uri'
pages = [
'/',
'/login',
'/dashboard',
'/api/v1/users'
]
results = pages.map do |path|
uri = URI.parse("https://#{ENV['APP_DOMAIN']}#{path}")
response = Net::HTTP.get_response(uri)
{
path: path,
csp: response['Content-Security-Policy'],
hsts: response['Strict-Transport-Security'],
x_frame: response['X-Frame-Options'],
x_content_type: response['X-Content-Type-Options']
}
end
results.each do |result|
puts "\n#{result[:path]}"
result.except(:path).each do |header, value|
status = value.present? ? '✓' : '✗'
puts " #{status} #{header}: #{value || 'MISSING'}"
end
end
end
end
CSP violation reporting in production provides ongoing monitoring of security policy effectiveness. Configure a reporting endpoint and analyze violations to identify attack attempts or legitimate functionality blocked by overly strict policies.
# app/controllers/csp_reports_controller.rb
class CspReportsController < ApplicationController
skip_before_action :verify_authenticity_token
def create
violation = parse_violation(request.body.read)
if legitimate_violation?(violation)
notify_security_team(violation)
store_violation(violation)
end
head :ok
end
private
def parse_violation(body)
JSON.parse(body)['csp-report']
rescue JSON::ParserError
nil
end
def legitimate_violation?(violation)
return false unless violation
# Filter out browser extensions and known false positives
blocked_uri = violation['blocked-uri']
return false if blocked_uri.start_with?('chrome-extension://', 'moz-extension://')
true
end
def store_violation(violation)
CspViolationLog.create!(
document_uri: violation['document-uri'],
violated_directive: violation['violated-directive'],
blocked_uri: violation['blocked-uri'],
source_file: violation['source-file'],
line_number: violation['line-number']
)
end
def notify_security_team(violation)
SecurityMailer.csp_violation(violation).deliver_later
end
end
Reference
Core Security Headers
| Header | Purpose | Common Values |
|---|---|---|
| Content-Security-Policy | Controls resource loading and script execution | default-src 'self'; script-src 'self' https://trusted.com |
| Strict-Transport-Security | Enforces HTTPS connections | max-age=31536000; includeSubDomains; preload |
| X-Frame-Options | Controls page framing | DENY, SAMEORIGIN, ALLOW-FROM uri |
| X-Content-Type-Options | Prevents MIME sniffing | nosniff |
| Referrer-Policy | Controls referrer information | no-referrer, strict-origin-when-cross-origin |
| Permissions-Policy | Controls browser feature access | geolocation=(), camera=(), microphone=() |
CSP Directives
| Directive | Controls | Example |
|---|---|---|
| default-src | Default source for all resource types | default-src 'self' |
| script-src | JavaScript sources | script-src 'self' https://cdn.example.com |
| style-src | CSS sources | style-src 'self' 'unsafe-inline' |
| img-src | Image sources | img-src 'self' data: https: |
| font-src | Font sources | font-src 'self' https://fonts.gstatic.com |
| connect-src | XMLHttpRequest, WebSocket, EventSource | connect-src 'self' https://api.example.com |
| frame-src | iframe sources | frame-src https://www.youtube.com |
| frame-ancestors | Who can embed this page | frame-ancestors 'none' |
| base-uri | Base element URLs | base-uri 'self' |
| form-action | Form submission targets | form-action 'self' |
| object-src | Plugin sources | object-src 'none' |
| media-src | Audio and video sources | media-src 'self' https://media.example.com |
CSP Source Values
| Value | Meaning | Use Case |
|---|---|---|
| 'none' | Block all sources | Disable resource type completely |
| 'self' | Same origin only | Allow resources from same domain |
| 'unsafe-inline' | Allow inline scripts/styles | Avoid in production if possible |
| 'unsafe-eval' | Allow eval and similar functions | Avoid - security risk |
| https: | Any HTTPS source | Too permissive for scripts |
| data: | Data URIs | Useful for inline images |
| 'nonce-value' | Script with matching nonce | Allow specific inline scripts |
| 'sha256-hash' | Resource with matching hash | Allow specific inline content |
| domain.com | Specific domain | Trusted third-party resources |
HSTS Configuration
| Parameter | Purpose | Recommended Value |
|---|---|---|
| max-age | Duration in seconds | 31536000 (1 year) or 63072000 (2 years) |
| includeSubDomains | Apply to all subdomains | Include for comprehensive protection |
| preload | Submit to browser preload lists | Include after testing thoroughly |
Referrer Policy Values
| Value | Behavior | Use Case |
|---|---|---|
| no-referrer | Never send referrer | Maximum privacy |
| no-referrer-when-downgrade | Send for HTTPS to HTTPS | Default in most browsers |
| origin | Send origin only | Balance privacy and functionality |
| origin-when-cross-origin | Full URL for same-origin | Good balance |
| same-origin | Send for same-origin only | Strict internal tracking |
| strict-origin | Origin for HTTPS to HTTPS | Good for most sites |
| strict-origin-when-cross-origin | Combines strict-origin and origin-when-cross-origin | Recommended default |
| unsafe-url | Always send full URL | Avoid - leaks sensitive data |
Rails Configuration Methods
| Method | Purpose | Example |
|---|---|---|
| content_security_policy | Set CSP directives | policy.script_src :self, :https |
| append_content_security_policy_directives | Add to existing policy | append_content_security_policy_directives script_src: ['https://new.com'] |
| override_content_security_policy_directives | Replace directives | override_content_security_policy_directives script_src: [:self] |
| content_security_policy_nonce_generator | Generate nonces | ->(request) { SecureRandom.base64(16) } |
| content_security_policy_report_only | Report mode | true for testing, false for enforcement |
Common CSP Patterns
| Pattern | Configuration | Use Case |
|---|---|---|
| Strict CSP | default-src 'none'; script-src 'nonce-{random}' | Maximum security |
| API responses | default-src 'none'; frame-ancestors 'none' | JSON APIs |
| Static content | default-src 'self'; img-src 'self' https: data: | Simple sites |
| Third-party analytics | script-src 'self' https://analytics.com; connect-src https://api.analytics.com | Marketing sites |
| User content | default-src 'none'; sandbox | User uploads |
| Development mode | script-src 'self' 'unsafe-inline' 'unsafe-eval' | Local development only |