Skip to content

Commit 76fb828

Browse files
committed
Merge pull request #231 from jmera/secure-cookies
Secure Cookies
2 parents c4c69e1 + 3301a0d commit 76fb828

File tree

6 files changed

+58
-17
lines changed

6 files changed

+58
-17
lines changed

lib/secure_headers.rb

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ def content_security_policy_style_nonce(request)
149149
content_security_policy_nonce(request, CSP::STYLE_SRC)
150150
end
151151

152+
# Public: Retreives the config for a given header type:
153+
#
154+
# Checks to see if there is an override for this request, then
155+
# Checks to see if a named override is used for this request, then
156+
# Falls back to the global config
157+
def config_for(request)
158+
request.env[SECURE_HEADERS_CONFIG] ||
159+
Configuration.get(Configuration::DEFAULT_CONFIG)
160+
end
161+
152162
private
153163

154164
# Private: gets or creates a nonce for CSP.
@@ -217,16 +227,6 @@ def use_cached_headers(default_headers, request)
217227
end
218228
end
219229

220-
# Private: Retreives the config for a given header type:
221-
#
222-
# Checks to see if there is an override for this request, then
223-
# Checks to see if a named override is used for this request, then
224-
# Falls back to the global config
225-
def config_for(request)
226-
request.env[SECURE_HEADERS_CONFIG] ||
227-
Configuration.get(Configuration::DEFAULT_CONFIG)
228-
end
229-
230230
# Private: chooses the applicable CSP header for the provided user agent.
231231
#
232232
# headers - a hash of header_config_key => [header_name, header_value]

lib/secure_headers/configuration.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def add_noop_configuration
9090

9191
attr_accessor :hsts, :x_frame_options, :x_content_type_options,
9292
:x_xss_protection, :csp, :x_download_options, :x_permitted_cross_domain_policies,
93-
:hpkp
93+
:hpkp, :secure_cookies
9494
attr_reader :cached_headers
9595

9696
def initialize(&block)

lib/secure_headers/middleware.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module SecureHeaders
22
class Middleware
3+
SECURE_COOKIE_REGEXP = /;\s*secure\s*(;|$)/i.freeze
4+
35
def initialize(app)
46
@app = app
57
end
@@ -8,8 +10,29 @@ def initialize(app)
810
def call(env)
911
req = Rack::Request.new(env)
1012
status, headers, response = @app.call(env)
13+
14+
config = SecureHeaders.config_for(req)
15+
flag_cookies_as_secure!(headers) if config.secure_cookies
1116
headers.merge!(SecureHeaders.header_hash_for(req))
1217
[status, headers, response]
1318
end
19+
20+
private
21+
22+
# inspired by https://github.com/tobmatth/rack-ssl-enforcer/blob/6c014/lib/rack/ssl-enforcer.rb#L183-L194
23+
def flag_cookies_as_secure!(headers)
24+
if cookies = headers['Set-Cookie']
25+
# Support Rails 2.3 / Rack 1.1 arrays as headers
26+
cookies = cookies.split("\n") unless cookies.is_a?(Array)
27+
28+
headers['Set-Cookie'] = cookies.map do |cookie|
29+
if cookie !~ SECURE_COOKIE_REGEXP
30+
"#{cookie}; secure"
31+
else
32+
cookie
33+
end
34+
end.join("\n")
35+
end
36+
end
1437
end
1538
end

lib/secure_headers/railtie.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Railtie < Rails::Railtie
1010
'Public-Key-Pins', 'Public-Key-Pins-Report-Only']
1111

1212
initializer "secure_headers.middleware" do
13-
Rails.application.config.middleware.use SecureHeaders::Middleware
13+
Rails.application.config.middleware.insert_before 0, SecureHeaders::Middleware
1414
end
1515

1616
initializer "secure_headers.action_controller" do

spec/lib/secure_headers/configuration_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module SecureHeaders
3737
config = Configuration.get(:test_override)
3838
noop = Configuration.get(Configuration::NOOP_CONFIGURATION)
3939
[:hsts, :x_frame_options, :x_content_type_options, :x_xss_protection,
40-
:x_download_options, :x_permitted_cross_domain_policies, :hpkp, :csp].each do |key|
40+
:x_download_options, :x_permitted_cross_domain_policies, :hpkp, :csp, :secure_cookies].each do |key|
4141

4242
expect(config.send(key)).to eq(noop.send(key)), "Value not copied: #{key}."
4343
end

spec/lib/secure_headers/middleware_spec.rb

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
module SecureHeaders
44
describe Middleware do
5-
let(:app) { ->(env) { [200, env, "app"] } }
5+
let(:app) { lambda { |env| [200, env, "app"] } }
6+
let(:cookie_app) { lambda { |env| [200, env.merge("Set-Cookie" => "foo=bar"), "app"] } }
67

7-
let :middleware do
8-
Middleware.new(app)
9-
end
8+
let(:middleware) { Middleware.new(app) }
9+
let(:cookie_middleware) { Middleware.new(cookie_app) }
1010

1111
before(:each) do
1212
reset_config
@@ -36,5 +36,23 @@ module SecureHeaders
3636
_, env = middleware.call request.env
3737
expect(env[CSP::HEADER_NAME]).to match("example.org")
3838
end
39+
40+
context "cookies should be flagged" do
41+
it "flags cookies as secure" do
42+
Configuration.default { |config| config.secure_cookies = true }
43+
request = Rack::MockRequest.new(cookie_middleware)
44+
response = request.get '/'
45+
expect(response.headers['Set-Cookie']).to match(Middleware::SECURE_COOKIE_REGEXP)
46+
end
47+
end
48+
49+
context "cookies should not be flagged" do
50+
it "does not flags cookies as secure" do
51+
Configuration.default { |config| config.secure_cookies = false }
52+
request = Rack::MockRequest.new(cookie_middleware)
53+
response = request.get '/'
54+
expect(response.headers['Set-Cookie']).not_to match(Middleware::SECURE_COOKIE_REGEXP)
55+
end
56+
end
3957
end
4058
end

0 commit comments

Comments
 (0)