secure_headers 6.3.1 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Gemfile +3 -1
- data/README.md +22 -17
- data/lib/secure_headers/configuration.rb +11 -7
- data/lib/secure_headers/headers/clear_site_data.rb +4 -4
- data/lib/secure_headers/headers/content_security_policy.rb +25 -38
- data/lib/secure_headers/headers/content_security_policy_config.rb +17 -54
- data/lib/secure_headers/headers/cookie.rb +2 -2
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +2 -2
- data/lib/secure_headers/headers/policy_management.rb +54 -12
- data/lib/secure_headers/headers/referrer_policy.rb +1 -1
- data/lib/secure_headers/headers/strict_transport_security.rb +1 -1
- data/lib/secure_headers/headers/x_content_type_options.rb +1 -1
- data/lib/secure_headers/headers/x_download_options.rb +2 -2
- data/lib/secure_headers/headers/x_frame_options.rb +1 -1
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -2
- data/lib/secure_headers/headers/x_xss_protection.rb +2 -2
- data/lib/secure_headers/railtie.rb +5 -5
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers/view_helper.rb +7 -6
- data/lib/tasks/tasks.rake +6 -7
- data/secure_headers.gemspec +17 -7
- metadata +22 -67
- data/.github/ISSUE_TEMPLATE.md +0 -41
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -20
- data/.github/workflows/build.yml +0 -24
- data/.github/workflows/sync.yml +0 -20
- data/.gitignore +0 -13
- data/.rspec +0 -3
- data/.rubocop.yml +0 -4
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/CODE_OF_CONDUCT.md +0 -46
- data/CONTRIBUTING.md +0 -41
- data/Guardfile +0 -13
- data/Rakefile +0 -32
- data/docs/cookies.md +0 -65
- data/docs/hashes.md +0 -64
- data/docs/named_overrides_and_appends.md +0 -104
- data/docs/per_action_configuration.md +0 -141
- data/docs/sinatra.md +0 -25
- data/docs/upgrading-to-3-0.md +0 -42
- data/docs/upgrading-to-4-0.md +0 -35
- data/docs/upgrading-to-5-0.md +0 -15
- data/docs/upgrading-to-6-0.md +0 -50
- data/spec/lib/secure_headers/configuration_spec.rb +0 -121
- data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +0 -87
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +0 -165
- data/spec/lib/secure_headers/headers/cookie_spec.rb +0 -179
- data/spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb +0 -42
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +0 -260
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +0 -91
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +0 -33
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +0 -31
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +0 -29
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +0 -36
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +0 -48
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +0 -47
- data/spec/lib/secure_headers/middleware_spec.rb +0 -117
- data/spec/lib/secure_headers/view_helpers_spec.rb +0 -191
- data/spec/lib/secure_headers_spec.rb +0 -516
- data/spec/spec_helper.rb +0 -64
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 484062b599a7d8ca3ad93c0b91bd6f88b9f80eb7b3f5106fbb4b94b0ae7a82f9
|
|
4
|
+
data.tar.gz: 68c9dc56b62c0d0c77f166e08ae24e6f13dadcda1a9ebaff8b18e4dad0177fa9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 63969aa532b3aa321b2e848764e1ddcbbd9e36fc57bd0e2d98bb4f8ede7c94e32ec6d2aef3f81581d99c1bc623c108d159be635e8df238390304077360fa6f9f
|
|
7
|
+
data.tar.gz: fbc1a3a713680ac487ad16185176ebb5cbdce5416bdd9e765faf0757aa0c10a8ef71b825225bfaf40a66c16ce955edacb2deeedc910e14264bd5b8469be8805d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 6.5.0
|
|
2
|
+
|
|
3
|
+
- CSP: Remove source expression deduplication. (@lgarron) https://github.com/github/secure_headers/pull/499
|
|
4
|
+
|
|
5
|
+
## 6.4.0
|
|
6
|
+
|
|
7
|
+
- CSP: Add support for trusted-types, require-trusted-types-for directive (@JackMc): https://github.com/github/secure_headers/pull/486
|
|
8
|
+
|
|
9
|
+
## 6.3.4
|
|
10
|
+
|
|
11
|
+
- CSP: Do not deduplicate alternate schema source expressions (@keithamus): https://github.com/github/secure_headers/pull/478
|
|
12
|
+
|
|
13
|
+
## 6.3.3
|
|
14
|
+
|
|
15
|
+
Fix hash generation for indented helper methods (@rahearn)
|
|
16
|
+
|
|
17
|
+
## 6.3.2
|
|
18
|
+
|
|
19
|
+
Add support for style-src-attr, style-src-elem, script-src-attr, and script-src-elem directives (@ggalmazor)
|
|
20
|
+
|
|
1
21
|
## 6.3.1
|
|
2
22
|
|
|
3
23
|
Fixes deprecation warnings when running under ruby 2.7
|
data/Gemfile
CHANGED
|
@@ -3,13 +3,15 @@ source "https://rubygems.org"
|
|
|
3
3
|
|
|
4
4
|
gemspec
|
|
5
5
|
|
|
6
|
+
gem "benchmark-ips"
|
|
7
|
+
|
|
6
8
|
group :test do
|
|
7
9
|
gem "coveralls"
|
|
8
10
|
gem "json"
|
|
9
11
|
gem "pry-nav"
|
|
10
12
|
gem "rack"
|
|
11
13
|
gem "rspec"
|
|
12
|
-
gem "rubocop"
|
|
14
|
+
gem "rubocop"
|
|
13
15
|
gem "rubocop-github"
|
|
14
16
|
gem "rubocop-performance"
|
|
15
17
|
gem "term-ansicolor"
|
data/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Secure Headers 
|
|
2
2
|
|
|
3
|
-
**main branch represents
|
|
3
|
+
**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.
|
|
4
4
|
|
|
5
5
|
The gem will automatically apply several headers that are related to security. This includes:
|
|
6
|
-
- Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](
|
|
6
|
+
- Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](https://www.w3.org/TR/CSP2/)
|
|
7
7
|
- https://csp.withgoogle.com
|
|
8
8
|
- https://csp.withgoogle.com/docs/strict-csp.html
|
|
9
9
|
- https://csp-evaluator.withgoogle.com
|
|
@@ -11,11 +11,11 @@ The gem will automatically apply several headers that are related to security.
|
|
|
11
11
|
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options Specification](https://tools.ietf.org/html/rfc7034)
|
|
12
12
|
- X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](https://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
|
|
13
13
|
- X-Content-Type-Options - [Prevent content type sniffing](https://msdn.microsoft.com/library/gg622941\(v=vs.85\).aspx)
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
14
|
+
- x-download-options - [Prevent file downloads opening](https://msdn.microsoft.com/library/jj542450(v=vs.85).aspx)
|
|
15
|
+
- 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)
|
|
16
|
+
- referrer-policy - [Referrer Policy draft](https://w3c.github.io/webappsec-referrer-policy/)
|
|
17
|
+
- 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/).
|
|
18
|
+
- clear-site-data - Clearing browser data for origin. [clear-site-data specification](https://w3c.github.io/webappsec-clear-site-data/).
|
|
19
19
|
|
|
20
20
|
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`.
|
|
21
21
|
|
|
@@ -62,7 +62,6 @@ SecureHeaders::Configuration.default do |config|
|
|
|
62
62
|
# directive values: these values will directly translate into source directives
|
|
63
63
|
default_src: %w('none'),
|
|
64
64
|
base_uri: %w('self'),
|
|
65
|
-
block_all_mixed_content: true, # see http://www.w3.org/TR/mixed-content/
|
|
66
65
|
child_src: %w('self'), # if child-src isn't supported, the value for frame-src will be set.
|
|
67
66
|
connect_src: %w(wss:),
|
|
68
67
|
font_src: %w('self' data:),
|
|
@@ -75,7 +74,11 @@ SecureHeaders::Configuration.default do |config|
|
|
|
75
74
|
sandbox: true, # true and [] will set a maximally restrictive setting
|
|
76
75
|
plugin_types: %w(application/x-shockwave-flash),
|
|
77
76
|
script_src: %w('self'),
|
|
77
|
+
script_src_elem: %w('self'),
|
|
78
|
+
script_src_attr: %w('self'),
|
|
78
79
|
style_src: %w('unsafe-inline'),
|
|
80
|
+
style_src_elem: %w('unsafe-inline'),
|
|
81
|
+
style_src_attr: %w('unsafe-inline'),
|
|
79
82
|
worker_src: %w('self'),
|
|
80
83
|
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
|
|
81
84
|
report_uri: %w(https://report-uri.io/example-csp)
|
|
@@ -88,18 +91,21 @@ SecureHeaders::Configuration.default do |config|
|
|
|
88
91
|
end
|
|
89
92
|
```
|
|
90
93
|
|
|
94
|
+
### Deprecated Configuration Values
|
|
95
|
+
* `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.
|
|
96
|
+
|
|
91
97
|
## Default values
|
|
92
98
|
|
|
93
99
|
All headers except for PublicKeyPins and ClearSiteData have a default value. The default set of headers is:
|
|
94
100
|
|
|
95
101
|
```
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
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'
|
|
103
|
+
strict-transport-security: max-age=631138519
|
|
104
|
+
x-content-type-options: nosniff
|
|
105
|
+
x-download-options: noopen
|
|
106
|
+
x-frame-options: sameorigin
|
|
107
|
+
x-permitted-cross-domain-policies: none
|
|
108
|
+
x-xss-protection: 0
|
|
103
109
|
```
|
|
104
110
|
|
|
105
111
|
## API configurations
|
|
@@ -165,9 +171,8 @@ If you've made a contribution and see your name missing from the list, make a PR
|
|
|
165
171
|
* Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
|
|
166
172
|
* Node.js (express) [helmet](https://github.com/helmetjs/helmet) and [hood](https://github.com/seanmonstar/hood)
|
|
167
173
|
* Node.js (hapi) [blankie](https://github.com/nlf/blankie)
|
|
168
|
-
* J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
|
|
169
174
|
* ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
|
|
170
|
-
* Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
|
|
175
|
+
* 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)
|
|
171
176
|
* Go - [secureheader](https://github.com/kr/secureheader)
|
|
172
177
|
* Elixir [secure_headers](https://github.com/anotherhale/secure_headers)
|
|
173
178
|
* Dropwizard [dropwizard-web-security](https://github.com/palantir/dropwizard-web-security)
|
|
@@ -83,13 +83,17 @@ module SecureHeaders
|
|
|
83
83
|
# can lead to modifying parent objects.
|
|
84
84
|
def deep_copy(config)
|
|
85
85
|
return unless config
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
result = {}
|
|
87
|
+
config.each_pair do |key, value|
|
|
88
|
+
result[key] =
|
|
89
|
+
case value
|
|
90
|
+
when Array
|
|
91
|
+
value.dup
|
|
92
|
+
else
|
|
93
|
+
value
|
|
94
|
+
end
|
|
92
95
|
end
|
|
96
|
+
result
|
|
93
97
|
end
|
|
94
98
|
|
|
95
99
|
# Private: Returns the internal default configuration. This should only
|
|
@@ -252,7 +256,7 @@ module SecureHeaders
|
|
|
252
256
|
end
|
|
253
257
|
end
|
|
254
258
|
|
|
255
|
-
# Configures the
|
|
259
|
+
# Configures the content-security-policy-report-only header. `new_csp` cannot
|
|
256
260
|
# contain `report_only: false` or an error will be raised.
|
|
257
261
|
#
|
|
258
262
|
# NOTE: if csp has not been configured/has the default value when
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module SecureHeaders
|
|
3
3
|
class ClearSiteDataConfigError < StandardError; end
|
|
4
4
|
class ClearSiteData
|
|
5
|
-
HEADER_NAME = "
|
|
5
|
+
HEADER_NAME = "clear-site-data".freeze
|
|
6
6
|
|
|
7
7
|
# Valid `types`
|
|
8
8
|
CACHE = "cache".freeze
|
|
@@ -12,7 +12,7 @@ module SecureHeaders
|
|
|
12
12
|
ALL_TYPES = [CACHE, COOKIES, STORAGE, EXECUTION_CONTEXTS]
|
|
13
13
|
|
|
14
14
|
class << self
|
|
15
|
-
# Public: make an
|
|
15
|
+
# Public: make an clear-site-data header name, value pair
|
|
16
16
|
#
|
|
17
17
|
# Returns nil if not configured, returns header name and value if configured.
|
|
18
18
|
def make_header(config = nil, user_agent = nil)
|
|
@@ -39,8 +39,8 @@ module SecureHeaders
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
# Public: Transform a
|
|
43
|
-
# String that can be used as the value for the
|
|
42
|
+
# Public: Transform a clear-site-data config (an Array of Strings) into a
|
|
43
|
+
# String that can be used as the value for the clear-site-data header.
|
|
44
44
|
#
|
|
45
45
|
# types - An Array of String of types of data to clear.
|
|
46
46
|
#
|
|
@@ -7,26 +7,27 @@ module SecureHeaders
|
|
|
7
7
|
include PolicyManagement
|
|
8
8
|
|
|
9
9
|
def initialize(config = nil)
|
|
10
|
-
@config =
|
|
11
|
-
if config
|
|
12
|
-
|
|
10
|
+
@config =
|
|
11
|
+
if config.is_a?(Hash)
|
|
12
|
+
if config[:report_only]
|
|
13
|
+
ContentSecurityPolicyReportOnlyConfig.new(config || DEFAULT_CONFIG)
|
|
14
|
+
else
|
|
15
|
+
ContentSecurityPolicyConfig.new(config || DEFAULT_CONFIG)
|
|
16
|
+
end
|
|
17
|
+
elsif config.nil?
|
|
18
|
+
ContentSecurityPolicyConfig.new(DEFAULT_CONFIG)
|
|
13
19
|
else
|
|
14
|
-
|
|
20
|
+
config
|
|
15
21
|
end
|
|
16
|
-
elsif config.nil?
|
|
17
|
-
ContentSecurityPolicyConfig.new(DEFAULT_CONFIG)
|
|
18
|
-
else
|
|
19
|
-
config
|
|
20
|
-
end
|
|
21
22
|
|
|
22
|
-
@preserve_schemes = @config
|
|
23
|
-
@script_nonce = @config
|
|
24
|
-
@style_nonce = @config
|
|
23
|
+
@preserve_schemes = @config[:preserve_schemes]
|
|
24
|
+
@script_nonce = @config[:script_nonce]
|
|
25
|
+
@style_nonce = @config[:style_nonce]
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
##
|
|
28
|
-
# Returns the name to use for the header. Either "
|
|
29
|
-
# "
|
|
29
|
+
# Returns the name to use for the header. Either "content-security-policy" or
|
|
30
|
+
# "content-security-policy-report-only"
|
|
30
31
|
def name
|
|
31
32
|
@config.class.const_get(:HEADER_NAME)
|
|
32
33
|
end
|
|
@@ -34,11 +35,12 @@ module SecureHeaders
|
|
|
34
35
|
##
|
|
35
36
|
# Return the value of the CSP header
|
|
36
37
|
def value
|
|
37
|
-
@value ||=
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
@value ||=
|
|
39
|
+
if @config
|
|
40
|
+
build_value
|
|
41
|
+
else
|
|
42
|
+
DEFAULT_VALUE
|
|
43
|
+
end
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
private
|
|
@@ -51,7 +53,9 @@ module SecureHeaders
|
|
|
51
53
|
def build_value
|
|
52
54
|
directives.map do |directive_name|
|
|
53
55
|
case DIRECTIVE_VALUE_TYPES[directive_name]
|
|
54
|
-
when :source_list,
|
|
56
|
+
when :source_list,
|
|
57
|
+
:require_sri_for_list, # require_sri is a simple set of strings that don't need to deal with symbol casing
|
|
58
|
+
:require_trusted_types_for_list
|
|
55
59
|
build_source_list_directive(directive_name)
|
|
56
60
|
when :boolean
|
|
57
61
|
symbol_to_hyphen_case(directive_name) if @config.directive_value(directive_name)
|
|
@@ -129,7 +133,7 @@ module SecureHeaders
|
|
|
129
133
|
unless directive == REPORT_URI || @preserve_schemes
|
|
130
134
|
source_list = strip_source_schemes(source_list)
|
|
131
135
|
end
|
|
132
|
-
|
|
136
|
+
source_list.uniq
|
|
133
137
|
end
|
|
134
138
|
end
|
|
135
139
|
|
|
@@ -147,23 +151,6 @@ module SecureHeaders
|
|
|
147
151
|
end
|
|
148
152
|
end
|
|
149
153
|
|
|
150
|
-
# Removes duplicates and sources that already match an existing wild card.
|
|
151
|
-
#
|
|
152
|
-
# e.g. *.github.com asdf.github.com becomes *.github.com
|
|
153
|
-
def dedup_source_list(sources)
|
|
154
|
-
sources = sources.uniq
|
|
155
|
-
wild_sources = sources.select { |source| source =~ STAR_REGEXP }
|
|
156
|
-
|
|
157
|
-
if wild_sources.any?
|
|
158
|
-
sources.reject do |source|
|
|
159
|
-
!wild_sources.include?(source) &&
|
|
160
|
-
wild_sources.any? { |pattern| File.fnmatch(pattern, source) }
|
|
161
|
-
end
|
|
162
|
-
else
|
|
163
|
-
sources
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
154
|
# Private: append a nonce to the script/style directories if script_nonce
|
|
168
155
|
# or style_nonce are provided.
|
|
169
156
|
def populate_nonces(directive, source_list)
|
|
@@ -1,60 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module SecureHeaders
|
|
3
3
|
module DynamicConfig
|
|
4
|
-
def self.included(base)
|
|
5
|
-
base.send(:attr_reader, *base.attrs)
|
|
6
|
-
base.attrs.each do |attr|
|
|
7
|
-
base.send(:define_method, "#{attr}=") do |value|
|
|
8
|
-
if self.class.attrs.include?(attr)
|
|
9
|
-
write_attribute(attr, value)
|
|
10
|
-
else
|
|
11
|
-
raise ContentSecurityPolicyConfigError, "Unknown config directive: #{attr}=#{value}"
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
4
|
def initialize(hash)
|
|
18
|
-
@
|
|
19
|
-
@block_all_mixed_content = nil
|
|
20
|
-
@child_src = nil
|
|
21
|
-
@connect_src = nil
|
|
22
|
-
@default_src = nil
|
|
23
|
-
@font_src = nil
|
|
24
|
-
@form_action = nil
|
|
25
|
-
@frame_ancestors = nil
|
|
26
|
-
@frame_src = nil
|
|
27
|
-
@img_src = nil
|
|
28
|
-
@manifest_src = nil
|
|
29
|
-
@media_src = nil
|
|
30
|
-
@navigate_to = nil
|
|
31
|
-
@object_src = nil
|
|
32
|
-
@plugin_types = nil
|
|
33
|
-
@prefetch_src = nil
|
|
34
|
-
@preserve_schemes = nil
|
|
35
|
-
@report_only = nil
|
|
36
|
-
@report_uri = nil
|
|
37
|
-
@require_sri_for = nil
|
|
38
|
-
@sandbox = nil
|
|
39
|
-
@script_nonce = nil
|
|
40
|
-
@script_src = nil
|
|
41
|
-
@style_nonce = nil
|
|
42
|
-
@style_src = nil
|
|
43
|
-
@worker_src = nil
|
|
44
|
-
@upgrade_insecure_requests = nil
|
|
45
|
-
@disable_nonce_backwards_compatibility = nil
|
|
5
|
+
@config = {}
|
|
46
6
|
|
|
47
7
|
from_hash(hash)
|
|
48
8
|
end
|
|
49
9
|
|
|
10
|
+
def initialize_copy(hash)
|
|
11
|
+
@config = hash.to_h
|
|
12
|
+
end
|
|
13
|
+
|
|
50
14
|
def update_directive(directive, value)
|
|
51
|
-
|
|
15
|
+
@config[directive] = value
|
|
52
16
|
end
|
|
53
17
|
|
|
54
18
|
def directive_value(directive)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
19
|
+
# No need to check attrs, as we only assign valid keys
|
|
20
|
+
@config[directive]
|
|
58
21
|
end
|
|
59
22
|
|
|
60
23
|
def merge(new_hash)
|
|
@@ -72,10 +35,7 @@ module SecureHeaders
|
|
|
72
35
|
end
|
|
73
36
|
|
|
74
37
|
def to_h
|
|
75
|
-
|
|
76
|
-
value = self.send(key)
|
|
77
|
-
hash[key] = value unless value.nil?
|
|
78
|
-
end
|
|
38
|
+
@config.dup
|
|
79
39
|
end
|
|
80
40
|
|
|
81
41
|
def dup
|
|
@@ -108,16 +68,19 @@ module SecureHeaders
|
|
|
108
68
|
|
|
109
69
|
def write_attribute(attr, value)
|
|
110
70
|
value = value.dup if PolicyManagement::DIRECTIVE_VALUE_TYPES[attr] == :source_list
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
if value.nil?
|
|
72
|
+
@config.delete(attr)
|
|
73
|
+
else
|
|
74
|
+
@config[attr] = value
|
|
75
|
+
end
|
|
113
76
|
end
|
|
114
77
|
end
|
|
115
78
|
|
|
116
79
|
class ContentSecurityPolicyConfigError < StandardError; end
|
|
117
80
|
class ContentSecurityPolicyConfig
|
|
118
|
-
HEADER_NAME = "
|
|
81
|
+
HEADER_NAME = "content-security-policy".freeze
|
|
119
82
|
|
|
120
|
-
ATTRS = PolicyManagement::ALL_DIRECTIVES + PolicyManagement::META_CONFIGS + PolicyManagement::NONCES
|
|
83
|
+
ATTRS = Set.new(PolicyManagement::ALL_DIRECTIVES + PolicyManagement::META_CONFIGS + PolicyManagement::NONCES)
|
|
121
84
|
def self.attrs
|
|
122
85
|
ATTRS
|
|
123
86
|
end
|
|
@@ -144,7 +107,7 @@ module SecureHeaders
|
|
|
144
107
|
end
|
|
145
108
|
|
|
146
109
|
class ContentSecurityPolicyReportOnlyConfig < ContentSecurityPolicyConfig
|
|
147
|
-
HEADER_NAME = "
|
|
110
|
+
HEADER_NAME = "content-security-policy-report-only".freeze
|
|
148
111
|
|
|
149
112
|
def report_only?
|
|
150
113
|
true
|
|
@@ -80,9 +80,9 @@ module SecureHeaders
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def conditionally_flag?(configuration)
|
|
83
|
-
if(Array(configuration[:only]).any? && (Array(configuration[:only]) & parsed_cookie.keys).any?)
|
|
83
|
+
if (Array(configuration[:only]).any? && (Array(configuration[:only]) & parsed_cookie.keys).any?)
|
|
84
84
|
true
|
|
85
|
-
elsif(Array(configuration[:except]).any? && (Array(configuration[:except]) & parsed_cookie.keys).none?)
|
|
85
|
+
elsif (Array(configuration[:except]).any? && (Array(configuration[:except]) & parsed_cookie.keys).none?)
|
|
86
86
|
true
|
|
87
87
|
else
|
|
88
88
|
false
|
|
@@ -3,14 +3,14 @@ module SecureHeaders
|
|
|
3
3
|
class ExpectCertificateTransparencyConfigError < StandardError; end
|
|
4
4
|
|
|
5
5
|
class ExpectCertificateTransparency
|
|
6
|
-
HEADER_NAME = "
|
|
6
|
+
HEADER_NAME = "expect-ct".freeze
|
|
7
7
|
INVALID_CONFIGURATION_ERROR = "config must be a hash.".freeze
|
|
8
8
|
INVALID_ENFORCE_VALUE_ERROR = "enforce must be a boolean".freeze
|
|
9
9
|
REQUIRED_MAX_AGE_ERROR = "max-age is a required directive.".freeze
|
|
10
10
|
INVALID_MAX_AGE_ERROR = "max-age must be a number.".freeze
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
|
-
# Public: Generate a
|
|
13
|
+
# Public: Generate a expect-ct header.
|
|
14
14
|
#
|
|
15
15
|
# Returns nil if not configured, returns header name and value if
|
|
16
16
|
# configured.
|
|
@@ -71,26 +71,44 @@ module SecureHeaders
|
|
|
71
71
|
|
|
72
72
|
# All the directives currently under consideration for CSP level 3.
|
|
73
73
|
# https://w3c.github.io/webappsec/specs/CSP2/
|
|
74
|
-
BLOCK_ALL_MIXED_CONTENT = :block_all_mixed_content
|
|
75
74
|
MANIFEST_SRC = :manifest_src
|
|
76
75
|
NAVIGATE_TO = :navigate_to
|
|
77
76
|
PREFETCH_SRC = :prefetch_src
|
|
78
77
|
REQUIRE_SRI_FOR = :require_sri_for
|
|
79
78
|
UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
|
|
80
79
|
WORKER_SRC = :worker_src
|
|
80
|
+
SCRIPT_SRC_ELEM = :script_src_elem
|
|
81
|
+
SCRIPT_SRC_ATTR = :script_src_attr
|
|
82
|
+
STYLE_SRC_ELEM = :style_src_elem
|
|
83
|
+
STYLE_SRC_ATTR = :style_src_attr
|
|
81
84
|
|
|
82
85
|
DIRECTIVES_3_0 = [
|
|
83
86
|
DIRECTIVES_2_0,
|
|
84
|
-
BLOCK_ALL_MIXED_CONTENT,
|
|
85
87
|
MANIFEST_SRC,
|
|
86
88
|
NAVIGATE_TO,
|
|
87
89
|
PREFETCH_SRC,
|
|
88
90
|
REQUIRE_SRI_FOR,
|
|
89
91
|
WORKER_SRC,
|
|
90
|
-
UPGRADE_INSECURE_REQUESTS
|
|
92
|
+
UPGRADE_INSECURE_REQUESTS,
|
|
93
|
+
SCRIPT_SRC_ELEM,
|
|
94
|
+
SCRIPT_SRC_ATTR,
|
|
95
|
+
STYLE_SRC_ELEM,
|
|
96
|
+
STYLE_SRC_ATTR
|
|
91
97
|
].flatten.freeze
|
|
92
98
|
|
|
93
|
-
|
|
99
|
+
# Experimental directives - these vary greatly in support
|
|
100
|
+
# See MDN for details.
|
|
101
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/content-security-policy/trusted-types
|
|
102
|
+
TRUSTED_TYPES = :trusted_types
|
|
103
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/content-security-policy/require-trusted-types-for
|
|
104
|
+
REQUIRE_TRUSTED_TYPES_FOR = :require_trusted_types_for
|
|
105
|
+
|
|
106
|
+
DIRECTIVES_EXPERIMENTAL = [
|
|
107
|
+
TRUSTED_TYPES,
|
|
108
|
+
REQUIRE_TRUSTED_TYPES_FOR,
|
|
109
|
+
].flatten.freeze
|
|
110
|
+
|
|
111
|
+
ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0 + DIRECTIVES_EXPERIMENTAL).uniq.sort
|
|
94
112
|
|
|
95
113
|
# Think of default-src and report-uri as the beginning and end respectively,
|
|
96
114
|
# everything else is in between.
|
|
@@ -98,7 +116,6 @@ module SecureHeaders
|
|
|
98
116
|
|
|
99
117
|
DIRECTIVE_VALUE_TYPES = {
|
|
100
118
|
BASE_URI => :source_list,
|
|
101
|
-
BLOCK_ALL_MIXED_CONTENT => :boolean,
|
|
102
119
|
CHILD_SRC => :source_list,
|
|
103
120
|
CONNECT_SRC => :source_list,
|
|
104
121
|
DEFAULT_SRC => :source_list,
|
|
@@ -113,11 +130,17 @@ module SecureHeaders
|
|
|
113
130
|
OBJECT_SRC => :source_list,
|
|
114
131
|
PLUGIN_TYPES => :media_type_list,
|
|
115
132
|
REQUIRE_SRI_FOR => :require_sri_for_list,
|
|
133
|
+
REQUIRE_TRUSTED_TYPES_FOR => :require_trusted_types_for_list,
|
|
116
134
|
REPORT_URI => :source_list,
|
|
117
135
|
PREFETCH_SRC => :source_list,
|
|
118
136
|
SANDBOX => :sandbox_list,
|
|
119
137
|
SCRIPT_SRC => :source_list,
|
|
138
|
+
SCRIPT_SRC_ELEM => :source_list,
|
|
139
|
+
SCRIPT_SRC_ATTR => :source_list,
|
|
120
140
|
STYLE_SRC => :source_list,
|
|
141
|
+
STYLE_SRC_ELEM => :source_list,
|
|
142
|
+
STYLE_SRC_ATTR => :source_list,
|
|
143
|
+
TRUSTED_TYPES => :source_list,
|
|
121
144
|
WORKER_SRC => :source_list,
|
|
122
145
|
UPGRADE_INSECURE_REQUESTS => :boolean,
|
|
123
146
|
}.freeze
|
|
@@ -163,6 +186,7 @@ module SecureHeaders
|
|
|
163
186
|
].freeze
|
|
164
187
|
|
|
165
188
|
REQUIRE_SRI_FOR_VALUES = Set.new(%w(script style))
|
|
189
|
+
REQUIRE_TRUSTED_TYPES_FOR_VALUES = Set.new(%w('script'))
|
|
166
190
|
|
|
167
191
|
module ClassMethods
|
|
168
192
|
# Public: generate a header name, value array that is user-agent-aware.
|
|
@@ -214,7 +238,7 @@ module SecureHeaders
|
|
|
214
238
|
#
|
|
215
239
|
# raises an error if the original config is OPT_OUT
|
|
216
240
|
#
|
|
217
|
-
# 1. for non-source-list values (report_only,
|
|
241
|
+
# 1. for non-source-list values (report_only, upgrade_insecure_requests),
|
|
218
242
|
# additions will overwrite the original value.
|
|
219
243
|
# 2. if a value in additions does not exist in the original config, the
|
|
220
244
|
# default-src value is included to match original behavior.
|
|
@@ -258,7 +282,8 @@ module SecureHeaders
|
|
|
258
282
|
source_list?(directive) ||
|
|
259
283
|
sandbox_list?(directive) ||
|
|
260
284
|
media_type_list?(directive) ||
|
|
261
|
-
require_sri_for_list?(directive)
|
|
285
|
+
require_sri_for_list?(directive) ||
|
|
286
|
+
require_trusted_types_for_list?(directive)
|
|
262
287
|
end
|
|
263
288
|
|
|
264
289
|
# For each directive in additions that does not exist in the original config,
|
|
@@ -266,11 +291,12 @@ module SecureHeaders
|
|
|
266
291
|
def populate_fetch_source_with_default!(original, additions)
|
|
267
292
|
# in case we would be appending to an empty directive, fill it with the default-src value
|
|
268
293
|
additions.each_key do |directive|
|
|
269
|
-
directive =
|
|
270
|
-
directive.to_s.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
294
|
+
directive =
|
|
295
|
+
if directive.to_s.end_with?("_nonce")
|
|
296
|
+
directive.to_s.gsub(/_nonce/, "_src").to_sym
|
|
297
|
+
else
|
|
298
|
+
directive
|
|
299
|
+
end
|
|
274
300
|
# Don't set a default if directive has an existing value
|
|
275
301
|
next if original[directive]
|
|
276
302
|
if FETCH_SOURCES.include?(directive)
|
|
@@ -295,6 +321,10 @@ module SecureHeaders
|
|
|
295
321
|
DIRECTIVE_VALUE_TYPES[directive] == :require_sri_for_list
|
|
296
322
|
end
|
|
297
323
|
|
|
324
|
+
def require_trusted_types_for_list?(directive)
|
|
325
|
+
DIRECTIVE_VALUE_TYPES[directive] == :require_trusted_types_for_list
|
|
326
|
+
end
|
|
327
|
+
|
|
298
328
|
# Private: Validates that the configuration has a valid type, or that it is a valid
|
|
299
329
|
# source expression.
|
|
300
330
|
def validate_directive!(directive, value)
|
|
@@ -312,6 +342,8 @@ module SecureHeaders
|
|
|
312
342
|
validate_media_type_expression!(directive, value)
|
|
313
343
|
when :require_sri_for_list
|
|
314
344
|
validate_require_sri_source_expression!(directive, value)
|
|
345
|
+
when :require_trusted_types_for_list
|
|
346
|
+
validate_require_trusted_types_for_source_expression!(directive, value)
|
|
315
347
|
else
|
|
316
348
|
raise ContentSecurityPolicyConfigError.new("Unknown directive #{directive}")
|
|
317
349
|
end
|
|
@@ -356,6 +388,16 @@ module SecureHeaders
|
|
|
356
388
|
end
|
|
357
389
|
end
|
|
358
390
|
|
|
391
|
+
# Private: validates that a require trusted types for expression:
|
|
392
|
+
# 1. is an array of strings
|
|
393
|
+
# 2. is a subset of ["'script'"]
|
|
394
|
+
def validate_require_trusted_types_for_source_expression!(directive, require_trusted_types_for_expression)
|
|
395
|
+
ensure_array_of_strings!(directive, require_trusted_types_for_expression)
|
|
396
|
+
unless require_trusted_types_for_expression.to_set.subset?(REQUIRE_TRUSTED_TYPES_FOR_VALUES)
|
|
397
|
+
raise ContentSecurityPolicyConfigError.new(%(require-trusted-types-for for must be a subset of #{REQUIRE_TRUSTED_TYPES_FOR_VALUES.to_a} but was #{require_trusted_types_for_expression}))
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
359
401
|
# Private: validates that a source expression:
|
|
360
402
|
# 1. is an array of strings
|
|
361
403
|
# 2. does not contain any deprecated, now invalid values (inline, eval, self, none)
|
|
@@ -3,7 +3,7 @@ module SecureHeaders
|
|
|
3
3
|
class STSConfigError < StandardError; end
|
|
4
4
|
|
|
5
5
|
class StrictTransportSecurity
|
|
6
|
-
HEADER_NAME = "
|
|
6
|
+
HEADER_NAME = "strict-transport-security".freeze
|
|
7
7
|
HSTS_MAX_AGE = "631138519"
|
|
8
8
|
DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
|
|
9
9
|
VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
module SecureHeaders
|
|
3
3
|
class XDOConfigError < StandardError; end
|
|
4
4
|
class XDownloadOptions
|
|
5
|
-
HEADER_NAME = "
|
|
5
|
+
HEADER_NAME = "x-download-options".freeze
|
|
6
6
|
DEFAULT_VALUE = "noopen"
|
|
7
7
|
|
|
8
8
|
class << self
|
|
9
|
-
# Public: generate an
|
|
9
|
+
# Public: generate an x-download-options header.
|
|
10
10
|
#
|
|
11
11
|
# Returns a default header if no configuration is provided, or a
|
|
12
12
|
# header name and value based on the config.
|