secure_headers 6.3.4 → 7.3.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -2
  3. data/Gemfile +2 -0
  4. data/README.md +81 -15
  5. data/lib/secure_headers/configuration.rb +64 -13
  6. data/lib/secure_headers/headers/clear_site_data.rb +31 -33
  7. data/lib/secure_headers/headers/content_security_policy.rb +80 -46
  8. data/lib/secure_headers/headers/content_security_policy_config.rb +17 -58
  9. data/lib/secure_headers/headers/cookie.rb +4 -6
  10. data/lib/secure_headers/headers/expect_certificate_transparency.rb +20 -22
  11. data/lib/secure_headers/headers/policy_management.rb +66 -18
  12. data/lib/secure_headers/headers/referrer_policy.rb +20 -22
  13. data/lib/secure_headers/headers/reporting_endpoints.rb +54 -0
  14. data/lib/secure_headers/headers/strict_transport_security.rb +13 -15
  15. data/lib/secure_headers/headers/x_content_type_options.rb +14 -16
  16. data/lib/secure_headers/headers/x_download_options.rb +14 -16
  17. data/lib/secure_headers/headers/x_frame_options.rb +14 -16
  18. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +14 -16
  19. data/lib/secure_headers/headers/x_xss_protection.rb +14 -16
  20. data/lib/secure_headers/middleware.rb +11 -7
  21. data/lib/secure_headers/railtie.rb +11 -8
  22. data/lib/secure_headers/task_helper.rb +65 -0
  23. data/lib/secure_headers/version.rb +1 -1
  24. data/lib/secure_headers/view_helper.rb +7 -6
  25. data/lib/secure_headers.rb +26 -2
  26. data/lib/tasks/tasks.rake +4 -53
  27. data/secure_headers.gemspec +19 -7
  28. metadata +38 -70
  29. data/.github/ISSUE_TEMPLATE.md +0 -41
  30. data/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  31. data/.github/workflows/build.yml +0 -24
  32. data/.github/workflows/github-release.yml +0 -28
  33. data/.gitignore +0 -13
  34. data/.rspec +0 -3
  35. data/.rubocop.yml +0 -4
  36. data/.ruby-gemset +0 -1
  37. data/.ruby-version +0 -1
  38. data/CODE_OF_CONDUCT.md +0 -46
  39. data/CONTRIBUTING.md +0 -41
  40. data/Guardfile +0 -13
  41. data/Rakefile +0 -32
  42. data/docs/cookies.md +0 -65
  43. data/docs/hashes.md +0 -64
  44. data/docs/named_overrides_and_appends.md +0 -104
  45. data/docs/per_action_configuration.md +0 -139
  46. data/docs/sinatra.md +0 -25
  47. data/docs/upgrading-to-3-0.md +0 -42
  48. data/docs/upgrading-to-4-0.md +0 -35
  49. data/docs/upgrading-to-5-0.md +0 -15
  50. data/docs/upgrading-to-6-0.md +0 -50
  51. data/spec/lib/secure_headers/configuration_spec.rb +0 -121
  52. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +0 -87
  53. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +0 -190
  54. data/spec/lib/secure_headers/headers/cookie_spec.rb +0 -179
  55. data/spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb +0 -42
  56. data/spec/lib/secure_headers/headers/policy_management_spec.rb +0 -264
  57. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +0 -91
  58. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +0 -33
  59. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +0 -31
  60. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +0 -29
  61. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +0 -36
  62. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +0 -48
  63. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +0 -47
  64. data/spec/lib/secure_headers/middleware_spec.rb +0 -117
  65. data/spec/lib/secure_headers/view_helpers_spec.rb +0 -191
  66. data/spec/lib/secure_headers_spec.rb +0 -516
  67. data/spec/spec_helper.rb +0 -64
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
- require "spec_helper"
3
-
4
- module SecureHeaders
5
- describe XFrameOptions do
6
- describe "#value" do
7
- specify { expect(XFrameOptions.make_header).to eq([XFrameOptions::HEADER_NAME, XFrameOptions::DEFAULT_VALUE]) }
8
- specify { expect(XFrameOptions.make_header("DENY")).to eq([XFrameOptions::HEADER_NAME, "DENY"]) }
9
-
10
- context "with invalid configuration" do
11
- it "allows SAMEORIGIN" do
12
- expect do
13
- XFrameOptions.validate_config!("SAMEORIGIN")
14
- end.not_to raise_error
15
- end
16
-
17
- it "allows DENY" do
18
- expect do
19
- XFrameOptions.validate_config!("DENY")
20
- end.not_to raise_error
21
- end
22
-
23
- it "allows ALLOW-FROM*" do
24
- expect do
25
- XFrameOptions.validate_config!("ALLOW-FROM: example.com")
26
- end.not_to raise_error
27
- end
28
- it "does not allow garbage" do
29
- expect do
30
- XFrameOptions.validate_config!("I like turtles")
31
- end.to raise_error(XFOConfigError)
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
- require "spec_helper"
3
-
4
- module SecureHeaders
5
- describe XPermittedCrossDomainPolicies do
6
- specify { expect(XPermittedCrossDomainPolicies.make_header).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "none"]) }
7
- specify { expect(XPermittedCrossDomainPolicies.make_header("master-only")).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "master-only"]) }
8
-
9
- context "valid configuration values" do
10
- it "accepts 'all'" do
11
- expect do
12
- XPermittedCrossDomainPolicies.validate_config!("all")
13
- end.not_to raise_error
14
- end
15
-
16
- it "accepts 'by-ftp-filename'" do
17
- expect do
18
- XPermittedCrossDomainPolicies.validate_config!("by-ftp-filename")
19
- end.not_to raise_error
20
- end
21
-
22
- it "accepts 'by-content-type'" do
23
- expect do
24
- XPermittedCrossDomainPolicies.validate_config!("by-content-type")
25
- end.not_to raise_error
26
- end
27
- it "accepts 'master-only'" do
28
- expect do
29
- XPermittedCrossDomainPolicies.validate_config!("master-only")
30
- end.not_to raise_error
31
- end
32
-
33
- it "accepts nil" do
34
- expect do
35
- XPermittedCrossDomainPolicies.validate_config!(nil)
36
- end.not_to raise_error
37
- end
38
- end
39
-
40
- context "invlaid configuration values" do
41
- it "doesn't accept invalid values" do
42
- expect do
43
- XPermittedCrossDomainPolicies.validate_config!("open")
44
- end.to raise_error(XPCDPConfigError)
45
- end
46
- end
47
- end
48
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
- require "spec_helper"
3
-
4
- module SecureHeaders
5
- describe XXssProtection do
6
- specify { expect(XXssProtection.make_header).to eq([XXssProtection::HEADER_NAME, XXssProtection::DEFAULT_VALUE]) }
7
- specify { expect(XXssProtection.make_header("1; mode=block; report=https://www.secure.com/reports")).to eq([XXssProtection::HEADER_NAME, "1; mode=block; report=https://www.secure.com/reports"]) }
8
-
9
- context "with invalid configuration" do
10
- it "should raise an error when providing a string that is not valid" do
11
- expect do
12
- XXssProtection.validate_config!("asdf")
13
- end.to raise_error(XXssProtectionConfigError)
14
-
15
- expect do
16
- XXssProtection.validate_config!("asdf; mode=donkey")
17
- end.to raise_error(XXssProtectionConfigError)
18
- end
19
-
20
- context "when using a hash value" do
21
- it "should allow string values ('1' or '0' are the only valid strings)" do
22
- expect do
23
- XXssProtection.validate_config!("1")
24
- end.not_to raise_error
25
- end
26
-
27
- it "should raise an error if no value key is supplied" do
28
- expect do
29
- XXssProtection.validate_config!("mode=block")
30
- end.to raise_error(XXssProtectionConfigError)
31
- end
32
-
33
- it "should raise an error if an invalid key is supplied" do
34
- expect do
35
- XXssProtection.validate_config!("123")
36
- end.to raise_error(XXssProtectionConfigError)
37
- end
38
-
39
- it "should raise an error if mode != block" do
40
- expect do
41
- XXssProtection.validate_config!("1; mode=donkey")
42
- end.to raise_error(XXssProtectionConfigError)
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
- require "spec_helper"
3
-
4
- module SecureHeaders
5
- describe Middleware do
6
- let(:app) { lambda { |env| [200, env, "app"] } }
7
- let(:cookie_app) { lambda { |env| [200, env.merge("Set-Cookie" => "foo=bar"), "app"] } }
8
-
9
- let(:middleware) { Middleware.new(app) }
10
- let(:cookie_middleware) { Middleware.new(cookie_app) }
11
-
12
- before(:each) do
13
- reset_config
14
- Configuration.default
15
- end
16
-
17
- it "sets the headers" do
18
- _, env = middleware.call(Rack::MockRequest.env_for("https://looocalhost", {}))
19
- expect_default_values(env)
20
- end
21
-
22
- it "respects overrides" do
23
- request = Rack::Request.new("HTTP_X_FORWARDED_SSL" => "on")
24
- SecureHeaders.override_x_frame_options(request, "DENY")
25
- _, env = middleware.call request.env
26
- expect(env[XFrameOptions::HEADER_NAME]).to eq("DENY")
27
- end
28
-
29
- it "uses named overrides" do
30
- Configuration.override("my_custom_config") do |config|
31
- config.csp[:script_src] = %w(example.org)
32
- end
33
- request = Rack::Request.new({})
34
- SecureHeaders.use_secure_headers_override(request, "my_custom_config")
35
- _, env = middleware.call request.env
36
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
37
- end
38
-
39
- context "cookies" do
40
- before(:each) do
41
- reset_config
42
- end
43
- context "cookies should be flagged" do
44
- it "flags cookies as secure" do
45
- Configuration.default { |config| config.cookies = {secure: true, httponly: OPT_OUT, samesite: OPT_OUT} }
46
- request = Rack::Request.new("HTTPS" => "on")
47
- _, env = cookie_middleware.call request.env
48
- expect(env["Set-Cookie"]).to eq("foo=bar; secure")
49
- end
50
- end
51
-
52
- it "allows opting out of cookie protection with OPT_OUT alone" do
53
- Configuration.default { |config| config.cookies = OPT_OUT }
54
-
55
- # do NOT make this request https. non-https requests modify a config,
56
- # causing an exception when operating on OPT_OUT. This ensures we don't
57
- # try to modify the config.
58
- request = Rack::Request.new({})
59
- _, env = cookie_middleware.call request.env
60
- expect(env["Set-Cookie"]).to eq("foo=bar")
61
- end
62
-
63
- context "cookies should not be flagged" do
64
- it "does not flags cookies as secure" do
65
- Configuration.default { |config| config.cookies = {secure: OPT_OUT, httponly: OPT_OUT, samesite: OPT_OUT} }
66
- request = Rack::Request.new("HTTPS" => "on")
67
- _, env = cookie_middleware.call request.env
68
- expect(env["Set-Cookie"]).to eq("foo=bar")
69
- end
70
- end
71
- end
72
-
73
- context "cookies" do
74
- before(:each) do
75
- reset_config
76
- end
77
- it "flags cookies from configuration" do
78
- Configuration.default { |config| config.cookies = { secure: true, httponly: true, samesite: { lax: true} } }
79
- request = Rack::Request.new("HTTPS" => "on")
80
- _, env = cookie_middleware.call request.env
81
-
82
- expect(env["Set-Cookie"]).to eq("foo=bar; secure; HttpOnly; SameSite=Lax")
83
- end
84
-
85
- it "flags cookies with a combination of SameSite configurations" do
86
- cookie_middleware = Middleware.new(lambda { |env| [200, env.merge("Set-Cookie" => ["_session=foobar", "_guest=true"]), "app"] })
87
-
88
- Configuration.default { |config| config.cookies = { samesite: { lax: { except: ["_session"] }, strict: { only: ["_session"] } }, httponly: OPT_OUT, secure: OPT_OUT} }
89
- request = Rack::Request.new("HTTPS" => "on")
90
- _, env = cookie_middleware.call request.env
91
-
92
- expect(env["Set-Cookie"]).to match("_session=foobar; SameSite=Strict")
93
- expect(env["Set-Cookie"]).to match("_guest=true; SameSite=Lax")
94
- end
95
-
96
- it "disables secure cookies for non-https requests" do
97
- Configuration.default { |config| config.cookies = { secure: true, httponly: OPT_OUT, samesite: OPT_OUT } }
98
-
99
- request = Rack::Request.new("HTTPS" => "off")
100
- _, env = cookie_middleware.call request.env
101
- expect(env["Set-Cookie"]).to eq("foo=bar")
102
- end
103
-
104
- it "sets the secure cookie flag correctly on interleaved http/https requests" do
105
- Configuration.default { |config| config.cookies = { secure: true, httponly: OPT_OUT, samesite: OPT_OUT } }
106
-
107
- request = Rack::Request.new("HTTPS" => "off")
108
- _, env = cookie_middleware.call request.env
109
- expect(env["Set-Cookie"]).to eq("foo=bar")
110
-
111
- request = Rack::Request.new("HTTPS" => "on")
112
- _, env = cookie_middleware.call request.env
113
- expect(env["Set-Cookie"]).to eq("foo=bar; secure")
114
- end
115
- end
116
- end
117
- end
@@ -1,191 +0,0 @@
1
- # frozen_string_literal: true
2
- require "spec_helper"
3
- require "erb"
4
-
5
- class Message < ERB
6
- include SecureHeaders::ViewHelpers
7
-
8
- def self.template
9
- <<-TEMPLATE
10
- <% hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
11
- console.log(1)
12
- <% end %>
13
-
14
- <% hashed_style_tag do %>
15
- body {
16
- background-color: black;
17
- }
18
- <% end %>
19
-
20
- <% nonced_javascript_tag do %>
21
- body {
22
- console.log(1)
23
- }
24
- <% end %>
25
-
26
- <% nonced_style_tag do %>
27
- body {
28
- background-color: black;
29
- }
30
- <% end %>
31
-
32
- <script nonce="<%= content_security_policy_script_nonce %>">
33
- alert(1)
34
- </script>
35
-
36
- <style nonce="<%= content_security_policy_style_nonce %>">
37
- body {
38
- background-color: black;
39
- }
40
- </style>
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
-
50
- TEMPLATE
51
- end
52
-
53
- def initialize(request, options = {})
54
- @virtual_path = "/asdfs/index"
55
- @_request = request
56
- @template = self.class.template
57
- super(@template)
58
- end
59
-
60
- def capture(*args)
61
- yield(*args)
62
- end
63
-
64
- def content_tag(type, content = nil, options = nil, &block)
65
- content = if block_given?
66
- capture(block)
67
- end
68
-
69
- if options.is_a?(Hash)
70
- options = options.map { |k, v| " #{k}=#{v}" }
71
- end
72
- "<#{type}#{options}>#{content}</#{type}>"
73
- end
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
-
91
- def result
92
- super(binding)
93
- end
94
-
95
- def request
96
- @_request
97
- end
98
- end
99
-
100
- class MessageWithConflictingMethod < Message
101
- def content_security_policy_nonce
102
- "rails-nonce"
103
- end
104
- end
105
-
106
- module SecureHeaders
107
- describe ViewHelpers do
108
- let(:app) { lambda { |env| [200, env, "app"] } }
109
- let(:middleware) { Middleware.new(app) }
110
- let(:request) { Rack::Request.new("HTTP_USER_AGENT" => USER_AGENTS[:chrome]) }
111
- let(:filename) { "app/views/asdfs/index.html.erb" }
112
-
113
- before(:all) do
114
- reset_config
115
- Configuration.default do |config|
116
- config.csp = {
117
- default_src: %w('self'),
118
- script_src: %w('self'),
119
- style_src: %w('self')
120
- }
121
- end
122
- end
123
-
124
- after(:each) do
125
- Configuration.instance_variable_set(:@script_hashes, nil)
126
- Configuration.instance_variable_set(:@style_hashes, nil)
127
- end
128
-
129
- it "raises an error when using hashed content without precomputed hashes" do
130
- expect {
131
- Message.new(request).result
132
- }.to raise_error(ViewHelpers::UnexpectedHashedScriptException)
133
- end
134
-
135
- it "raises an error when using hashed content with precomputed hashes, but none for the given file" do
136
- Configuration.instance_variable_set(:@script_hashes, filename.reverse => ["'sha256-123'"])
137
- expect {
138
- Message.new(request).result
139
- }.to raise_error(ViewHelpers::UnexpectedHashedScriptException)
140
- end
141
-
142
- it "raises an error when using previously unknown hashed content with precomputed hashes for a given file" do
143
- Configuration.instance_variable_set(:@script_hashes, filename => ["'sha256-123'"])
144
- expect {
145
- Message.new(request).result
146
- }.to raise_error(ViewHelpers::UnexpectedHashedScriptException)
147
- end
148
-
149
- it "adds known hash values to the corresponding headers when the helper is used" do
150
- begin
151
- allow(SecureRandom).to receive(:base64).and_return("abc123")
152
-
153
- expected_hash = "sha256-3/URElR9+3lvLIouavYD/vhoICSNKilh15CzI/nKqg8="
154
- Configuration.instance_variable_set(:@script_hashes, filename => ["'#{expected_hash}'"])
155
- expected_style_hash = "sha256-7oYK96jHg36D6BM042er4OfBnyUDTG3pH1L8Zso3aGc="
156
- Configuration.instance_variable_set(:@style_hashes, filename => ["'#{expected_style_hash}'"])
157
-
158
- # render erb that calls out to helpers.
159
- Message.new(request).result
160
- _, env = middleware.call request.env
161
-
162
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
163
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
164
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
165
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
166
- end
167
- end
168
-
169
- it "avoids calling content_security_policy_nonce internally" do
170
- begin
171
- allow(SecureRandom).to receive(:base64).and_return("abc123")
172
-
173
- expected_hash = "sha256-3/URElR9+3lvLIouavYD/vhoICSNKilh15CzI/nKqg8="
174
- Configuration.instance_variable_set(:@script_hashes, filename => ["'#{expected_hash}'"])
175
- expected_style_hash = "sha256-7oYK96jHg36D6BM042er4OfBnyUDTG3pH1L8Zso3aGc="
176
- Configuration.instance_variable_set(:@style_hashes, filename => ["'#{expected_style_hash}'"])
177
-
178
- # render erb that calls out to helpers.
179
- MessageWithConflictingMethod.new(request).result
180
- _, env = middleware.call request.env
181
-
182
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
183
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
184
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
185
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
186
-
187
- expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).not_to match(/rails-nonce/)
188
- end
189
- end
190
- end
191
- end