Skip to content

Commit 2e3a64d

Browse files
committed
Merge pull request #52 from rmm5t/strip_set_cookie
Rack::Cache caches Set-Cookie response headers yielding potential security holes in apps
2 parents 8ecd5b4 + 6379364 commit 2e3a64d

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

lib/rack/cache/context.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ def fetch
260260

261261
# Write the response to the cache.
262262
def store(response)
263+
strip_ignore_headers(response)
263264
metastore.store(@request, response, entitystore)
264265
response.headers['Age'] = response.age.to_s
265266
rescue Exception => e
@@ -269,6 +270,12 @@ def store(response)
269270
record :store
270271
end
271272

273+
# Remove all ignored response headers before writing to the cache.
274+
def strip_ignore_headers(response)
275+
stripped_values = ignore_headers.map { |name| response.headers.delete(name) }
276+
record :ignore if stripped_values.any?
277+
end
278+
272279
def log_error(exception)
273280
@env['rack.errors'].write("cache error: #{exception.message}\n#{exception.backtrace.join("\n")}\n")
274281
end

lib/rack/cache/options.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ def option_name(key)
7878
# Default: 0
7979
option_accessor :default_ttl
8080

81+
# Set of response headers that are removed before storing them in the
82+
# cache. These headers are only removed for cacheable responses. For
83+
# example, in most cases, it makes sense to prevent cookies from being
84+
# stored in the cache.
85+
#
86+
# Default: ['Set-Cookie']
87+
option_accessor :ignore_headers
88+
8189
# Set of request headers that trigger "private" cache-control behavior
8290
# on responses that don't explicitly state whether the response is
8391
# public or private via a Cache-Control directive. Applications that use
@@ -138,6 +146,7 @@ def initialize_options(options={})
138146
'rack-cache.metastore' => 'heap:/',
139147
'rack-cache.entitystore' => 'heap:/',
140148
'rack-cache.default_ttl' => 0,
149+
'rack-cache.ignore_headers' => ['Set-Cookie'],
141150
'rack-cache.private_headers' => ['Authorization', 'Cookie'],
142151
'rack-cache.allow_reload' => false,
143152
'rack-cache.allow_revalidate' => false,

test/context_test.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
response.should.be.ok
5858
cache.trace.should.include :miss
5959
cache.trace.should.include :store
60+
cache.trace.should.not.include :ignore
6061
response.headers.should.include 'Age'
6162
response.headers['Cache-Control'].should.equal 'public'
6263
end
@@ -85,6 +86,40 @@
8586
response.headers['Cache-Control'].should.equal 'private'
8687
end
8788

89+
it 'does remove Set-Cookie response header from a cacheable response' do
90+
respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"', 'Set-Cookie' => 'TestCookie=OK'
91+
get '/'
92+
93+
app.should.be.called
94+
response.should.be.ok
95+
cache.trace.should.include :store
96+
cache.trace.should.include :ignore
97+
response.headers['Set-Cookie'].should.be.nil
98+
end
99+
100+
it 'does remove all configured ignore_headers from a cacheable response' do
101+
respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"', 'SET-COOKIE' => 'TestCookie=OK', 'X-Strip-Me' => 'Secret'
102+
get '/', 'rack-cache.ignore_headers' => ['set-cookie', 'x-strip-me']
103+
104+
app.should.be.called
105+
response.should.be.ok
106+
cache.trace.should.include :store
107+
cache.trace.should.include :ignore
108+
response.headers['Set-Cookie'].should.be.nil
109+
response.headers['x-strip-me'].should.be.nil
110+
end
111+
112+
it 'does not remove Set-Cookie response header from a private response' do
113+
respond_with 200, 'Cache-Control' => 'private', 'Set-Cookie' => 'TestCookie=OK'
114+
get '/'
115+
116+
app.should.be.called
117+
response.should.be.ok
118+
cache.trace.should.not.include :store
119+
cache.trace.should.not.include :ignore
120+
response.headers['Set-Cookie'].should.equal 'TestCookie=OK'
121+
end
122+
88123
it 'responds with 304 when If-Modified-Since matches Last-Modified' do
89124
timestamp = Time.now.httpdate
90125
respond_with do |req,res|

0 commit comments

Comments
 (0)