secure_headers 5.0.4 → 5.2.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/.travis.yml +3 -3
- data/CHANGELOG.md +12 -0
- data/Gemfile +2 -1
- data/lib/secure_headers.rb +2 -2
- data/lib/secure_headers/configuration.rb +6 -2
- data/lib/secure_headers/headers/clear_site_data.rb +1 -1
- data/lib/secure_headers/headers/content_security_policy.rb +8 -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 +10 -0
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +6 -6
- data/spec/lib/secure_headers/middleware_spec.rb +2 -2
- data/spec/lib/secure_headers/view_helpers_spec.rb +1 -1
- metadata +3 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b901bf6e9f4127c81ec207e46599054feb18f32
|
4
|
+
data.tar.gz: 642585cfc04901c83ff5d90ea1600f5ef141fec4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e304a7d52e6dbf0a4fd2bb114201ba781e8565fd9ecbd4977e6169f922101f186c7024194c81ecd780376f2e4c18f7af3bd88f98cd2b86301b15de5e4148c44
|
7
|
+
data.tar.gz: 9d5e1e8f35954baea9e4fcec70af22e7f3c2d8b16deaa98d6c882c9373c9a76556a711696b081920d9279df96375dd6e2a473e265c9bfbf2e12a6f9a62da486f
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 5.2.0
|
2
|
+
|
3
|
+
Fixes newline injection issue
|
4
|
+
|
5
|
+
## 5.1.0
|
6
|
+
|
7
|
+
Fixes semicolon injection issue reported by @mvgijssel see https://github.com/twitter/secure_headers/issues/418
|
8
|
+
|
9
|
+
## 5.0.5
|
10
|
+
|
11
|
+
A release to deprecate `SecureHeaders::Configuration#get` in prep for 6.x
|
12
|
+
|
1
13
|
## 5.0.4
|
2
14
|
|
3
15
|
- Adds support for `nonced_stylesheet_pack_tag` #373 (@paulfri)
|
data/Gemfile
CHANGED
@@ -9,7 +9,7 @@ group :test do
|
|
9
9
|
gem "pry-nav"
|
10
10
|
gem "rack"
|
11
11
|
gem "rspec"
|
12
|
-
gem "rubocop"
|
12
|
+
gem "rubocop", "< 0.68"
|
13
13
|
gem "rubocop-github"
|
14
14
|
gem "term-ansicolor"
|
15
15
|
gem "tins"
|
@@ -19,4 +19,5 @@ 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/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
|
@@ -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
|
@@ -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 =~ /(\n|;)/
|
144
|
+
Kernel.warn("#{directive} contains a #{$1} in #{minified_source_list.inspect} 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(/[\n;]/, " ")
|
148
|
+
[symbol_to_hyphen_case(directive), escaped_source_list].join(" ").strip
|
143
149
|
end
|
144
150
|
end
|
145
151
|
|
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.2.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-5-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,16 @@ 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
|
+
|
36
|
+
it "deprecates and escapes semicolons in directive source lists" do
|
37
|
+
expect(Kernel).to receive(:warn).with(%(frame_ancestors contains a \n in "\\nfoo.com\\nhacked" which will raise an error in future versions. It has been replaced with a blank space.))
|
38
|
+
expect(ContentSecurityPolicy.new(frame_ancestors: ["\nfoo.com\nhacked"]).value).to eq("frame-ancestors foo.com hacked")
|
39
|
+
end
|
40
|
+
|
31
41
|
it "discards 'none' values if any other source expressions are present" do
|
32
42
|
csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
|
33
43
|
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
|
@@ -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
|
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.2.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
|
@@ -114,12 +114,7 @@ homepage: https://github.com/twitter/secureheaders
|
|
114
114
|
licenses:
|
115
115
|
- Apache Public License 2.0
|
116
116
|
metadata: {}
|
117
|
-
post_install_message:
|
118
|
-
|
119
|
-
**********
|
120
|
-
: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-5-0.md
|
121
|
-
**********
|
122
|
-
|
117
|
+
post_install_message:
|
123
118
|
rdoc_options: []
|
124
119
|
require_paths:
|
125
120
|
- lib
|