secure_headers 6.3.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/Gemfile +3 -1
  4. data/README.md +22 -17
  5. data/lib/secure_headers/configuration.rb +11 -7
  6. data/lib/secure_headers/headers/clear_site_data.rb +4 -4
  7. data/lib/secure_headers/headers/content_security_policy.rb +25 -38
  8. data/lib/secure_headers/headers/content_security_policy_config.rb +17 -54
  9. data/lib/secure_headers/headers/cookie.rb +2 -2
  10. data/lib/secure_headers/headers/expect_certificate_transparency.rb +2 -2
  11. data/lib/secure_headers/headers/policy_management.rb +54 -12
  12. data/lib/secure_headers/headers/referrer_policy.rb +1 -1
  13. data/lib/secure_headers/headers/strict_transport_security.rb +1 -1
  14. data/lib/secure_headers/headers/x_content_type_options.rb +1 -1
  15. data/lib/secure_headers/headers/x_download_options.rb +2 -2
  16. data/lib/secure_headers/headers/x_frame_options.rb +1 -1
  17. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -2
  18. data/lib/secure_headers/headers/x_xss_protection.rb +2 -2
  19. data/lib/secure_headers/railtie.rb +5 -5
  20. data/lib/secure_headers/version.rb +1 -1
  21. data/lib/secure_headers/view_helper.rb +7 -6
  22. data/lib/tasks/tasks.rake +6 -7
  23. data/secure_headers.gemspec +17 -7
  24. metadata +22 -67
  25. data/.github/ISSUE_TEMPLATE.md +0 -41
  26. data/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  27. data/.github/workflows/build.yml +0 -24
  28. data/.github/workflows/sync.yml +0 -20
  29. data/.gitignore +0 -13
  30. data/.rspec +0 -3
  31. data/.rubocop.yml +0 -4
  32. data/.ruby-gemset +0 -1
  33. data/.ruby-version +0 -1
  34. data/CODE_OF_CONDUCT.md +0 -46
  35. data/CONTRIBUTING.md +0 -41
  36. data/Guardfile +0 -13
  37. data/Rakefile +0 -32
  38. data/docs/cookies.md +0 -65
  39. data/docs/hashes.md +0 -64
  40. data/docs/named_overrides_and_appends.md +0 -104
  41. data/docs/per_action_configuration.md +0 -141
  42. data/docs/sinatra.md +0 -25
  43. data/docs/upgrading-to-3-0.md +0 -42
  44. data/docs/upgrading-to-4-0.md +0 -35
  45. data/docs/upgrading-to-5-0.md +0 -15
  46. data/docs/upgrading-to-6-0.md +0 -50
  47. data/spec/lib/secure_headers/configuration_spec.rb +0 -121
  48. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +0 -87
  49. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +0 -165
  50. data/spec/lib/secure_headers/headers/cookie_spec.rb +0 -179
  51. data/spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb +0 -42
  52. data/spec/lib/secure_headers/headers/policy_management_spec.rb +0 -260
  53. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +0 -91
  54. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +0 -33
  55. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +0 -31
  56. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +0 -29
  57. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +0 -36
  58. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +0 -48
  59. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +0 -47
  60. data/spec/lib/secure_headers/middleware_spec.rb +0 -117
  61. data/spec/lib/secure_headers/view_helpers_spec.rb +0 -191
  62. data/spec/lib/secure_headers_spec.rb +0 -516
  63. data/spec/spec_helper.rb +0 -64
@@ -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