secure_headers 3.2.0 → 3.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 +4 -4
- data/.github/ISSUE_TEMPLATE.md +41 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +2 -0
- data/CHANGELOG.md +140 -1
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +41 -0
- data/Gemfile +3 -1
- data/LICENSE +4 -199
- data/README.md +74 -334
- data/docs/HPKP.md +17 -0
- data/docs/cookies.md +50 -0
- data/docs/hashes.md +64 -0
- data/docs/named_overrides_and_appends.md +107 -0
- data/docs/per_action_configuration.md +105 -0
- data/docs/sinatra.md +25 -0
- data/lib/secure_headers/configuration.rb +120 -39
- data/lib/secure_headers/headers/clear_site_data.rb +54 -0
- data/lib/secure_headers/headers/content_security_policy.rb +89 -32
- data/lib/secure_headers/headers/content_security_policy_config.rb +161 -0
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
- data/lib/secure_headers/headers/policy_management.rb +84 -49
- data/lib/secure_headers/headers/public_key_pins.rb +1 -1
- data/lib/secure_headers/headers/referrer_policy.rb +36 -0
- 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 +1 -1
- data/lib/secure_headers/headers/x_frame_options.rb +1 -1
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +1 -1
- data/lib/secure_headers/headers/x_xss_protection.rb +1 -1
- data/lib/secure_headers/middleware.rb +6 -0
- data/lib/secure_headers/railtie.rb +1 -1
- data/lib/secure_headers/view_helper.rb +13 -7
- data/lib/secure_headers.rb +138 -55
- data/lib/tasks/tasks.rake +5 -4
- data/secure_headers.gemspec +2 -2
- data/spec/lib/secure_headers/configuration_spec.rb +5 -5
- data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +86 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +69 -12
- data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +28 -36
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +72 -0
- data/spec/lib/secure_headers/middleware_spec.rb +31 -1
- data/spec/lib/secure_headers/view_helpers_spec.rb +19 -7
- data/spec/lib/secure_headers_spec.rb +344 -44
- data/spec/spec_helper.rb +8 -3
- data/upgrading-to-3-0.md +13 -10
- metadata +24 -4
|
@@ -14,17 +14,17 @@ module SecureHeaders
|
|
|
14
14
|
|
|
15
15
|
describe "#name" do
|
|
16
16
|
context "when in report-only mode" do
|
|
17
|
-
specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(
|
|
17
|
+
specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME) }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
context "when in enforce mode" do
|
|
21
|
-
specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(
|
|
21
|
+
specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicyConfig::HEADER_NAME) }
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
describe "#value" do
|
|
26
26
|
it "discards 'none' values if any other source expressions are present" do
|
|
27
|
-
csp = ContentSecurityPolicy.new(default_opts.merge(
|
|
27
|
+
csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
|
|
28
28
|
expect(csp.value).not_to include("'none'")
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -71,42 +71,99 @@ module SecureHeaders
|
|
|
71
71
|
expect(csp.value).to eq("default-src example.org")
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
it "does add a boolean directive if the value is true" do
|
|
75
|
+
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: true)
|
|
76
|
+
expect(csp.value).to eq("default-src example.org; block-all-mixed-content; upgrade-insecure-requests")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "does not add a boolean directive if the value is false" do
|
|
80
|
+
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: false)
|
|
81
|
+
expect(csp.value).to eq("default-src example.org; block-all-mixed-content")
|
|
82
|
+
end
|
|
83
|
+
|
|
74
84
|
it "deduplicates any source expressions" do
|
|
75
85
|
csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
|
|
76
86
|
expect(csp.value).to eq("default-src example.org")
|
|
77
87
|
end
|
|
78
88
|
|
|
89
|
+
it "does not emit a warning when using frame-src" do
|
|
90
|
+
expect(Kernel).to_not receive(:warn)
|
|
91
|
+
ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "emits a warning when child-src and frame-src are supplied but are not equal" do
|
|
95
|
+
expect(Kernel).to receive(:warn).with(/both :child_src and :frame_src supplied and do not match./)
|
|
96
|
+
ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "will still set inconsistent child/frame-src values to be less surprising" do
|
|
100
|
+
expect(Kernel).to receive(:warn).at_least(:once)
|
|
101
|
+
firefox = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox]).value
|
|
102
|
+
firefox_transitional = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox46]).value
|
|
103
|
+
expect(firefox).not_to eq(firefox_transitional)
|
|
104
|
+
expect(firefox).to match(/frame-src/)
|
|
105
|
+
expect(firefox).not_to match(/child-src/)
|
|
106
|
+
expect(firefox_transitional).to match(/child-src/)
|
|
107
|
+
expect(firefox_transitional).not_to match(/frame-src/)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "supports strict-dynamic" do
|
|
111
|
+
csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456}, USER_AGENTS[:chrome])
|
|
112
|
+
expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
|
|
113
|
+
end
|
|
114
|
+
|
|
79
115
|
context "browser sniffing" do
|
|
80
116
|
let (:complex_opts) do
|
|
81
|
-
ContentSecurityPolicy::ALL_DIRECTIVES.each_with_object({}) do |directive, hash|
|
|
82
|
-
hash[directive] =
|
|
117
|
+
(ContentSecurityPolicy::ALL_DIRECTIVES - [:frame_src]).each_with_object({}) do |directive, hash|
|
|
118
|
+
hash[directive] = ["#{directive.to_s.gsub("_", "-")}.com"]
|
|
83
119
|
end.merge({
|
|
84
120
|
block_all_mixed_content: true,
|
|
85
121
|
upgrade_insecure_requests: true,
|
|
86
|
-
|
|
87
|
-
script_src: %w('self'),
|
|
122
|
+
script_src: %w(script-src.com),
|
|
88
123
|
script_nonce: 123456
|
|
89
124
|
})
|
|
90
125
|
end
|
|
91
126
|
|
|
92
127
|
it "does not filter any directives for Chrome" do
|
|
93
128
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
|
|
94
|
-
expect(policy.value).to eq("default-src
|
|
129
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
95
130
|
end
|
|
96
131
|
|
|
97
132
|
it "does not filter any directives for Opera" do
|
|
98
133
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
|
|
99
|
-
expect(policy.value).to eq("default-src
|
|
134
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
100
135
|
end
|
|
101
136
|
|
|
102
137
|
it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
|
|
103
138
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
|
|
104
|
-
expect(policy.value).to eq("default-src
|
|
139
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
|
|
143
|
+
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
|
|
144
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
|
|
148
|
+
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
|
|
149
|
+
expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
|
|
105
150
|
end
|
|
106
151
|
|
|
107
|
-
it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
|
|
152
|
+
it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
|
|
108
153
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
|
|
109
|
-
expect(policy.value).to eq("default-src
|
|
154
|
+
expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
|
|
158
|
+
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
|
|
159
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; report-uri report-uri.com")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "falls back to standard Firefox defaults when the useragent version is not present" do
|
|
163
|
+
ua = USER_AGENTS[:firefox].dup
|
|
164
|
+
allow(ua).to receive(:version).and_return(nil)
|
|
165
|
+
policy = ContentSecurityPolicy.new(complex_opts, ua)
|
|
166
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
110
167
|
end
|
|
111
168
|
end
|
|
112
169
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
module SecureHeaders
|
|
5
|
+
describe ExpectCertificateTransparency do
|
|
6
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce; max-age=1234") }
|
|
7
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") }
|
|
8
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: "yolocopter").value).to eq("max-age=1234") }
|
|
9
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, report_uri: "https://report-uri.io/expect-ct").value).to eq("max-age=1234; report-uri=\"https://report-uri.io/expect-ct\"") }
|
|
10
|
+
specify do
|
|
11
|
+
config = { enforce: true, max_age: 1234, report_uri: "https://report-uri.io/expect-ct" }
|
|
12
|
+
header_value = "enforce; max-age=1234; report-uri=\"https://report-uri.io/expect-ct\""
|
|
13
|
+
expect(ExpectCertificateTransparency.new(config).value).to eq(header_value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "with an invalid configuration" do
|
|
17
|
+
it "raises an exception when configuration isn't a hash" do
|
|
18
|
+
expect do
|
|
19
|
+
ExpectCertificateTransparency.validate_config!(%w(a))
|
|
20
|
+
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "raises an exception when max-age is not provided" do
|
|
24
|
+
expect do
|
|
25
|
+
ExpectCertificateTransparency.validate_config!(foo: "bar")
|
|
26
|
+
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "raises an exception with an invalid max-age" do
|
|
30
|
+
expect do
|
|
31
|
+
ExpectCertificateTransparency.validate_config!(max_age: "abc123")
|
|
32
|
+
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "raises an exception with an invalid enforce value" do
|
|
36
|
+
expect do
|
|
37
|
+
ExpectCertificateTransparency.validate_config!(enforce: "brokenstring")
|
|
38
|
+
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -23,15 +23,16 @@ module SecureHeaders
|
|
|
23
23
|
# directive values: these values will directly translate into source directives
|
|
24
24
|
default_src: %w(https: 'self'),
|
|
25
25
|
frame_src: %w('self' *.twimg.com itunes.apple.com),
|
|
26
|
-
|
|
26
|
+
child_src: %w('self' *.twimg.com itunes.apple.com),
|
|
27
|
+
connect_src: %w(wss:),
|
|
27
28
|
font_src: %w('self' data:),
|
|
28
29
|
img_src: %w(mycdn.com data:),
|
|
30
|
+
manifest_src: %w(manifest.com),
|
|
29
31
|
media_src: %w(utoob.com),
|
|
30
32
|
object_src: %w('self'),
|
|
31
33
|
script_src: %w('self'),
|
|
32
34
|
style_src: %w('unsafe-inline'),
|
|
33
35
|
base_uri: %w('self'),
|
|
34
|
-
child_src: %w('self'),
|
|
35
36
|
form_action: %w('self' github.com),
|
|
36
37
|
frame_ancestors: %w('none'),
|
|
37
38
|
plugin_types: %w(application/x-shockwave-flash),
|
|
@@ -40,70 +41,75 @@ module SecureHeaders
|
|
|
40
41
|
report_uri: %w(https://example.com/uri-directive)
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
it "requires a :default_src value" do
|
|
47
48
|
expect do
|
|
48
|
-
|
|
49
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
|
|
49
50
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
it "requires :report_only to be a truthy value" do
|
|
53
54
|
expect do
|
|
54
|
-
|
|
55
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
|
|
55
56
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
it "requires :preserve_schemes to be a truthy value" do
|
|
59
60
|
expect do
|
|
60
|
-
|
|
61
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(preserve_schemes: "steve")))
|
|
61
62
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
it "requires :block_all_mixed_content to be a boolean value" do
|
|
65
66
|
expect do
|
|
66
|
-
|
|
67
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
|
|
67
68
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
it "requires :upgrade_insecure_requests to be a boolean value" do
|
|
71
72
|
expect do
|
|
72
|
-
|
|
73
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
|
|
73
74
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
it "requires all source lists to be an array of strings" do
|
|
77
78
|
expect do
|
|
78
|
-
|
|
79
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: "steve"))
|
|
79
80
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
it "allows nil values" do
|
|
83
84
|
expect do
|
|
84
|
-
|
|
85
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: ["https:", nil]))
|
|
85
86
|
end.to_not raise_error
|
|
86
87
|
end
|
|
87
88
|
|
|
88
89
|
it "rejects unknown directives / config" do
|
|
89
90
|
expect do
|
|
90
|
-
|
|
91
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), default_src_totally_mispelled: "steve"))
|
|
91
92
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
92
93
|
end
|
|
93
94
|
|
|
94
95
|
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
|
|
95
96
|
it "performs light validation on source lists" do
|
|
96
97
|
expect do
|
|
97
|
-
|
|
98
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval)))
|
|
98
99
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
99
100
|
end
|
|
100
101
|
end
|
|
101
102
|
|
|
102
103
|
describe "#combine_policies" do
|
|
103
104
|
it "combines the default-src value with the override if the directive was unconfigured" do
|
|
104
|
-
|
|
105
|
+
Configuration.default do |config|
|
|
106
|
+
config.csp = {
|
|
107
|
+
default_src: %w(https:)
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
|
|
105
111
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
106
|
-
expect(csp.name).to eq(
|
|
112
|
+
expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
|
|
107
113
|
expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
|
|
108
114
|
end
|
|
109
115
|
|
|
@@ -115,7 +121,7 @@ module SecureHeaders
|
|
|
115
121
|
}.freeze
|
|
116
122
|
end
|
|
117
123
|
report_uri = "https://report-uri.io/asdf"
|
|
118
|
-
combined_config =
|
|
124
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
|
|
119
125
|
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
|
120
126
|
expect(csp.value).to include("report-uri #{report_uri}")
|
|
121
127
|
end
|
|
@@ -127,12 +133,12 @@ module SecureHeaders
|
|
|
127
133
|
report_only: false
|
|
128
134
|
}.freeze
|
|
129
135
|
end
|
|
130
|
-
non_default_source_additions =
|
|
136
|
+
non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
|
|
131
137
|
hash[directive] = %w("http://example.org)
|
|
132
138
|
end
|
|
133
|
-
combined_config =
|
|
139
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
|
|
134
140
|
|
|
135
|
-
|
|
141
|
+
ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
|
|
136
142
|
expect(combined_config[directive]).to eq(%w("http://example.org))
|
|
137
143
|
end
|
|
138
144
|
|
|
@@ -146,9 +152,9 @@ module SecureHeaders
|
|
|
146
152
|
report_only: false
|
|
147
153
|
}
|
|
148
154
|
end
|
|
149
|
-
combined_config =
|
|
155
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
|
|
150
156
|
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
|
151
|
-
expect(csp.name).to eq(
|
|
157
|
+
expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
|
|
152
158
|
end
|
|
153
159
|
|
|
154
160
|
it "overrides the :block_all_mixed_content flag" do
|
|
@@ -158,7 +164,7 @@ module SecureHeaders
|
|
|
158
164
|
block_all_mixed_content: false
|
|
159
165
|
}
|
|
160
166
|
end
|
|
161
|
-
combined_config =
|
|
167
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
|
|
162
168
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
163
169
|
expect(csp.value).to eq("default-src https:; block-all-mixed-content")
|
|
164
170
|
end
|
|
@@ -168,23 +174,9 @@ module SecureHeaders
|
|
|
168
174
|
config.csp = OPT_OUT
|
|
169
175
|
end
|
|
170
176
|
expect do
|
|
171
|
-
|
|
177
|
+
ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
|
|
172
178
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
173
179
|
end
|
|
174
180
|
end
|
|
175
|
-
|
|
176
|
-
describe "#idempotent_additions?" do
|
|
177
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?(OPT_OUT, script_src: %w(b.com))).to be false }
|
|
178
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(c.com))).to be false }
|
|
179
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: %w(b.com))).to be false }
|
|
180
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(a.com b.com c.com))).to be false }
|
|
181
|
-
|
|
182
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com))).to be true }
|
|
183
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com a.com))).to be true }
|
|
184
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w())).to be true }
|
|
185
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: [nil])).to be true }
|
|
186
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: [nil])).to be true }
|
|
187
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: nil)).to be true }
|
|
188
|
-
end
|
|
189
181
|
end
|
|
190
182
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module SecureHeaders
|
|
4
|
+
describe ReferrerPolicy do
|
|
5
|
+
specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
|
|
6
|
+
specify { expect(ReferrerPolicy.make_header('no-referrer')).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
|
|
7
|
+
|
|
8
|
+
context "valid configuration values" do
|
|
9
|
+
it "accepts 'no-referrer'" do
|
|
10
|
+
expect do
|
|
11
|
+
ReferrerPolicy.validate_config!("no-referrer")
|
|
12
|
+
end.not_to raise_error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "accepts 'no-referrer-when-downgrade'" do
|
|
16
|
+
expect do
|
|
17
|
+
ReferrerPolicy.validate_config!("no-referrer-when-downgrade")
|
|
18
|
+
end.not_to raise_error
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "accepts 'same-origin'" do
|
|
22
|
+
expect do
|
|
23
|
+
ReferrerPolicy.validate_config!("same-origin")
|
|
24
|
+
end.not_to raise_error
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "accepts 'strict-origin'" do
|
|
28
|
+
expect do
|
|
29
|
+
ReferrerPolicy.validate_config!("strict-origin")
|
|
30
|
+
end.not_to raise_error
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "accepts 'strict-origin-when-cross-origin'" do
|
|
34
|
+
expect do
|
|
35
|
+
ReferrerPolicy.validate_config!("strict-origin-when-cross-origin")
|
|
36
|
+
end.not_to raise_error
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "accepts 'origin'" do
|
|
40
|
+
expect do
|
|
41
|
+
ReferrerPolicy.validate_config!("origin")
|
|
42
|
+
end.not_to raise_error
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "accepts 'origin-when-cross-origin'" do
|
|
46
|
+
expect do
|
|
47
|
+
ReferrerPolicy.validate_config!("origin-when-cross-origin")
|
|
48
|
+
end.not_to raise_error
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "accepts 'unsafe-url'" do
|
|
52
|
+
expect do
|
|
53
|
+
ReferrerPolicy.validate_config!("unsafe-url")
|
|
54
|
+
end.not_to raise_error
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "accepts nil" do
|
|
58
|
+
expect do
|
|
59
|
+
ReferrerPolicy.validate_config!(nil)
|
|
60
|
+
end.not_to raise_error
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context 'invlaid configuration values' do
|
|
65
|
+
it "doesn't accept invalid values" do
|
|
66
|
+
expect do
|
|
67
|
+
ReferrerPolicy.validate_config!("open")
|
|
68
|
+
end.to raise_error(ReferrerPolicyConfigError)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -13,6 +13,24 @@ module SecureHeaders
|
|
|
13
13
|
Configuration.default
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
it "warns if the hpkp report-uri host is the same as the current host" do
|
|
17
|
+
report_host = "report-uri.io"
|
|
18
|
+
Configuration.default do |config|
|
|
19
|
+
config.hpkp = {
|
|
20
|
+
max_age: 10000000,
|
|
21
|
+
pins: [
|
|
22
|
+
{sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
|
|
23
|
+
{sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
|
|
24
|
+
],
|
|
25
|
+
report_uri: "https://#{report_host}/example-hpkp"
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
expect(Kernel).to receive(:warn).with(Middleware::HPKP_SAME_HOST_WARNING)
|
|
30
|
+
|
|
31
|
+
middleware.call(Rack::MockRequest.env_for("https://#{report_host}", {}))
|
|
32
|
+
end
|
|
33
|
+
|
|
16
34
|
it "sets the headers" do
|
|
17
35
|
_, env = middleware.call(Rack::MockRequest.env_for("https://looocalhost", {}))
|
|
18
36
|
expect_default_values(env)
|
|
@@ -33,7 +51,7 @@ module SecureHeaders
|
|
|
33
51
|
SecureHeaders.use_secure_headers_override(request, "my_custom_config")
|
|
34
52
|
expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config"))
|
|
35
53
|
_, env = middleware.call request.env
|
|
36
|
-
expect(env[
|
|
54
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
|
|
37
55
|
end
|
|
38
56
|
|
|
39
57
|
context "secure_cookies" do
|
|
@@ -87,6 +105,18 @@ module SecureHeaders
|
|
|
87
105
|
_, env = cookie_middleware.call request.env
|
|
88
106
|
expect(env['Set-Cookie']).to eq("foo=bar")
|
|
89
107
|
end
|
|
108
|
+
|
|
109
|
+
it "sets the secure cookie flag correctly on interleaved http/https requests" do
|
|
110
|
+
Configuration.default { |config| config.cookies = { secure: true } }
|
|
111
|
+
|
|
112
|
+
request = Rack::Request.new("HTTPS" => "off")
|
|
113
|
+
_, env = cookie_middleware.call request.env
|
|
114
|
+
expect(env['Set-Cookie']).to eq("foo=bar")
|
|
115
|
+
|
|
116
|
+
request = Rack::Request.new("HTTPS" => "on")
|
|
117
|
+
_, env = cookie_middleware.call request.env
|
|
118
|
+
expect(env['Set-Cookie']).to eq("foo=bar; secure")
|
|
119
|
+
end
|
|
90
120
|
end
|
|
91
121
|
end
|
|
92
122
|
end
|
|
@@ -27,7 +27,16 @@ class Message < ERB
|
|
|
27
27
|
background-color: black;
|
|
28
28
|
}
|
|
29
29
|
<% end %>
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
<script nonce="<%= content_security_policy_script_nonce %>">
|
|
32
|
+
alert(1)
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style nonce="<%= content_security_policy_style_nonce %>">
|
|
36
|
+
body {
|
|
37
|
+
background-color: black;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
31
40
|
|
|
32
41
|
TEMPLATE
|
|
33
42
|
end
|
|
@@ -72,8 +81,11 @@ module SecureHeaders
|
|
|
72
81
|
|
|
73
82
|
before(:all) do
|
|
74
83
|
Configuration.default do |config|
|
|
75
|
-
config.csp
|
|
76
|
-
|
|
84
|
+
config.csp = {
|
|
85
|
+
:default_src => %w('self'),
|
|
86
|
+
:script_src => %w('self'),
|
|
87
|
+
:style_src => %w('self')
|
|
88
|
+
}
|
|
77
89
|
end
|
|
78
90
|
end
|
|
79
91
|
|
|
@@ -115,10 +127,10 @@ module SecureHeaders
|
|
|
115
127
|
Message.new(request).result
|
|
116
128
|
_, env = middleware.call request.env
|
|
117
129
|
|
|
118
|
-
expect(env[
|
|
119
|
-
expect(env[
|
|
120
|
-
expect(env[
|
|
121
|
-
expect(env[
|
|
130
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
|
|
131
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
|
|
132
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
|
|
133
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
|
|
122
134
|
end
|
|
123
135
|
end
|
|
124
136
|
end
|