1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
|
# Secure Headers [](https://github.com/github/secure_headers/actions/workflows/build.yml)
**main branch represents 7.x line**. See the [upgrading to 4.x doc](docs/upgrading-to-4-0.md), [upgrading to 5.x doc](docs/upgrading-to-5-0.md), [upgrading to 6.x doc](docs/upgrading-to-6-0.md) or [upgrading to 7.x doc](docs/upgrading-to-7-0.md) for instructions on how to upgrade. Bug fixes should go in the `6.x` branch for now.
The gem will automatically apply several headers that are related to security. This includes:
- Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](https://www.w3.org/TR/CSP2/)
- https://csp.withgoogle.com
- https://csp.withgoogle.com/docs/strict-csp.html
- https://csp-evaluator.withgoogle.com
- HTTP Strict Transport Security (HSTS) - Ensures the browser never visits the http version of a website. Protects from SSLStrip/Firesheep attacks. [HSTS Specification](https://tools.ietf.org/html/rfc6797)
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options Specification](https://tools.ietf.org/html/rfc7034)
- X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](https://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
- X-Content-Type-Options - [Prevent content type sniffing](https://msdn.microsoft.com/library/gg622941\(v=vs.85\).aspx)
- x-download-options - [Prevent file downloads opening](https://msdn.microsoft.com/library/jj542450(v=vs.85).aspx)
- x-permitted-cross-domain-policies - [Restrict Adobe Flash Player's access to data](https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html)
- referrer-policy - [Referrer Policy draft](https://w3c.github.io/webappsec-referrer-policy/)
- expect-ct - Only use certificates that are present in the certificate transparency logs. [expect-ct draft specification](https://datatracker.ietf.org/doc/draft-stark-expect-ct/).
- clear-site-data - Clearing browser data for origin. [clear-site-data specification](https://w3c.github.io/webappsec-clear-site-data/).
It can also mark all http cookies with the Secure, HttpOnly and SameSite attributes. This is on default but can be turned off by using `config.cookies = SecureHeaders::OPT_OUT`.
`secure_headers` is a library with a global config, per request overrides, and rack middleware that enables you customize your application settings.
## Documentation
- [Named overrides and appends](docs/named_overrides_and_appends.md)
- [Per action configuration](docs/per_action_configuration.md)
- [Cookies](docs/cookies.md)
- [Hashes](docs/hashes.md)
- [Sinatra Config](docs/sinatra.md)
## Configuration
If you do not supply a `default` configuration, exceptions will be raised. If you would like to use a default configuration (which is fairly locked down), just call `SecureHeaders::Configuration.default` without any arguments or block.
All `nil` values will fallback to their default values. `SecureHeaders::OPT_OUT` will disable the header entirely.
**Word of caution:** The following is not a default configuration per se. It serves as a sample implementation of the configuration. You should read more about these headers and determine what is appropriate for your requirements.
```ruby
SecureHeaders::Configuration.default do |config|
config.cookies = {
secure: true, # mark all cookies as "Secure"
httponly: true, # mark all cookies as "HttpOnly"
samesite: {
lax: true # mark all cookies as SameSite=lax
}
}
# Add "; preload" and submit the site to hstspreload.org for best protection.
config.hsts = "max-age=#{1.week.to_i}"
config.x_frame_options = "DENY"
config.x_content_type_options = "nosniff"
config.x_xss_protection = "1; mode=block"
config.x_download_options = "noopen"
config.x_permitted_cross_domain_policies = "none"
config.referrer_policy = %w(origin-when-cross-origin strict-origin-when-cross-origin)
config.csp = {
# "meta" values. these will shape the header, but the values are not included in the header.
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
disable_nonce_backwards_compatibility: true, # default: false. If false, `unsafe-inline` will be added automatically when using nonces. If true, it won't. See #403 for why you'd want this.
# directive values: these values will directly translate into source directives
default_src: %w('none'),
base_uri: %w('self'),
child_src: %w('self'), # if child-src isn't supported, the value for frame-src will be set.
connect_src: %w(wss:),
font_src: %w('self' data:),
form_action: %w('self' github.com),
frame_ancestors: %w('none'),
img_src: %w(mycdn.com data:),
manifest_src: %w('self'),
media_src: %w(utoob.com),
object_src: %w('self'),
sandbox: true, # true and [] will set a maximally restrictive setting
plugin_types: %w(application/x-shockwave-flash),
script_src: %w('self'),
script_src_elem: %w('self'),
script_src_attr: %w('self'),
style_src: %w('unsafe-inline'),
style_src_elem: %w('unsafe-inline'),
style_src_attr: %w('unsafe-inline'),
worker_src: %w('self'),
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
report_uri: %w(https://report-uri.io/example-csp)
}
# This is available only from 3.5.0; use the `report_only: true` setting for 3.4.1 and below.
config.csp_report_only = config.csp.merge({
img_src: %w(somewhereelse.com),
report_uri: %w(https://report-uri.io/example-csp-report-only)
})
# Optional: Use the modern report-to directive (with Reporting-Endpoints header)
config.csp = config.csp.merge({
report_to: "csp-endpoint"
})
# When using report-to, configure the reporting endpoints header
config.reporting_endpoints = {
"csp-endpoint": "https://report-uri.io/example-csp",
"csp-report-only": "https://report-uri.io/example-csp-report-only"
}
end
```
### CSP Reporting
SecureHeaders supports both the legacy `report-uri` and the modern `report-to` directives for CSP violation reporting:
#### report-uri (Legacy)
The `report-uri` directive sends violations to a URL endpoint. It's widely supported but limited to POST requests with JSON payloads.
```ruby
config.csp = {
default_src: %w('self'),
report_uri: %w(https://example.com/csp-report)
}
```
#### report-to (Modern)
The `report-to` directive specifies a named reporting endpoint defined in the `Reporting-Endpoints` header. This enables more flexible reporting through the HTTP Reporting API standard.
```ruby
config.csp = {
default_src: %w('self'),
report_to: "csp-endpoint"
}
config.reporting_endpoints = {
"csp-endpoint": "https://example.com/reports"
}
```
**Recommendation:** Use both `report-uri` and `report-to` for maximum compatibility while transitioning to the modern approach.
### Deprecated Configuration Values
* `block_all_mixed_content` - this value is deprecated in favor of `upgrade_insecure_requests`. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/block-all-mixed-content for more information.
## Default values
All headers except for PublicKeyPins and ClearSiteData have a default value. The default set of headers is:
```
content-security-policy: default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
strict-transport-security: max-age=631138519
x-content-type-options: nosniff
x-download-options: noopen
x-frame-options: sameorigin
x-permitted-cross-domain-policies: none
x-xss-protection: 0
```
## API configurations
Which headers you decide to use for API responses is entirely a personal choice. Things like X-Frame-Options seem to have no place in an API response and would be wasting bytes. While this is true, browsers can do funky things with non-html responses. At the minimum, we suggest CSP:
```ruby
SecureHeaders::Configuration.override(:api) do |config|
config.csp = { default_src: 'none' }
config.hsts = SecureHeaders::OPT_OUT
config.x_frame_options = SecureHeaders::OPT_OUT
config.x_content_type_options = SecureHeaders::OPT_OUT
config.x_xss_protection = SecureHeaders::OPT_OUT
config.x_permitted_cross_domain_policies = SecureHeaders::OPT_OUT
end
```
However, I would consider these headers anyways depending on your load and bandwidth requirements.
## Disabling secure_headers
If you want to disable `secure_headers` entirely (e.g., for specific environments or deployment scenarios), you can use `Configuration.disable!`:
```ruby
if ENV["ENABLE_STRICT_HEADERS"]
SecureHeaders::Configuration.default do |config|
# your configuration here
end
else
SecureHeaders::Configuration.disable!
end
```
**Important**: This configuration must be set during application startup (e.g., in an initializer). Once you call either `Configuration.default` or `Configuration.disable!`, the choice cannot be changed at runtime. Attempting to call `disable!` after `default` (or vice versa) will raise an `AlreadyConfiguredError`.
When disabled, no security headers will be set by the gem. This is useful when:
- You're gradually rolling out secure_headers across different customers or deployments
- You need to migrate existing custom headers to secure_headers
- You want environment-specific control over security headers
Note: When `disable!` is used, you don't need to configure a default configuration. The gem will not raise a `NotYetConfiguredError`.
## Acknowledgements
This project originated within the Security team at Twitter. An archived fork from the point of transition is here: https://github.com/twitter-archive/secure_headers.
Contributors include:
* Neil Matatall @oreoshake
* Chris Aniszczyk
* Artur Dryomov
* Bjørn Mæland
* Arthur Chiu
* Jonathan Viney
* Jeffrey Horn
* David Collazo
* Brendon Murphy
* William Makley
* Reed Loden
* Noah Kantrowitz
* Wyatt Anderson
* Salimane Adjao Moustapha
* Francois Chagnon
* Jeff Hodges
* Ian Melven
* Darío Javier Cravero
* Logan Hasson
* Raul E Rangel
* Steve Agalloco
* Nate Collings
* Josh Kalderimis
* Alex Kwiatkowski
* Julich Mera
* Jesse Storimer
* Tom Daniels
* Kolja Dummann
* Jean-Philippe Doyle
* Blake Hitchcock
* vanderhoorn
* orthographic-pedant
* Narsimham Chelluri
If you've made a contribution and see your name missing from the list, make a PR and add it!
## Similar libraries
* Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
* Node.js (express) [helmet](https://github.com/helmetjs/helmet) and [hood](https://github.com/seanmonstar/hood)
* Node.js (hapi) [blankie](https://github.com/nlf/blankie)
* ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
* Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security), [secure](https://github.com/TypeError/secure)
* Go - [secureheader](https://github.com/kr/secureheader)
* Elixir [secure_headers](https://github.com/anotherhale/secure_headers)
* Dropwizard [dropwizard-web-security](https://github.com/palantir/dropwizard-web-security)
* Ember.js [ember-cli-content-security-policy](https://github.com/rwjblue/ember-cli-content-security-policy/)
* PHP [secure-headers](https://github.com/BePsvPT/secure-headers)
|