secure_headers 5.0.1 → 5.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/.ruby-version +1 -1
- data/.travis.yml +3 -3
- data/CHANGELOG.md +20 -0
- data/Gemfile +6 -5
- data/README.md +2 -4
- data/docs/per_action_configuration.md +9 -1
- data/lib/secure_headers/configuration.rb +7 -3
- data/lib/secure_headers/headers/clear_site_data.rb +1 -1
- data/lib/secure_headers/headers/content_security_policy.rb +8 -2
- data/lib/secure_headers/headers/referrer_policy.rb +12 -5
- data/lib/secure_headers/view_helper.rb +50 -2
- data/lib/secure_headers.rb +2 -2
- data/secure_headers.gemspec +1 -10
- data/spec/lib/secure_headers/configuration_spec.rb +18 -13
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +5 -0
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +6 -6
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +19 -1
- data/spec/lib/secure_headers/middleware_spec.rb +2 -2
- data/spec/lib/secure_headers/view_helpers_spec.rb +25 -1
- metadata +4 -10
- data/docs/HPKP.md +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 32ea96aca183a8341bec40b998486beed14d59e8
|
|
4
|
+
data.tar.gz: 3081354f4bd486f5ea49d22f680d1ac2d36de4aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc8c64abdabaab458be788ee09a18984bab17cb8a01ef6eb76c75099cfaa5b8fc5962f5094e39f7067ebece388794e7f53a2e44f2fecdbfe4146a59a981211df
|
|
7
|
+
data.tar.gz: f8c369a5a2ac0e7547e231100c2a946a9204684d4db33206cc3baf0dd786b99c5a6d05cdaaaca5fcb439530a6d385d029c265dbc84f9b6ff665df52ad1191537
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.4.
|
|
1
|
+
2.4.2
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 5.1.0
|
|
2
|
+
|
|
3
|
+
Fixes semicolon injection issue reported by @mvgijssel see https://github.com/twitter/secure_headers/issues/418
|
|
4
|
+
|
|
5
|
+
## 5.0.5
|
|
6
|
+
|
|
7
|
+
A release to deprecate `SecureHeaders::Configuration#get` in prep for 6.x
|
|
8
|
+
|
|
9
|
+
## 5.0.4
|
|
10
|
+
|
|
11
|
+
- Adds support for `nonced_stylesheet_pack_tag` #373 (@paulfri)
|
|
12
|
+
|
|
13
|
+
## 5.0.3
|
|
14
|
+
|
|
15
|
+
- Add nonced versions of Rails link/include tags #372 (@steveh)
|
|
16
|
+
|
|
17
|
+
## 5.0.2
|
|
18
|
+
|
|
19
|
+
- Updates `Referrer-Policy` header to support multiple policy values
|
|
20
|
+
|
|
1
21
|
## 5.0.1
|
|
2
22
|
|
|
3
23
|
- Updates `Expect-CT` header to use a comma separator between directives, as specified in the most current spec.
|
data/Gemfile
CHANGED
|
@@ -5,18 +5,19 @@ gemspec
|
|
|
5
5
|
|
|
6
6
|
group :test do
|
|
7
7
|
gem "coveralls"
|
|
8
|
-
gem "json"
|
|
8
|
+
gem "json"
|
|
9
9
|
gem "pry-nav"
|
|
10
|
-
gem "rack"
|
|
10
|
+
gem "rack"
|
|
11
11
|
gem "rspec"
|
|
12
|
-
gem "rubocop", "
|
|
12
|
+
gem "rubocop", "< 0.68"
|
|
13
13
|
gem "rubocop-github"
|
|
14
|
-
gem "term-ansicolor"
|
|
15
|
-
gem "tins"
|
|
14
|
+
gem "term-ansicolor"
|
|
15
|
+
gem "tins"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
group :guard do
|
|
19
19
|
gem "growl"
|
|
20
20
|
gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24]
|
|
21
21
|
gem "rb-fsevent"
|
|
22
|
+
gem "terminal-notifier-guard"
|
|
22
23
|
end
|
data/README.md
CHANGED
|
@@ -18,7 +18,6 @@ The gem will automatically apply several headers that are related to security.
|
|
|
18
18
|
- X-Download-Options - [Prevent file downloads opening](https://msdn.microsoft.com/library/jj542450(v=vs.85).aspx)
|
|
19
19
|
- 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)
|
|
20
20
|
- Referrer-Policy - [Referrer Policy draft](https://w3c.github.io/webappsec-referrer-policy/)
|
|
21
|
-
- Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorities. [Public Key Pinning Specification](https://tools.ietf.org/html/rfc7469)
|
|
22
21
|
- 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/).
|
|
23
22
|
- Clear-Site-Data - Clearing browser data for origin. [Clear-Site-Data specification](https://w3c.github.io/webappsec-clear-site-data/).
|
|
24
23
|
|
|
@@ -31,7 +30,6 @@ It can also mark all http cookies with the Secure, HttpOnly and SameSite attribu
|
|
|
31
30
|
- [Named overrides and appends](docs/named_overrides_and_appends.md)
|
|
32
31
|
- [Per action configuration](docs/per_action_configuration.md)
|
|
33
32
|
- [Cookies](docs/cookies.md)
|
|
34
|
-
- [HPKP](docs/HPKP.md)
|
|
35
33
|
- [Hashes](docs/hashes.md)
|
|
36
34
|
- [Sinatra Config](docs/sinatra.md)
|
|
37
35
|
|
|
@@ -67,13 +65,13 @@ SecureHeaders::Configuration.default do |config|
|
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
# Add "; preload" and submit the site to hstspreload.org for best protection.
|
|
70
|
-
config.hsts = "max-age=#{
|
|
68
|
+
config.hsts = "max-age=#{1.week.to_i}"
|
|
71
69
|
config.x_frame_options = "DENY"
|
|
72
70
|
config.x_content_type_options = "nosniff"
|
|
73
71
|
config.x_xss_protection = "1; mode=block"
|
|
74
72
|
config.x_download_options = "noopen"
|
|
75
73
|
config.x_permitted_cross_domain_policies = "none"
|
|
76
|
-
config.referrer_policy =
|
|
74
|
+
config.referrer_policy = %w(origin-when-cross-origin strict-origin-when-cross-origin)
|
|
77
75
|
config.csp = {
|
|
78
76
|
# "meta" values. these will shape the header, but the values are not included in the header.
|
|
79
77
|
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
|
|
@@ -66,6 +66,14 @@ body {
|
|
|
66
66
|
background-color: black;
|
|
67
67
|
}
|
|
68
68
|
<% end %>
|
|
69
|
+
|
|
70
|
+
<%= nonced_javascript_include_tag "include.js" %>
|
|
71
|
+
|
|
72
|
+
<%= nonced_javascript_pack_tag "pack.js" %>
|
|
73
|
+
|
|
74
|
+
<%= nonced_stylesheet_link_tag "link.css" %>
|
|
75
|
+
|
|
76
|
+
<%= nonced_stylesheet_pack_tag "pack.css" %>
|
|
69
77
|
```
|
|
70
78
|
|
|
71
79
|
becomes:
|
|
@@ -130,4 +138,4 @@ end
|
|
|
130
138
|
class SessionsController < ApplicationController
|
|
131
139
|
after_action :clear_browser_cache, only: :destroy
|
|
132
140
|
end
|
|
133
|
-
```
|
|
141
|
+
```
|
|
@@ -28,7 +28,7 @@ module SecureHeaders
|
|
|
28
28
|
#
|
|
29
29
|
# Returns: the newly created config
|
|
30
30
|
def override(name, base = DEFAULT_CONFIG, &block)
|
|
31
|
-
unless get(base)
|
|
31
|
+
unless get(base, internal: true)
|
|
32
32
|
raise NotYetConfiguredError, "#{base} policy not yet supplied"
|
|
33
33
|
end
|
|
34
34
|
override = @configurations[base].dup
|
|
@@ -40,7 +40,11 @@ module SecureHeaders
|
|
|
40
40
|
#
|
|
41
41
|
# Returns the configuration with a given name or raises a
|
|
42
42
|
# NotYetConfiguredError if `default` has not been called.
|
|
43
|
-
def get(name = DEFAULT_CONFIG)
|
|
43
|
+
def get(name = DEFAULT_CONFIG, internal: false)
|
|
44
|
+
unless internal
|
|
45
|
+
Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#get` is deprecated. It will be removed in the next major release. Use SecureHeaders::Configuration.dup to retrieve the default config."
|
|
46
|
+
end
|
|
47
|
+
|
|
44
48
|
if @configurations.nil?
|
|
45
49
|
raise NotYetConfiguredError, "Default policy not yet supplied"
|
|
46
50
|
end
|
|
@@ -308,7 +312,7 @@ module SecureHeaders
|
|
|
308
312
|
def generate_csp_headers_for_config(headers, header_key, csp_config)
|
|
309
313
|
unless csp_config.opt_out?
|
|
310
314
|
headers[header_key] = {}
|
|
311
|
-
ContentSecurityPolicy::VARIATIONS.
|
|
315
|
+
ContentSecurityPolicy::VARIATIONS.each_key do |name|
|
|
312
316
|
csp = ContentSecurityPolicy.make_header(csp_config, UserAgent.parse(name))
|
|
313
317
|
headers[header_key][name] = csp.freeze
|
|
314
318
|
end
|
|
@@ -138,8 +138,14 @@ module SecureHeaders
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
if source_list != OPT_OUT && source_list && source_list.any?
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
minified_source_list = minify_source_list(directive, source_list).join(" ")
|
|
142
|
+
|
|
143
|
+
if minified_source_list.include?(";")
|
|
144
|
+
Kernel.warn("#{directive} contains a ; in '#{minified_source_list}' which will raise an error in future versions. It has been replaced with a blank space.")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
escaped_source_list = minified_source_list.gsub(";", " ")
|
|
148
|
+
[symbol_to_hyphen_case(directive), escaped_source_list].join(" ").strip
|
|
143
149
|
end
|
|
144
150
|
end
|
|
145
151
|
|
|
@@ -22,14 +22,21 @@ module SecureHeaders
|
|
|
22
22
|
# Returns a default header if no configuration is provided, or a
|
|
23
23
|
# header name and value based on the config.
|
|
24
24
|
def make_header(config = nil)
|
|
25
|
-
|
|
25
|
+
config ||= DEFAULT_VALUE
|
|
26
|
+
[HEADER_NAME, Array(config).join(", ")]
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def validate_config!(config)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
case config
|
|
31
|
+
when nil, OPT_OUT
|
|
32
|
+
# valid
|
|
33
|
+
when String, Array
|
|
34
|
+
config = Array(config)
|
|
35
|
+
unless config.all? { |t| t.is_a?(String) && VALID_POLICIES.include?(t.downcase) }
|
|
36
|
+
raise ReferrerPolicyConfigError.new("Value can only be one or more of #{VALID_POLICIES.join(", ")}")
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
raise TypeError.new("Must be a string or array of strings. Found #{config.class}: #{config}")
|
|
33
40
|
end
|
|
34
41
|
end
|
|
35
42
|
end
|
|
@@ -7,21 +7,61 @@ module SecureHeaders
|
|
|
7
7
|
class UnexpectedHashedScriptException < StandardError; end
|
|
8
8
|
|
|
9
9
|
# Public: create a style tag using the content security policy nonce.
|
|
10
|
-
# Instructs secure_headers to append a nonce to style
|
|
10
|
+
# Instructs secure_headers to append a nonce to style-src directive.
|
|
11
11
|
#
|
|
12
12
|
# Returns an html-safe style tag with the nonce attribute.
|
|
13
13
|
def nonced_style_tag(content_or_options = {}, &block)
|
|
14
14
|
nonced_tag(:style, content_or_options, block)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# Public: create a stylesheet link tag using the content security policy nonce.
|
|
18
|
+
# Instructs secure_headers to append a nonce to style-src directive.
|
|
19
|
+
#
|
|
20
|
+
# Returns an html-safe link tag with the nonce attribute.
|
|
21
|
+
def nonced_stylesheet_link_tag(*args, &block)
|
|
22
|
+
opts = extract_options(args).merge(nonce: content_security_policy_nonce(:style))
|
|
23
|
+
|
|
24
|
+
stylesheet_link_tag(*args, opts, &block)
|
|
25
|
+
end
|
|
26
|
+
|
|
17
27
|
# Public: create a script tag using the content security policy nonce.
|
|
18
|
-
# Instructs secure_headers to append a nonce to
|
|
28
|
+
# Instructs secure_headers to append a nonce to script-src directive.
|
|
19
29
|
#
|
|
20
30
|
# Returns an html-safe script tag with the nonce attribute.
|
|
21
31
|
def nonced_javascript_tag(content_or_options = {}, &block)
|
|
22
32
|
nonced_tag(:script, content_or_options, block)
|
|
23
33
|
end
|
|
24
34
|
|
|
35
|
+
# Public: create a script src tag using the content security policy nonce.
|
|
36
|
+
# Instructs secure_headers to append a nonce to script-src directive.
|
|
37
|
+
#
|
|
38
|
+
# Returns an html-safe script tag with the nonce attribute.
|
|
39
|
+
def nonced_javascript_include_tag(*args, &block)
|
|
40
|
+
opts = extract_options(args).merge(nonce: content_security_policy_nonce(:script))
|
|
41
|
+
|
|
42
|
+
javascript_include_tag(*args, opts, &block)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Public: create a script Webpacker pack tag using the content security policy nonce.
|
|
46
|
+
# Instructs secure_headers to append a nonce to script-src directive.
|
|
47
|
+
#
|
|
48
|
+
# Returns an html-safe script tag with the nonce attribute.
|
|
49
|
+
def nonced_javascript_pack_tag(*args, &block)
|
|
50
|
+
opts = extract_options(args).merge(nonce: content_security_policy_nonce(:script))
|
|
51
|
+
|
|
52
|
+
javascript_pack_tag(*args, opts, &block)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Public: create a stylesheet Webpacker link tag using the content security policy nonce.
|
|
56
|
+
# Instructs secure_headers to append a nonce to style-src directive.
|
|
57
|
+
#
|
|
58
|
+
# Returns an html-safe link tag with the nonce attribute.
|
|
59
|
+
def nonced_stylesheet_pack_tag(*args, &block)
|
|
60
|
+
opts = extract_options(args).merge(nonce: content_security_policy_nonce(:style))
|
|
61
|
+
|
|
62
|
+
stylesheet_pack_tag(*args, opts, &block)
|
|
63
|
+
end
|
|
64
|
+
|
|
25
65
|
# Public: use the content security policy nonce for this request directly.
|
|
26
66
|
# Instructs secure_headers to append a nonce to style/script-src directives.
|
|
27
67
|
#
|
|
@@ -114,6 +154,14 @@ module SecureHeaders
|
|
|
114
154
|
end
|
|
115
155
|
content_tag type, content, options.merge(nonce: content_security_policy_nonce(type))
|
|
116
156
|
end
|
|
157
|
+
|
|
158
|
+
def extract_options(args)
|
|
159
|
+
if args.last.is_a? Hash
|
|
160
|
+
args.pop
|
|
161
|
+
else
|
|
162
|
+
{}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
117
165
|
end
|
|
118
166
|
end
|
|
119
167
|
|
data/lib/secure_headers.rb
CHANGED
|
@@ -196,7 +196,7 @@ module SecureHeaders
|
|
|
196
196
|
#
|
|
197
197
|
# name - the name of the previously configured override.
|
|
198
198
|
def use_secure_headers_override(request, name)
|
|
199
|
-
if config = Configuration.get(name)
|
|
199
|
+
if config = Configuration.get(name, internal: true)
|
|
200
200
|
override_secure_headers_request_config(request, config)
|
|
201
201
|
else
|
|
202
202
|
raise ArgumentError.new("no override by the name of #{name} has been configured")
|
|
@@ -228,7 +228,7 @@ module SecureHeaders
|
|
|
228
228
|
# Falls back to the global config
|
|
229
229
|
def config_for(request, prevent_dup = false)
|
|
230
230
|
config = request.env[SECURE_HEADERS_CONFIG] ||
|
|
231
|
-
Configuration.get(Configuration::DEFAULT_CONFIG)
|
|
231
|
+
Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
|
|
232
232
|
|
|
233
233
|
|
|
234
234
|
# Global configs are frozen, per-request configs are not. When we're not
|
data/secure_headers.gemspec
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
Gem::Specification.new do |gem|
|
|
4
4
|
gem.name = "secure_headers"
|
|
5
|
-
gem.version = "5.0
|
|
5
|
+
gem.version = "5.1.0"
|
|
6
6
|
gem.authors = ["Neil Matatall"]
|
|
7
7
|
gem.email = ["neil.matatall@gmail.com"]
|
|
8
8
|
gem.description = "Manages application of security headers with many safe defaults."
|
|
@@ -17,13 +17,4 @@ Gem::Specification.new do |gem|
|
|
|
17
17
|
gem.require_paths = ["lib"]
|
|
18
18
|
gem.add_development_dependency "rake"
|
|
19
19
|
gem.add_dependency "useragent", ">= 0.15.0"
|
|
20
|
-
|
|
21
|
-
# TODO: delete this after 4.1 is cut or a number of 4.0.x releases have occurred
|
|
22
|
-
gem.post_install_message = <<-POST_INSTALL
|
|
23
|
-
|
|
24
|
-
**********
|
|
25
|
-
:wave: secure_headers 5.0 introduces a lot of breaking changes (in the name of security!). It's highly likely you will need to update your secure_headers cookie configuration to avoid breaking things. See the upgrade guide for details: https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-4-0.md https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-4-0.md
|
|
26
|
-
**********
|
|
27
|
-
|
|
28
|
-
POST_INSTALL
|
|
29
20
|
end
|
|
@@ -9,15 +9,20 @@ module SecureHeaders
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it "has a default config" do
|
|
12
|
-
expect(Configuration.get(Configuration::DEFAULT_CONFIG)).to_not be_nil
|
|
12
|
+
expect(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)).to_not be_nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "warns when using deprecated internal-ish #get API" do
|
|
16
|
+
expect(Kernel).to receive(:warn).once.with(/`#get` is deprecated/)
|
|
17
|
+
Configuration.get(Configuration::DEFAULT_CONFIG)
|
|
13
18
|
end
|
|
14
19
|
|
|
15
20
|
it "has an 'noop' config" do
|
|
16
|
-
expect(Configuration.get(Configuration::NOOP_CONFIGURATION)).to_not be_nil
|
|
21
|
+
expect(Configuration.get(Configuration::NOOP_CONFIGURATION, internal: true)).to_not be_nil
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
it "precomputes headers upon creation" do
|
|
20
|
-
default_config = Configuration.get(Configuration::DEFAULT_CONFIG)
|
|
25
|
+
default_config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
|
|
21
26
|
header_hash = default_config.cached_headers.each_with_object({}) do |(key, value), hash|
|
|
22
27
|
header_name, header_value = if key == :csp
|
|
23
28
|
value["Chrome"]
|
|
@@ -35,8 +40,8 @@ module SecureHeaders
|
|
|
35
40
|
# do nothing, just copy it
|
|
36
41
|
end
|
|
37
42
|
|
|
38
|
-
config = Configuration.get(:test_override)
|
|
39
|
-
noop = Configuration.get(Configuration::NOOP_CONFIGURATION)
|
|
43
|
+
config = Configuration.get(:test_override, internal: true)
|
|
44
|
+
noop = Configuration.get(Configuration::NOOP_CONFIGURATION, internal: true)
|
|
40
45
|
[:csp, :csp_report_only, :cookies].each do |key|
|
|
41
46
|
expect(config.send(key)).to eq(noop.send(key))
|
|
42
47
|
end
|
|
@@ -47,7 +52,7 @@ module SecureHeaders
|
|
|
47
52
|
config.x_content_type_options = OPT_OUT
|
|
48
53
|
end
|
|
49
54
|
|
|
50
|
-
expect(Configuration.get.cached_headers).to_not eq(Configuration.get(:test_override).cached_headers)
|
|
55
|
+
expect(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).cached_headers).to_not eq(Configuration.get(:test_override, internal: true).cached_headers)
|
|
51
56
|
end
|
|
52
57
|
|
|
53
58
|
it "stores an override of the global config" do
|
|
@@ -55,7 +60,7 @@ module SecureHeaders
|
|
|
55
60
|
config.x_frame_options = "DENY"
|
|
56
61
|
end
|
|
57
62
|
|
|
58
|
-
expect(Configuration.get(:test_override)).to_not be_nil
|
|
63
|
+
expect(Configuration.get(:test_override, internal: true)).to_not be_nil
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
it "deep dup's config values when overriding so the original cannot be modified" do
|
|
@@ -63,8 +68,8 @@ module SecureHeaders
|
|
|
63
68
|
config.csp[:default_src] << "'self'"
|
|
64
69
|
end
|
|
65
70
|
|
|
66
|
-
default = Configuration.get
|
|
67
|
-
override = Configuration.get(:override)
|
|
71
|
+
default = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
|
|
72
|
+
override = Configuration.get(:override, internal: true)
|
|
68
73
|
|
|
69
74
|
expect(override.csp.directive_value(:default_src)).not_to be(default.csp.directive_value(:default_src))
|
|
70
75
|
end
|
|
@@ -78,9 +83,9 @@ module SecureHeaders
|
|
|
78
83
|
config.csp = config.csp.merge(script_src: %w(example.org))
|
|
79
84
|
end
|
|
80
85
|
|
|
81
|
-
original_override = Configuration.get(:override)
|
|
86
|
+
original_override = Configuration.get(:override, internal: true)
|
|
82
87
|
expect(original_override.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self'))
|
|
83
|
-
override_config = Configuration.get(:second_override)
|
|
88
|
+
override_config = Configuration.get(:second_override, internal: true)
|
|
84
89
|
expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
|
|
85
90
|
end
|
|
86
91
|
|
|
@@ -101,7 +106,7 @@ module SecureHeaders
|
|
|
101
106
|
config.cookies = OPT_OUT
|
|
102
107
|
end
|
|
103
108
|
|
|
104
|
-
config = Configuration.get
|
|
109
|
+
config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
|
|
105
110
|
expect(config.cookies).to eq(OPT_OUT)
|
|
106
111
|
end
|
|
107
112
|
|
|
@@ -110,7 +115,7 @@ module SecureHeaders
|
|
|
110
115
|
config.cookies = {httponly: true, secure: true, samesite: {lax: false}}
|
|
111
116
|
end
|
|
112
117
|
|
|
113
|
-
config = Configuration.get
|
|
118
|
+
config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
|
|
114
119
|
expect(config.cookies).to eq({httponly: true, secure: true, samesite: {lax: false}})
|
|
115
120
|
end
|
|
116
121
|
end
|
|
@@ -28,6 +28,11 @@ module SecureHeaders
|
|
|
28
28
|
expect(ContentSecurityPolicy.new.value).to eq("default-src https:; form-action 'self'; img-src https: data: 'self'; object-src 'none'; script-src https:; style-src 'self' 'unsafe-inline' https:")
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
it "deprecates and escapes semicolons in directive source lists" do
|
|
32
|
+
expect(Kernel).to receive(:warn).with("frame_ancestors contains a ; in 'google.com;script-src *;.;' which will raise an error in future versions. It has been replaced with a blank space.")
|
|
33
|
+
expect(ContentSecurityPolicy.new(frame_ancestors: %w(https://google.com;script-src https://*;.;)).value).to eq("frame-ancestors google.com script-src * .")
|
|
34
|
+
end
|
|
35
|
+
|
|
31
36
|
it "discards 'none' values if any other source expressions are present" do
|
|
32
37
|
csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
|
|
33
38
|
expect(csp.value).not_to include("'none'")
|
|
@@ -152,7 +152,7 @@ module SecureHeaders
|
|
|
152
152
|
script_src: %w('self'),
|
|
153
153
|
}
|
|
154
154
|
end
|
|
155
|
-
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, style_src: %w(anothercdn.com))
|
|
155
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, style_src: %w(anothercdn.com))
|
|
156
156
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
157
157
|
expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
|
|
158
158
|
expect(csp.value).to eq("default-src https:; script-src 'self'; style-src https: anothercdn.com")
|
|
@@ -167,7 +167,7 @@ module SecureHeaders
|
|
|
167
167
|
}.freeze
|
|
168
168
|
end
|
|
169
169
|
report_uri = "https://report-uri.io/asdf"
|
|
170
|
-
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
|
|
170
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, report_uri: [report_uri])
|
|
171
171
|
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
|
172
172
|
expect(csp.value).to include("report-uri #{report_uri}")
|
|
173
173
|
end
|
|
@@ -183,7 +183,7 @@ module SecureHeaders
|
|
|
183
183
|
non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
|
|
184
184
|
hash[directive] = %w("http://example.org)
|
|
185
185
|
end
|
|
186
|
-
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
|
|
186
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, non_default_source_additions)
|
|
187
187
|
|
|
188
188
|
ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
|
|
189
189
|
expect(combined_config[directive]).to eq(%w("http://example.org))
|
|
@@ -198,7 +198,7 @@ module SecureHeaders
|
|
|
198
198
|
report_only: false
|
|
199
199
|
}
|
|
200
200
|
end
|
|
201
|
-
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
|
|
201
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, report_only: true)
|
|
202
202
|
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
|
203
203
|
expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
|
|
204
204
|
end
|
|
@@ -211,7 +211,7 @@ module SecureHeaders
|
|
|
211
211
|
block_all_mixed_content: false
|
|
212
212
|
}
|
|
213
213
|
end
|
|
214
|
-
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
|
|
214
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, block_all_mixed_content: true)
|
|
215
215
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
216
216
|
expect(csp.value).to eq("default-src https:; block-all-mixed-content; script-src 'self'")
|
|
217
217
|
end
|
|
@@ -221,7 +221,7 @@ module SecureHeaders
|
|
|
221
221
|
config.csp = OPT_OUT
|
|
222
222
|
end
|
|
223
223
|
expect do
|
|
224
|
-
ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
|
|
224
|
+
ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, script_src: %w(anothercdn.com))
|
|
225
225
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
226
226
|
end
|
|
227
227
|
end
|
|
@@ -5,6 +5,7 @@ module SecureHeaders
|
|
|
5
5
|
describe ReferrerPolicy do
|
|
6
6
|
specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
|
|
7
7
|
specify { expect(ReferrerPolicy.make_header("no-referrer")).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
|
|
8
|
+
specify { expect(ReferrerPolicy.make_header(%w(origin-when-cross-origin strict-origin-when-cross-origin))).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin, strict-origin-when-cross-origin"]) }
|
|
8
9
|
|
|
9
10
|
context "valid configuration values" do
|
|
10
11
|
it "accepts 'no-referrer'" do
|
|
@@ -60,14 +61,31 @@ module SecureHeaders
|
|
|
60
61
|
ReferrerPolicy.validate_config!(nil)
|
|
61
62
|
end.not_to raise_error
|
|
62
63
|
end
|
|
64
|
+
|
|
65
|
+
it "accepts array of policy values" do
|
|
66
|
+
expect do
|
|
67
|
+
ReferrerPolicy.validate_config!(
|
|
68
|
+
%w(
|
|
69
|
+
origin-when-cross-origin
|
|
70
|
+
strict-origin-when-cross-origin
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
end.not_to raise_error
|
|
74
|
+
end
|
|
63
75
|
end
|
|
64
76
|
|
|
65
|
-
context "
|
|
77
|
+
context "invalid configuration values" do
|
|
66
78
|
it "doesn't accept invalid values" do
|
|
67
79
|
expect do
|
|
68
80
|
ReferrerPolicy.validate_config!("open")
|
|
69
81
|
end.to raise_error(ReferrerPolicyConfigError)
|
|
70
82
|
end
|
|
83
|
+
|
|
84
|
+
it "doesn't accept invalid types" do
|
|
85
|
+
expect do
|
|
86
|
+
ReferrerPolicy.validate_config!({})
|
|
87
|
+
end.to raise_error(TypeError)
|
|
88
|
+
end
|
|
71
89
|
end
|
|
72
90
|
end
|
|
73
91
|
end
|
|
@@ -50,7 +50,7 @@ module SecureHeaders
|
|
|
50
50
|
end
|
|
51
51
|
request = Rack::Request.new({})
|
|
52
52
|
SecureHeaders.use_secure_headers_override(request, "my_custom_config")
|
|
53
|
-
expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config"))
|
|
53
|
+
expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config", internal: true))
|
|
54
54
|
_, env = middleware.call request.env
|
|
55
55
|
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
|
|
56
56
|
end
|
|
@@ -66,7 +66,7 @@ module SecureHeaders
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
it "allows opting out of cookie protection with OPT_OUT alone" do
|
|
69
|
-
Configuration.default { |config| config.cookies = OPT_OUT}
|
|
69
|
+
Configuration.default { |config| config.cookies = OPT_OUT }
|
|
70
70
|
|
|
71
71
|
# do NOT make this request https. non-https requests modify a config,
|
|
72
72
|
# causing an exception when operating on OPT_OUT. This ensures we don't
|
|
@@ -39,6 +39,14 @@ class Message < ERB
|
|
|
39
39
|
}
|
|
40
40
|
</style>
|
|
41
41
|
|
|
42
|
+
<%= nonced_javascript_include_tag "include.js", defer: true %>
|
|
43
|
+
|
|
44
|
+
<%= nonced_javascript_pack_tag "pack.js", "otherpack.js", defer: true %>
|
|
45
|
+
|
|
46
|
+
<%= nonced_stylesheet_link_tag "link.css", media: :all %>
|
|
47
|
+
|
|
48
|
+
<%= nonced_stylesheet_pack_tag "pack.css", "otherpack.css", media: :all %>
|
|
49
|
+
|
|
42
50
|
TEMPLATE
|
|
43
51
|
end
|
|
44
52
|
|
|
@@ -59,11 +67,27 @@ TEMPLATE
|
|
|
59
67
|
end
|
|
60
68
|
|
|
61
69
|
if options.is_a?(Hash)
|
|
62
|
-
options = options.map {|k, v| " #{k}=#{v}"}
|
|
70
|
+
options = options.map { |k, v| " #{k}=#{v}" }
|
|
63
71
|
end
|
|
64
72
|
"<#{type}#{options}>#{content}</#{type}>"
|
|
65
73
|
end
|
|
66
74
|
|
|
75
|
+
def javascript_include_tag(*sources, **options)
|
|
76
|
+
sources.map do |source|
|
|
77
|
+
content_tag(:script, nil, options.merge(src: source))
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
alias_method :javascript_pack_tag, :javascript_include_tag
|
|
82
|
+
|
|
83
|
+
def stylesheet_link_tag(*sources, **options)
|
|
84
|
+
sources.map do |source|
|
|
85
|
+
content_tag(:link, nil, options.merge(href: source, rel: "stylesheet", media: "screen"))
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
alias_method :stylesheet_pack_tag, :stylesheet_link_tag
|
|
90
|
+
|
|
67
91
|
def result
|
|
68
92
|
super(binding)
|
|
69
93
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: secure_headers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.0
|
|
4
|
+
version: 5.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Neil Matatall
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2020-01-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -61,7 +61,6 @@ files:
|
|
|
61
61
|
- LICENSE
|
|
62
62
|
- README.md
|
|
63
63
|
- Rakefile
|
|
64
|
-
- docs/HPKP.md
|
|
65
64
|
- docs/cookies.md
|
|
66
65
|
- docs/hashes.md
|
|
67
66
|
- docs/named_overrides_and_appends.md
|
|
@@ -115,12 +114,7 @@ homepage: https://github.com/twitter/secureheaders
|
|
|
115
114
|
licenses:
|
|
116
115
|
- Apache Public License 2.0
|
|
117
116
|
metadata: {}
|
|
118
|
-
post_install_message:
|
|
119
|
-
|
|
120
|
-
**********
|
|
121
|
-
:wave: secure_headers 5.0 introduces a lot of breaking changes (in the name of security!). It's highly likely you will need to update your secure_headers cookie configuration to avoid breaking things. See the upgrade guide for details: https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-4-0.md https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-4-0.md
|
|
122
|
-
**********
|
|
123
|
-
|
|
117
|
+
post_install_message:
|
|
124
118
|
rdoc_options: []
|
|
125
119
|
require_paths:
|
|
126
120
|
- lib
|
|
@@ -136,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
136
130
|
version: '0'
|
|
137
131
|
requirements: []
|
|
138
132
|
rubyforge_project:
|
|
139
|
-
rubygems_version: 2.6.
|
|
133
|
+
rubygems_version: 2.6.13
|
|
140
134
|
signing_key:
|
|
141
135
|
specification_version: 4
|
|
142
136
|
summary: Add easily configured security headers to responses including content-security-policy,
|
data/docs/HPKP.md
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
## HTTP Public Key Pins
|
|
2
|
-
|
|
3
|
-
Be aware that pinning error reporting is governed by the same rules as everything else. If you have a pinning failure that tries to report back to the same origin, by definition this will not work.
|
|
4
|
-
|
|
5
|
-
```ruby
|
|
6
|
-
config.hpkp = {
|
|
7
|
-
max_age: 60.days.to_i, # max_age is a required parameter
|
|
8
|
-
include_subdomains: true, # whether or not to apply pins to subdomains
|
|
9
|
-
# Per the spec, SHA256 hashes are the only currently supported format.
|
|
10
|
-
pins: [
|
|
11
|
-
{sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
|
|
12
|
-
{sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
|
|
13
|
-
],
|
|
14
|
-
report_only: true, # defaults to false (report-only mode)
|
|
15
|
-
report_uri: 'https://report-uri.io/example-hpkp'
|
|
16
|
-
}
|
|
17
|
-
```
|