secure_headers 6.0.0 → 6.7.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 +5 -5
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/build.yml +24 -0
- data/.github/workflows/github-release.yml +28 -0
- data/.rubocop.yml +1 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +47 -1
- data/Gemfile +4 -1
- data/LICENSE +1 -1
- data/README.md +70 -30
- data/docs/cookies.md +5 -4
- data/docs/named_overrides_and_appends.md +3 -6
- data/docs/per_action_configuration.md +2 -4
- data/docs/upgrading-to-6-0.md +4 -4
- data/lib/secure_headers/configuration.rb +24 -16
- data/lib/secure_headers/headers/content_security_policy.rb +34 -41
- data/lib/secure_headers/headers/content_security_policy_config.rb +15 -48
- data/lib/secure_headers/headers/cookie.rb +9 -3
- data/lib/secure_headers/headers/policy_management.rb +92 -17
- data/lib/secure_headers/middleware.rb +0 -6
- data/lib/secure_headers/utils/cookies_config.rb +7 -5
- data/lib/secure_headers/version.rb +5 -0
- data/lib/secure_headers/view_helper.rb +11 -10
- data/lib/secure_headers.rb +3 -11
- data/lib/tasks/tasks.rake +6 -7
- data/secure_headers.gemspec +9 -5
- data/spec/lib/secure_headers/configuration_spec.rb +54 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +98 -8
- data/spec/lib/secure_headers/headers/cookie_spec.rb +22 -25
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +29 -19
- data/spec/lib/secure_headers/middleware_spec.rb +0 -19
- data/spec/lib/secure_headers/view_helpers_spec.rb +5 -4
- data/spec/lib/secure_headers_spec.rb +0 -35
- data/spec/spec_helper.rb +10 -1
- metadata +13 -12
- data/.travis.yml +0 -29
- data/lib/secure_headers/headers/public_key_pins.rb +0 -81
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +0 -38
data/secure_headers.gemspec
CHANGED
@@ -1,16 +1,20 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "secure_headers/version"
|
6
|
+
|
3
7
|
Gem::Specification.new do |gem|
|
4
8
|
gem.name = "secure_headers"
|
5
|
-
gem.version =
|
9
|
+
gem.version = SecureHeaders::VERSION
|
6
10
|
gem.authors = ["Neil Matatall"]
|
7
11
|
gem.email = ["neil.matatall@gmail.com"]
|
8
|
-
gem.
|
9
|
-
gem.
|
12
|
+
gem.summary = "Manages application of security headers with many safe defaults."
|
13
|
+
gem.description = 'Add easily configured security headers to responses
|
10
14
|
including content-security-policy, x-frame-options,
|
11
15
|
strict-transport-security, etc.'
|
12
16
|
gem.homepage = "https://github.com/twitter/secureheaders"
|
13
|
-
gem.license = "
|
17
|
+
gem.license = "MIT"
|
14
18
|
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
15
19
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
16
20
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
@@ -34,6 +34,60 @@ module SecureHeaders
|
|
34
34
|
expect(Configuration.overrides(:test_override)).to_not be_nil
|
35
35
|
end
|
36
36
|
|
37
|
+
describe "#override" do
|
38
|
+
it "raises on configuring an existing override" do
|
39
|
+
set_override = Proc.new {
|
40
|
+
Configuration.override(:test_override) do |config|
|
41
|
+
config.x_frame_options = "DENY"
|
42
|
+
end
|
43
|
+
}
|
44
|
+
|
45
|
+
set_override.call
|
46
|
+
|
47
|
+
expect { set_override.call }
|
48
|
+
.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "raises when a named append with the given name exists" do
|
52
|
+
Configuration.named_append(:test_override) do |config|
|
53
|
+
config.x_frame_options = "DENY"
|
54
|
+
end
|
55
|
+
|
56
|
+
expect do
|
57
|
+
Configuration.override(:test_override) do |config|
|
58
|
+
config.x_frame_options = "SAMEORIGIN"
|
59
|
+
end
|
60
|
+
end.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#named_append" do
|
65
|
+
it "raises on configuring an existing append" do
|
66
|
+
set_override = Proc.new {
|
67
|
+
Configuration.named_append(:test_override) do |config|
|
68
|
+
config.x_frame_options = "DENY"
|
69
|
+
end
|
70
|
+
}
|
71
|
+
|
72
|
+
set_override.call
|
73
|
+
|
74
|
+
expect { set_override.call }
|
75
|
+
.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "raises when an override with the given name exists" do
|
79
|
+
Configuration.override(:test_override) do |config|
|
80
|
+
config.x_frame_options = "DENY"
|
81
|
+
end
|
82
|
+
|
83
|
+
expect do
|
84
|
+
Configuration.named_append(:test_override) do |config|
|
85
|
+
config.x_frame_options = "SAMEORIGIN"
|
86
|
+
end
|
87
|
+
end.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
37
91
|
it "deprecates the secure_cookies configuration" do
|
38
92
|
expect {
|
39
93
|
Configuration.default do |config|
|
@@ -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'")
|
@@ -38,12 +48,12 @@ module SecureHeaders
|
|
38
48
|
expect(csp.value).to eq("default-src * 'unsafe-inline' 'unsafe-eval' data: blob:")
|
39
49
|
end
|
40
50
|
|
41
|
-
it "
|
51
|
+
it "does not minify source expressions based on overlapping wildcards" do
|
42
52
|
config = {
|
43
53
|
default_src: %w(a.example.org b.example.org *.example.org https://*.example.org)
|
44
54
|
}
|
45
55
|
csp = ContentSecurityPolicy.new(config)
|
46
|
-
expect(csp.value).to eq("default-src *.example.org")
|
56
|
+
expect(csp.value).to eq("default-src a.example.org b.example.org *.example.org")
|
47
57
|
end
|
48
58
|
|
49
59
|
it "removes http/s schemes from hosts" do
|
@@ -82,20 +92,30 @@ module SecureHeaders
|
|
82
92
|
end
|
83
93
|
|
84
94
|
it "does add a boolean directive if the value is true" do
|
85
|
-
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"],
|
86
|
-
expect(csp.value).to eq("default-src example.org;
|
95
|
+
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], upgrade_insecure_requests: true)
|
96
|
+
expect(csp.value).to eq("default-src example.org; upgrade-insecure-requests")
|
87
97
|
end
|
88
98
|
|
89
99
|
it "does not add a boolean directive if the value is false" do
|
90
|
-
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"],
|
91
|
-
expect(csp.value).to eq("default-src example.org
|
100
|
+
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], upgrade_insecure_requests: false)
|
101
|
+
expect(csp.value).to eq("default-src example.org")
|
92
102
|
end
|
93
103
|
|
94
|
-
it "
|
95
|
-
csp = ContentSecurityPolicy.new(default_src: %w(example.org
|
104
|
+
it "handles wildcard subdomain with wildcard port" do
|
105
|
+
csp = ContentSecurityPolicy.new(default_src: %w(https://*.example.org:*))
|
106
|
+
expect(csp.value).to eq("default-src *.example.org:*")
|
107
|
+
end
|
108
|
+
|
109
|
+
it "deduplicates source expressions that match exactly (after scheme stripping)" do
|
110
|
+
csp = ContentSecurityPolicy.new(default_src: %w(example.org https://example.org example.org))
|
96
111
|
expect(csp.value).to eq("default-src example.org")
|
97
112
|
end
|
98
113
|
|
114
|
+
it "does not deduplicate non-matching schema source expressions" do
|
115
|
+
csp = ContentSecurityPolicy.new(default_src: %w(*.example.org wss://example.example.org))
|
116
|
+
expect(csp.value).to eq("default-src *.example.org wss://example.example.org")
|
117
|
+
end
|
118
|
+
|
99
119
|
it "creates maximally strict sandbox policy when passed no sandbox token values" do
|
100
120
|
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: [])
|
101
121
|
expect(csp.value).to eq("default-src example.org; sandbox")
|
@@ -116,10 +136,80 @@ module SecureHeaders
|
|
116
136
|
ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
|
117
137
|
end
|
118
138
|
|
139
|
+
it "allows script as a require-sri-src" do
|
140
|
+
csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(script))
|
141
|
+
expect(csp.value).to eq("default-src 'self'; require-sri-for script")
|
142
|
+
end
|
143
|
+
|
144
|
+
it "allows style as a require-sri-src" do
|
145
|
+
csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(style))
|
146
|
+
expect(csp.value).to eq("default-src 'self'; require-sri-for style")
|
147
|
+
end
|
148
|
+
|
149
|
+
it "allows script and style as a require-sri-src" do
|
150
|
+
csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(script style))
|
151
|
+
expect(csp.value).to eq("default-src 'self'; require-sri-for script style")
|
152
|
+
end
|
153
|
+
|
154
|
+
it "allows style as a require-trusted-types-for source" do
|
155
|
+
csp = ContentSecurityPolicy.new(default_src: %w('self'), require_trusted_types_for: %w(script))
|
156
|
+
expect(csp.value).to eq("default-src 'self'; require-trusted-types-for script")
|
157
|
+
end
|
158
|
+
|
159
|
+
it "includes prefetch-src" do
|
160
|
+
csp = ContentSecurityPolicy.new(default_src: %w('self'), prefetch_src: %w(foo.com))
|
161
|
+
expect(csp.value).to eq("default-src 'self'; prefetch-src foo.com")
|
162
|
+
end
|
163
|
+
|
164
|
+
it "includes navigate-to" do
|
165
|
+
csp = ContentSecurityPolicy.new(default_src: %w('self'), navigate_to: %w(foo.com))
|
166
|
+
expect(csp.value).to eq("default-src 'self'; navigate-to foo.com")
|
167
|
+
end
|
168
|
+
|
119
169
|
it "supports strict-dynamic" do
|
120
170
|
csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456})
|
121
171
|
expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456' 'unsafe-inline'")
|
122
172
|
end
|
173
|
+
|
174
|
+
it "supports strict-dynamic and opting out of the appended 'unsafe-inline'" do
|
175
|
+
csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456, disable_nonce_backwards_compatibility: true })
|
176
|
+
expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
|
177
|
+
end
|
178
|
+
|
179
|
+
it "supports script-src-elem directive" do
|
180
|
+
csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_elem: %w('self')})
|
181
|
+
expect(csp.value).to eq("script-src 'self'; script-src-elem 'self'")
|
182
|
+
end
|
183
|
+
|
184
|
+
it "supports script-src-attr directive" do
|
185
|
+
csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_attr: %w('self')})
|
186
|
+
expect(csp.value).to eq("script-src 'self'; script-src-attr 'self'")
|
187
|
+
end
|
188
|
+
|
189
|
+
it "supports style-src-elem directive" do
|
190
|
+
csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_elem: %w('self')})
|
191
|
+
expect(csp.value).to eq("style-src 'self'; style-src-elem 'self'")
|
192
|
+
end
|
193
|
+
|
194
|
+
it "supports style-src-attr directive" do
|
195
|
+
csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')})
|
196
|
+
expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'")
|
197
|
+
end
|
198
|
+
|
199
|
+
it "supports trusted-types directive" do
|
200
|
+
csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy)})
|
201
|
+
expect(csp.value).to eq("trusted-types blahblahpolicy")
|
202
|
+
end
|
203
|
+
|
204
|
+
it "supports trusted-types directive with 'none'" do
|
205
|
+
csp = ContentSecurityPolicy.new({trusted_types: %w('none')})
|
206
|
+
expect(csp.value).to eq("trusted-types 'none'")
|
207
|
+
end
|
208
|
+
|
209
|
+
it "allows duplicate policy names in trusted-types directive" do
|
210
|
+
csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy 'allow-duplicates')})
|
211
|
+
expect(csp.value).to eq("trusted-types blahblahpolicy 'allow-duplicates'")
|
212
|
+
end
|
123
213
|
end
|
124
214
|
end
|
125
215
|
end
|
@@ -68,29 +68,21 @@ module SecureHeaders
|
|
68
68
|
end
|
69
69
|
|
70
70
|
context "SameSite cookies" do
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
it "flags SameSite=Lax when configured with a boolean" do
|
77
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: true}, secure: OPT_OUT, httponly: OPT_OUT)
|
78
|
-
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
|
79
|
-
end
|
80
|
-
|
81
|
-
it "does not flag cookies as SameSite=Lax when excluded" do
|
82
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
83
|
-
expect(cookie.to_s).to eq("_session=thisisatest")
|
84
|
-
end
|
71
|
+
%w(None Lax Strict).each do |flag|
|
72
|
+
it "flags SameSite=#{flag}" do
|
73
|
+
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
74
|
+
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
|
75
|
+
end
|
85
76
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
77
|
+
it "flags SameSite=#{flag} when configured with a boolean" do
|
78
|
+
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => true}, secure: OPT_OUT, httponly: OPT_OUT)
|
79
|
+
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
|
80
|
+
end
|
90
81
|
|
91
|
-
|
92
|
-
|
93
|
-
|
82
|
+
it "does not flag cookies as SameSite=#{flag} when excluded" do
|
83
|
+
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
84
|
+
expect(cookie.to_s).to eq("_session=thisisatest")
|
85
|
+
end
|
94
86
|
end
|
95
87
|
|
96
88
|
it "flags SameSite=Strict when configured with a boolean" do
|
@@ -149,10 +141,15 @@ module SecureHeaders
|
|
149
141
|
end.to raise_error(CookiesConfigError)
|
150
142
|
end
|
151
143
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
144
|
+
cookie_options = %i(none lax strict)
|
145
|
+
cookie_options.each do |flag|
|
146
|
+
(cookie_options - [flag]).each do |other_flag|
|
147
|
+
it "raises an exception when SameSite #{flag} and #{other_flag} enforcement modes are configured with booleans" do
|
148
|
+
expect do
|
149
|
+
Cookie.validate_config!(samesite: { flag => true, other_flag => true})
|
150
|
+
end.to raise_error(CookiesConfigError)
|
151
|
+
end
|
152
|
+
end
|
156
153
|
end
|
157
154
|
|
158
155
|
it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
|
@@ -28,24 +28,34 @@ module SecureHeaders
|
|
28
28
|
|
29
29
|
# directive values: these values will directly translate into source directives
|
30
30
|
default_src: %w(https: 'self'),
|
31
|
-
|
32
|
-
|
31
|
+
|
32
|
+
base_uri: %w('self'),
|
33
33
|
connect_src: %w(wss:),
|
34
|
+
child_src: %w('self' *.twimg.com itunes.apple.com),
|
34
35
|
font_src: %w('self' data:),
|
36
|
+
form_action: %w('self' github.com),
|
37
|
+
frame_ancestors: %w('none'),
|
38
|
+
frame_src: %w('self' *.twimg.com itunes.apple.com),
|
35
39
|
img_src: %w(mycdn.com data:),
|
36
40
|
manifest_src: %w(manifest.com),
|
37
41
|
media_src: %w(utoob.com),
|
42
|
+
navigate_to: %w(netscape.com),
|
38
43
|
object_src: %w('self'),
|
44
|
+
plugin_types: %w(application/x-shockwave-flash),
|
45
|
+
prefetch_src: %w(fetch.com),
|
46
|
+
require_sri_for: %w(script style),
|
47
|
+
require_trusted_types_for: %w('script'),
|
39
48
|
script_src: %w('self'),
|
40
49
|
style_src: %w('unsafe-inline'),
|
41
|
-
worker_src: %w(worker.com),
|
42
|
-
base_uri: %w('self'),
|
43
|
-
form_action: %w('self' github.com),
|
44
|
-
frame_ancestors: %w('none'),
|
45
|
-
plugin_types: %w(application/x-shockwave-flash),
|
46
|
-
block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
|
47
50
|
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
|
48
|
-
|
51
|
+
worker_src: %w(worker.com),
|
52
|
+
script_src_elem: %w(example.com),
|
53
|
+
script_src_attr: %w(example.com),
|
54
|
+
style_src_elem: %w(example.com),
|
55
|
+
style_src_attr: %w(example.com),
|
56
|
+
trusted_types: %w(abcpolicy),
|
57
|
+
|
58
|
+
report_uri: %w(https://example.com/uri-directive),
|
49
59
|
}
|
50
60
|
|
51
61
|
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
|
@@ -81,12 +91,6 @@ module SecureHeaders
|
|
81
91
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
82
92
|
end
|
83
93
|
|
84
|
-
it "requires :block_all_mixed_content to be a boolean value" do
|
85
|
-
expect do
|
86
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
|
87
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
88
|
-
end
|
89
|
-
|
90
94
|
it "requires :upgrade_insecure_requests to be a boolean value" do
|
91
95
|
expect do
|
92
96
|
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
|
@@ -111,6 +115,12 @@ module SecureHeaders
|
|
111
115
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
112
116
|
end
|
113
117
|
|
118
|
+
it "rejects style for trusted types" do
|
119
|
+
expect do
|
120
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(style_src: %w('self'), require_trusted_types_for: %w(script style), trusted_types: %w(abcpolicy))))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
114
124
|
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
|
115
125
|
it "performs light validation on source lists" do
|
116
126
|
expect do
|
@@ -227,18 +237,18 @@ module SecureHeaders
|
|
227
237
|
expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
|
228
238
|
end
|
229
239
|
|
230
|
-
it "overrides the :
|
240
|
+
it "overrides the :upgrade_insecure_requests flag" do
|
231
241
|
Configuration.default do |config|
|
232
242
|
config.csp = {
|
233
243
|
default_src: %w(https:),
|
234
244
|
script_src: %w('self'),
|
235
|
-
|
245
|
+
upgrade_insecure_requests: false
|
236
246
|
}
|
237
247
|
end
|
238
248
|
default_policy = Configuration.dup
|
239
|
-
combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h,
|
249
|
+
combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, upgrade_insecure_requests: true)
|
240
250
|
csp = ContentSecurityPolicy.new(combined_config)
|
241
|
-
expect(csp.value).to eq("default-src https:;
|
251
|
+
expect(csp.value).to eq("default-src https:; script-src 'self'; upgrade-insecure-requests")
|
242
252
|
end
|
243
253
|
|
244
254
|
it "raises an error if appending to a OPT_OUT policy" do
|
@@ -14,25 +14,6 @@ module SecureHeaders
|
|
14
14
|
Configuration.default
|
15
15
|
end
|
16
16
|
|
17
|
-
it "warns if the hpkp report-uri host is the same as the current host" do
|
18
|
-
report_host = "report-uri.io"
|
19
|
-
reset_config
|
20
|
-
Configuration.default do |config|
|
21
|
-
config.hpkp = {
|
22
|
-
max_age: 10000000,
|
23
|
-
pins: [
|
24
|
-
{sha256: "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
|
25
|
-
{sha256: "73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f"}
|
26
|
-
],
|
27
|
-
report_uri: "https://#{report_host}/example-hpkp"
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
|
-
expect(Kernel).to receive(:warn).with(Middleware::HPKP_SAME_HOST_WARNING)
|
32
|
-
|
33
|
-
middleware.call(Rack::MockRequest.env_for("https://#{report_host}", {}))
|
34
|
-
end
|
35
|
-
|
36
17
|
it "sets the headers" do
|
37
18
|
_, env = middleware.call(Rack::MockRequest.env_for("https://looocalhost", {}))
|
38
19
|
expect_default_values(env)
|
@@ -6,7 +6,7 @@ class Message < ERB
|
|
6
6
|
include SecureHeaders::ViewHelpers
|
7
7
|
|
8
8
|
def self.template
|
9
|
-
<<-TEMPLATE
|
9
|
+
<<-TEMPLATE
|
10
10
|
<% hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
|
11
11
|
console.log(1)
|
12
12
|
<% end %>
|
@@ -62,9 +62,10 @@ TEMPLATE
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def content_tag(type, content = nil, options = nil, &block)
|
65
|
-
content =
|
66
|
-
|
67
|
-
|
65
|
+
content =
|
66
|
+
if block_given?
|
67
|
+
capture(block)
|
68
|
+
end
|
68
69
|
|
69
70
|
if options.is_a?(Hash)
|
70
71
|
options = options.map { |k, v| " #{k}=#{v}" }
|
@@ -90,16 +90,6 @@ module SecureHeaders
|
|
90
90
|
Configuration.default do |config|
|
91
91
|
config.csp = { default_src: ["example.com"], script_src: %w('self') }
|
92
92
|
config.csp_report_only = config.csp
|
93
|
-
config.hpkp = {
|
94
|
-
report_only: false,
|
95
|
-
max_age: 10000000,
|
96
|
-
include_subdomains: true,
|
97
|
-
report_uri: "https://report-uri.io/example-hpkp",
|
98
|
-
pins: [
|
99
|
-
{sha256: "abc"},
|
100
|
-
{sha256: "123"}
|
101
|
-
]
|
102
|
-
}
|
103
93
|
end
|
104
94
|
SecureHeaders.opt_out_of_all_protection(request)
|
105
95
|
hash = SecureHeaders.header_hash_for(request)
|
@@ -141,23 +131,6 @@ module SecureHeaders
|
|
141
131
|
expect(SecureHeaders.header_hash_for(plaintext_request)[StrictTransportSecurity::HEADER_NAME]).to be_nil
|
142
132
|
end
|
143
133
|
|
144
|
-
it "does not set the HPKP header if request is over HTTP" do
|
145
|
-
plaintext_request = Rack::Request.new({})
|
146
|
-
Configuration.default do |config|
|
147
|
-
config.hpkp = {
|
148
|
-
max_age: 1_000_000,
|
149
|
-
include_subdomains: true,
|
150
|
-
report_uri: "//example.com/uri-directive",
|
151
|
-
pins: [
|
152
|
-
{ sha256: "abc" },
|
153
|
-
{ sha256: "123" }
|
154
|
-
]
|
155
|
-
}
|
156
|
-
end
|
157
|
-
|
158
|
-
expect(SecureHeaders.header_hash_for(plaintext_request)[PublicKeyPins::HEADER_NAME]).to be_nil
|
159
|
-
end
|
160
|
-
|
161
134
|
context "content security policy" do
|
162
135
|
let(:chrome_request) {
|
163
136
|
Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
|
@@ -531,14 +504,6 @@ module SecureHeaders
|
|
531
504
|
end.to raise_error(ReferrerPolicyConfigError)
|
532
505
|
end
|
533
506
|
|
534
|
-
it "validates your hpkp config upon configuration" do
|
535
|
-
expect do
|
536
|
-
Configuration.default do |config|
|
537
|
-
config.hpkp = "lol"
|
538
|
-
end
|
539
|
-
end.to raise_error(PublicKeyPinsConfigError)
|
540
|
-
end
|
541
|
-
|
542
507
|
it "validates your cookies config upon configuration" do
|
543
508
|
expect do
|
544
509
|
Configuration.default do |config|
|
data/spec/spec_helper.rb
CHANGED
@@ -37,7 +37,6 @@ def expect_default_values(hash)
|
|
37
37
|
expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
|
38
38
|
expect(hash[SecureHeaders::ClearSiteData::HEADER_NAME]).to be_nil
|
39
39
|
expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
|
40
|
-
expect(hash[SecureHeaders::PublicKeyPins::HEADER_NAME]).to be_nil
|
41
40
|
end
|
42
41
|
|
43
42
|
module SecureHeaders
|
@@ -46,10 +45,20 @@ module SecureHeaders
|
|
46
45
|
def clear_default_config
|
47
46
|
remove_instance_variable(:@default_config) if defined?(@default_config)
|
48
47
|
end
|
48
|
+
|
49
|
+
def clear_overrides
|
50
|
+
remove_instance_variable(:@overrides) if defined?(@overrides)
|
51
|
+
end
|
52
|
+
|
53
|
+
def clear_appends
|
54
|
+
remove_instance_variable(:@appends) if defined?(@appends)
|
55
|
+
end
|
49
56
|
end
|
50
57
|
end
|
51
58
|
end
|
52
59
|
|
53
60
|
def reset_config
|
54
61
|
SecureHeaders::Configuration.clear_default_config
|
62
|
+
SecureHeaders::Configuration.clear_overrides
|
63
|
+
SecureHeaders::Configuration.clear_appends
|
55
64
|
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: 6.
|
4
|
+
version: 6.7.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: 2024-08-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -24,7 +24,10 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
description:
|
27
|
+
description: |-
|
28
|
+
Add easily configured security headers to responses
|
29
|
+
including content-security-policy, x-frame-options,
|
30
|
+
strict-transport-security, etc.
|
28
31
|
email:
|
29
32
|
- neil.matatall@gmail.com
|
30
33
|
executables: []
|
@@ -33,12 +36,14 @@ extra_rdoc_files: []
|
|
33
36
|
files:
|
34
37
|
- ".github/ISSUE_TEMPLATE.md"
|
35
38
|
- ".github/PULL_REQUEST_TEMPLATE.md"
|
39
|
+
- ".github/dependabot.yml"
|
40
|
+
- ".github/workflows/build.yml"
|
41
|
+
- ".github/workflows/github-release.yml"
|
36
42
|
- ".gitignore"
|
37
43
|
- ".rspec"
|
38
44
|
- ".rubocop.yml"
|
39
45
|
- ".ruby-gemset"
|
40
46
|
- ".ruby-version"
|
41
|
-
- ".travis.yml"
|
42
47
|
- CHANGELOG.md
|
43
48
|
- CODE_OF_CONDUCT.md
|
44
49
|
- CONTRIBUTING.md
|
@@ -65,7 +70,6 @@ files:
|
|
65
70
|
- lib/secure_headers/headers/cookie.rb
|
66
71
|
- lib/secure_headers/headers/expect_certificate_transparency.rb
|
67
72
|
- lib/secure_headers/headers/policy_management.rb
|
68
|
-
- lib/secure_headers/headers/public_key_pins.rb
|
69
73
|
- lib/secure_headers/headers/referrer_policy.rb
|
70
74
|
- lib/secure_headers/headers/strict_transport_security.rb
|
71
75
|
- lib/secure_headers/headers/x_content_type_options.rb
|
@@ -76,6 +80,7 @@ files:
|
|
76
80
|
- lib/secure_headers/middleware.rb
|
77
81
|
- lib/secure_headers/railtie.rb
|
78
82
|
- lib/secure_headers/utils/cookies_config.rb
|
83
|
+
- lib/secure_headers/version.rb
|
79
84
|
- lib/secure_headers/view_helper.rb
|
80
85
|
- lib/tasks/tasks.rake
|
81
86
|
- secure_headers.gemspec
|
@@ -85,7 +90,6 @@ files:
|
|
85
90
|
- spec/lib/secure_headers/headers/cookie_spec.rb
|
86
91
|
- spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb
|
87
92
|
- spec/lib/secure_headers/headers/policy_management_spec.rb
|
88
|
-
- spec/lib/secure_headers/headers/public_key_pins_spec.rb
|
89
93
|
- spec/lib/secure_headers/headers/referrer_policy_spec.rb
|
90
94
|
- spec/lib/secure_headers/headers/strict_transport_security_spec.rb
|
91
95
|
- spec/lib/secure_headers/headers/x_content_type_options_spec.rb
|
@@ -99,7 +103,7 @@ files:
|
|
99
103
|
- spec/spec_helper.rb
|
100
104
|
homepage: https://github.com/twitter/secureheaders
|
101
105
|
licenses:
|
102
|
-
-
|
106
|
+
- MIT
|
103
107
|
metadata: {}
|
104
108
|
post_install_message:
|
105
109
|
rdoc_options: []
|
@@ -116,12 +120,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
120
|
- !ruby/object:Gem::Version
|
117
121
|
version: '0'
|
118
122
|
requirements: []
|
119
|
-
|
120
|
-
rubygems_version: 2.6.13
|
123
|
+
rubygems_version: 3.0.3.1
|
121
124
|
signing_key:
|
122
125
|
specification_version: 4
|
123
|
-
summary:
|
124
|
-
x-frame-options, strict-transport-security, etc.
|
126
|
+
summary: Manages application of security headers with many safe defaults.
|
125
127
|
test_files:
|
126
128
|
- spec/lib/secure_headers/configuration_spec.rb
|
127
129
|
- spec/lib/secure_headers/headers/clear_site_data_spec.rb
|
@@ -129,7 +131,6 @@ test_files:
|
|
129
131
|
- spec/lib/secure_headers/headers/cookie_spec.rb
|
130
132
|
- spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb
|
131
133
|
- spec/lib/secure_headers/headers/policy_management_spec.rb
|
132
|
-
- spec/lib/secure_headers/headers/public_key_pins_spec.rb
|
133
134
|
- spec/lib/secure_headers/headers/referrer_policy_spec.rb
|
134
135
|
- spec/lib/secure_headers/headers/strict_transport_security_spec.rb
|
135
136
|
- spec/lib/secure_headers/headers/x_content_type_options_spec.rb
|
data/.travis.yml
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
|
3
|
-
rvm:
|
4
|
-
- ruby-head
|
5
|
-
- 2.5.0
|
6
|
-
- 2.4.3
|
7
|
-
- 2.3.6
|
8
|
-
- 2.2.9
|
9
|
-
- jruby-head
|
10
|
-
|
11
|
-
env:
|
12
|
-
- SUITE=rspec spec
|
13
|
-
- SUITE=rubocop
|
14
|
-
|
15
|
-
script: bundle exec $SUITE
|
16
|
-
|
17
|
-
matrix:
|
18
|
-
allow_failures:
|
19
|
-
- rvm: jruby-head
|
20
|
-
- rvm: ruby-head
|
21
|
-
|
22
|
-
before_install:
|
23
|
-
- gem update --system
|
24
|
-
- gem --version
|
25
|
-
- gem update bundler
|
26
|
-
bundler_args: --without guard -j 3
|
27
|
-
|
28
|
-
sudo: false
|
29
|
-
cache: bundler
|