rack-protection 1.0.0 → 1.5.5
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 +7 -0
- data/README.md +9 -13
- data/Rakefile +15 -4
- data/lib/rack/protection/authenticity_token.rb +10 -3
- data/lib/rack/protection/base.rb +52 -4
- data/lib/rack/protection/escaped_params.rb +34 -8
- data/lib/rack/protection/frame_options.rb +14 -3
- data/lib/rack/protection/http_origin.rb +32 -0
- data/lib/rack/protection/ip_spoofing.rb +1 -1
- data/lib/rack/protection/json_csrf.rb +14 -4
- data/lib/rack/protection/path_traversal.rb +26 -5
- data/lib/rack/protection/remote_referrer.rb +0 -3
- data/lib/rack/protection/session_hijacking.rb +5 -5
- data/lib/rack/protection/version.rb +6 -34
- data/lib/rack/protection/xss_header.rb +4 -6
- data/lib/rack/protection.rb +13 -8
- data/rack-protection.gemspec +57 -4
- data/spec/authenticity_token_spec.rb +15 -0
- data/spec/base_spec.rb +40 -0
- data/spec/escaped_params_spec.rb +9 -0
- data/spec/frame_options_spec.rb +19 -4
- data/spec/http_origin_spec.rb +38 -0
- data/spec/json_csrf_spec.rb +26 -0
- data/spec/path_traversal_spec.rb +20 -2
- data/spec/protection_spec.rb +100 -0
- data/spec/session_hijacking_spec.rb +19 -4
- data/spec/spec_helper.rb +7 -1
- data/spec/xss_header_spec.rb +35 -3
- metadata +82 -32
data/rack-protection.gemspec
CHANGED
@@ -2,23 +2,74 @@
|
|
2
2
|
Gem::Specification.new do |s|
|
3
3
|
# general infos
|
4
4
|
s.name = "rack-protection"
|
5
|
-
s.version = "1.
|
5
|
+
s.version = "1.5.5"
|
6
6
|
s.description = "You should use protection!"
|
7
7
|
s.homepage = "http://github.com/rkh/rack-protection"
|
8
8
|
s.summary = s.description
|
9
|
+
s.license = 'MIT'
|
9
10
|
|
10
11
|
# generated from git shortlog -sn
|
11
12
|
s.authors = [
|
12
13
|
"Konstantin Haase",
|
14
|
+
"Alex Rodionov",
|
15
|
+
"Patrick Ellis",
|
16
|
+
"Jason Staten",
|
17
|
+
"ITO Nobuaki",
|
18
|
+
"Jeff Welling",
|
19
|
+
"Matteo Centenaro",
|
20
|
+
"Egor Homakov",
|
21
|
+
"Florian Gilcher",
|
22
|
+
"Fojas",
|
23
|
+
"Igor Bochkariov",
|
24
|
+
"Mael Clerambault",
|
25
|
+
"Martin Mauch",
|
26
|
+
"Renne Nissinen",
|
27
|
+
"SAKAI, Kazuaki",
|
28
|
+
"Stanislav Savulchik",
|
29
|
+
"Steve Agalloco",
|
30
|
+
"TOBY",
|
31
|
+
"Thais Camilo and Konstantin Haase",
|
32
|
+
"Vipul A M",
|
33
|
+
"Akzhan Abdulin",
|
34
|
+
"brookemckim",
|
35
|
+
"Bj\u{f8}rge N\u{e6}ss",
|
36
|
+
"Chris Heald",
|
37
|
+
"Chris Mytton",
|
13
38
|
"Corey Ward",
|
14
|
-
"
|
39
|
+
"Dario Cravero",
|
40
|
+
"David Kellum"
|
15
41
|
]
|
16
42
|
|
17
43
|
# generated from git shortlog -sne
|
18
44
|
s.email = [
|
19
45
|
"konstantin.mailinglists@googlemail.com",
|
46
|
+
"p0deje@gmail.com",
|
47
|
+
"jstaten07@gmail.com",
|
48
|
+
"patrick@soundcloud.com",
|
49
|
+
"jeff.welling@gmail.com",
|
50
|
+
"bugant@gmail.com",
|
51
|
+
"daydream.trippers@gmail.com",
|
52
|
+
"florian.gilcher@asquera.de",
|
53
|
+
"developer@fojasaur.us",
|
54
|
+
"ujifgc@gmail.com",
|
55
|
+
"mael@clerambault.fr",
|
56
|
+
"martin.mauch@gmail.com",
|
57
|
+
"rennex@iki.fi",
|
58
|
+
"kaz.july.7@gmail.com",
|
59
|
+
"s.savulchik@gmail.com",
|
60
|
+
"steve.agalloco@gmail.com",
|
61
|
+
"toby.net.info.mail+git@gmail.com",
|
62
|
+
"dev+narwen+rkh@rkh.im",
|
63
|
+
"vipulnsward@gmail.com",
|
64
|
+
"akzhan.abdulin@gmail.com",
|
65
|
+
"brooke@digitalocean.com",
|
66
|
+
"bjoerge@bengler.no",
|
67
|
+
"cheald@gmail.com",
|
68
|
+
"self@hecticjeff.net",
|
20
69
|
"coreyward@me.com",
|
21
|
-
"
|
70
|
+
"dario@uxtemple.com",
|
71
|
+
"dek-oss@gravitext.com",
|
72
|
+
"homakov@gmail.com"
|
22
73
|
]
|
23
74
|
|
24
75
|
# generated from git ls-files
|
@@ -33,6 +84,7 @@ Gem::Specification.new do |s|
|
|
33
84
|
"lib/rack/protection/escaped_params.rb",
|
34
85
|
"lib/rack/protection/form_token.rb",
|
35
86
|
"lib/rack/protection/frame_options.rb",
|
87
|
+
"lib/rack/protection/http_origin.rb",
|
36
88
|
"lib/rack/protection/ip_spoofing.rb",
|
37
89
|
"lib/rack/protection/json_csrf.rb",
|
38
90
|
"lib/rack/protection/path_traversal.rb",
|
@@ -43,9 +95,11 @@ Gem::Specification.new do |s|
|
|
43
95
|
"lib/rack/protection/xss_header.rb",
|
44
96
|
"rack-protection.gemspec",
|
45
97
|
"spec/authenticity_token_spec.rb",
|
98
|
+
"spec/base_spec.rb",
|
46
99
|
"spec/escaped_params_spec.rb",
|
47
100
|
"spec/form_token_spec.rb",
|
48
101
|
"spec/frame_options_spec.rb",
|
102
|
+
"spec/http_origin_spec.rb",
|
49
103
|
"spec/ip_spoofing_spec.rb",
|
50
104
|
"spec/json_csrf_spec.rb",
|
51
105
|
"spec/path_traversal_spec.rb",
|
@@ -59,7 +113,6 @@ Gem::Specification.new do |s|
|
|
59
113
|
|
60
114
|
# dependencies
|
61
115
|
s.add_dependency "rack"
|
62
|
-
s.add_dependency "escape_utils"
|
63
116
|
s.add_development_dependency "rack-test"
|
64
117
|
s.add_development_dependency "rspec", "~> 2.0"
|
65
118
|
end
|
@@ -30,4 +30,19 @@ describe Rack::Protection::AuthenticityToken do
|
|
30
30
|
it "prevents ajax requests without a valid token" do
|
31
31
|
post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest").should_not be_ok
|
32
32
|
end
|
33
|
+
|
34
|
+
it "allows for a custom authenticity token param" do
|
35
|
+
mock_app do
|
36
|
+
use Rack::Protection::AuthenticityToken, :authenticity_param => 'csrf_param'
|
37
|
+
run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] }
|
38
|
+
end
|
39
|
+
|
40
|
+
post('/', {"csrf_param" => "a"}, 'rack.session' => {:csrf => "a"})
|
41
|
+
last_response.should be_ok
|
42
|
+
end
|
43
|
+
|
44
|
+
it "sets a new csrf token for the session in env, even after a 'safe' request" do
|
45
|
+
get('/', {}, {})
|
46
|
+
env['rack.session'][:csrf].should_not be_nil
|
47
|
+
end
|
33
48
|
end
|
data/spec/base_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
describe Rack::Protection::Base do
|
4
|
+
|
5
|
+
subject { described_class.new(lambda {}) }
|
6
|
+
|
7
|
+
describe "#random_string" do
|
8
|
+
it "outputs a string of 32 characters" do
|
9
|
+
subject.random_string.length.should == 32
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#referrer" do
|
14
|
+
it "Reads referrer from Referer header" do
|
15
|
+
env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/valid"}
|
16
|
+
subject.referrer(env).should == "bar.com"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "Reads referrer from Host header when Referer header is relative" do
|
20
|
+
env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "/valid"}
|
21
|
+
subject.referrer(env).should == "foo.com"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "Reads referrer from Host header when Referer header is missing" do
|
25
|
+
env = {"HTTP_HOST" => "foo.com"}
|
26
|
+
subject.referrer(env).should == "foo.com"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "Returns nil when Referer header is missing and allow_empty_referrer is false" do
|
30
|
+
env = {"HTTP_HOST" => "foo.com"}
|
31
|
+
subject.options[:allow_empty_referrer] = false
|
32
|
+
subject.referrer(env).should be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "Returns nil when Referer header is invalid" do
|
36
|
+
env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/bad|uri"}
|
37
|
+
subject.referrer(env).should be_nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/escaped_params_spec.rb
CHANGED
@@ -30,5 +30,14 @@ describe Rack::Protection::EscapedParams do
|
|
30
30
|
get '/', :foo => {:bar => "<bar>"}
|
31
31
|
body.should == '<bar>'
|
32
32
|
end
|
33
|
+
|
34
|
+
it 'leaves cache-breaker params untouched' do
|
35
|
+
mock_app do |env|
|
36
|
+
[200, {'Content-Type' => 'text/plain'}, ['hi']]
|
37
|
+
end
|
38
|
+
|
39
|
+
get '/?95df8d9bf5237ad08df3115ee74dcb10'
|
40
|
+
body.should == 'hi'
|
41
|
+
end
|
33
42
|
end
|
34
43
|
end
|
data/spec/frame_options_spec.rb
CHANGED
@@ -3,8 +3,12 @@ require File.expand_path('../spec_helper.rb', __FILE__)
|
|
3
3
|
describe Rack::Protection::FrameOptions do
|
4
4
|
it_behaves_like "any rack application"
|
5
5
|
|
6
|
-
it 'should set the X-
|
7
|
-
get('/').headers["X-Frame-Options"].should == "
|
6
|
+
it 'should set the X-Frame-Options' do
|
7
|
+
get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "SAMEORIGIN"
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should not set the X-Frame-Options for other content types' do
|
11
|
+
get('/', {}, 'wants' => 'text/foo').headers["X-Frame-Options"].should be_nil
|
8
12
|
end
|
9
13
|
|
10
14
|
it 'should allow changing the protection mode' do
|
@@ -14,11 +18,22 @@ describe Rack::Protection::FrameOptions do
|
|
14
18
|
run DummyApp
|
15
19
|
end
|
16
20
|
|
17
|
-
get('/').headers["X-Frame-Options"].should == "
|
21
|
+
get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "DENY"
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
it 'should allow changing the protection mode to a string' do
|
26
|
+
# I have no clue what other modes are available
|
27
|
+
mock_app do
|
28
|
+
use Rack::Protection::FrameOptions, :frame_options => "ALLOW-FROM foo"
|
29
|
+
run DummyApp
|
30
|
+
end
|
31
|
+
|
32
|
+
get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "ALLOW-FROM foo"
|
18
33
|
end
|
19
34
|
|
20
35
|
it 'should not override the header if already set' do
|
21
36
|
mock_app with_headers("X-Frame-Options" => "allow")
|
22
|
-
get('/').headers["X-Frame-Options"].should == "allow"
|
37
|
+
get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "allow"
|
23
38
|
end
|
24
39
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
describe Rack::Protection::HttpOrigin do
|
4
|
+
it_behaves_like "any rack application"
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
mock_app do
|
8
|
+
use Rack::Protection::HttpOrigin
|
9
|
+
run DummyApp
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
%w(GET HEAD POST PUT DELETE).each do |method|
|
14
|
+
it "accepts #{method} requests with no Origin" do
|
15
|
+
send(method.downcase, '/').should be_ok
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
%w(GET HEAD).each do |method|
|
20
|
+
it "accepts #{method} requests with non-whitelisted Origin" do
|
21
|
+
send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com').should be_ok
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
%w(POST PUT DELETE).each do |method|
|
26
|
+
it "denies #{method} requests with non-whitelisted Origin" do
|
27
|
+
send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com').should_not be_ok
|
28
|
+
end
|
29
|
+
|
30
|
+
it "accepts #{method} requests with whitelisted Origin" do
|
31
|
+
mock_app do
|
32
|
+
use Rack::Protection::HttpOrigin, :origin_whitelist => ['http://www.friend.com']
|
33
|
+
run DummyApp
|
34
|
+
end
|
35
|
+
send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://www.friend.com').should be_ok
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/json_csrf_spec.rb
CHANGED
@@ -12,6 +12,14 @@ describe Rack::Protection::JsonCsrf do
|
|
12
12
|
get('/', {}, 'HTTP_REFERER' => 'http://evil.com').should_not be_ok
|
13
13
|
end
|
14
14
|
|
15
|
+
it "accepts requests with json responses with a remote referrer when there's an origin header set" do
|
16
|
+
get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_ORIGIN' => 'http://good.com').should be_ok
|
17
|
+
end
|
18
|
+
|
19
|
+
it "accepts requests with json responses with a remote referrer when there's an x-origin header set" do
|
20
|
+
get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_X_ORIGIN' => 'http://good.com').should be_ok
|
21
|
+
end
|
22
|
+
|
15
23
|
it "accepts get requests with json responses with a local referrer" do
|
16
24
|
get('/', {}, 'HTTP_REFERER' => '/').should be_ok
|
17
25
|
end
|
@@ -19,6 +27,11 @@ describe Rack::Protection::JsonCsrf do
|
|
19
27
|
it "accepts get requests with json responses with no referrer" do
|
20
28
|
get('/', {}).should be_ok
|
21
29
|
end
|
30
|
+
|
31
|
+
it "accepts XHR requests" do
|
32
|
+
get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest').should be_ok
|
33
|
+
end
|
34
|
+
|
22
35
|
end
|
23
36
|
|
24
37
|
describe 'not json response' do
|
@@ -29,4 +42,17 @@ describe Rack::Protection::JsonCsrf do
|
|
29
42
|
end
|
30
43
|
|
31
44
|
end
|
45
|
+
|
46
|
+
describe 'with drop_session as default reaction' do
|
47
|
+
it 'still denies' do
|
48
|
+
mock_app do
|
49
|
+
use Rack::Protection, :reaction => :drop_session
|
50
|
+
run proc { |e| [200, {'Content-Type' => 'application/json'}, []]}
|
51
|
+
end
|
52
|
+
|
53
|
+
session = {:foo => :bar}
|
54
|
+
get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'rack.session' => session)
|
55
|
+
last_response.should_not be_ok
|
56
|
+
end
|
57
|
+
end
|
32
58
|
end
|
data/spec/path_traversal_spec.rb
CHANGED
@@ -14,10 +14,28 @@ describe Rack::Protection::PathTraversal do
|
|
14
14
|
|
15
15
|
{ # yes, this is ugly, feel free to change that
|
16
16
|
'/..' => '/', '/a/../b' => '/b', '/a/../b/' => '/b/', '/a/.' => '/a/',
|
17
|
-
'/%2e.' => '/', '/a/%
|
18
|
-
'//' => '/', '/%2fetc%
|
17
|
+
'/%2e.' => '/', '/a/%2E%2e/b' => '/b', '/a%2f%2E%2e%2Fb/' => '/b/',
|
18
|
+
'//' => '/', '/%2fetc%2Fpasswd' => '/etc/passwd'
|
19
19
|
}.each do |a, b|
|
20
20
|
it("replaces #{a.inspect} with #{b.inspect}") { get(a).body.should == b }
|
21
21
|
end
|
22
|
+
|
23
|
+
it 'should be able to deal with PATH_INFO = nil (fcgi?)' do
|
24
|
+
app = Rack::Protection::PathTraversal.new(proc { 42 })
|
25
|
+
app.call({}).should be == 42
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if "".respond_to?(:encoding) # Ruby 1.9+ M17N
|
30
|
+
context "PATH_INFO's encoding" do
|
31
|
+
before do
|
32
|
+
@app = Rack::Protection::PathTraversal.new(proc { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO'].encoding.to_s]] })
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should remain unchanged as ASCII-8BIT' do
|
36
|
+
body = @app.call({ 'PATH_INFO' => '/'.encode('ASCII-8BIT') })[2][0]
|
37
|
+
body.should == 'ASCII-8BIT'
|
38
|
+
end
|
39
|
+
end
|
22
40
|
end
|
23
41
|
end
|
data/spec/protection_spec.rb
CHANGED
@@ -2,4 +2,104 @@ require File.expand_path('../spec_helper.rb', __FILE__)
|
|
2
2
|
|
3
3
|
describe Rack::Protection do
|
4
4
|
it_behaves_like "any rack application"
|
5
|
+
|
6
|
+
it 'passes on options' do
|
7
|
+
mock_app do
|
8
|
+
use Rack::Protection, :track => ['HTTP_FOO']
|
9
|
+
run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] }
|
10
|
+
end
|
11
|
+
|
12
|
+
session = {:foo => :bar}
|
13
|
+
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a'
|
14
|
+
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b'
|
15
|
+
session[:foo].should be == :bar
|
16
|
+
|
17
|
+
get '/', {}, 'rack.session' => session, 'HTTP_FOO' => 'BAR'
|
18
|
+
session.should be_empty
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'passes errors through if :reaction => :report is used' do
|
22
|
+
mock_app do
|
23
|
+
use Rack::Protection, :reaction => :report
|
24
|
+
run proc { |e| [200, {'Content-Type' => 'text/plain'}, [e["protection.failed"].to_s]] }
|
25
|
+
end
|
26
|
+
|
27
|
+
session = {:foo => :bar}
|
28
|
+
post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com')
|
29
|
+
last_response.should be_ok
|
30
|
+
body.should == "true"
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#react" do
|
34
|
+
it 'prevents attacks and warns about it' do
|
35
|
+
io = StringIO.new
|
36
|
+
mock_app do
|
37
|
+
use Rack::Protection, :logger => Logger.new(io)
|
38
|
+
run DummyApp
|
39
|
+
end
|
40
|
+
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
|
41
|
+
io.string.should match /prevented.*Origin/
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'reports attacks if reaction is to report' do
|
45
|
+
io = StringIO.new
|
46
|
+
mock_app do
|
47
|
+
use Rack::Protection, :reaction => :report, :logger => Logger.new(io)
|
48
|
+
run DummyApp
|
49
|
+
end
|
50
|
+
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
|
51
|
+
io.string.should match /reported.*Origin/
|
52
|
+
io.string.should_not match /prevented.*Origin/
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'passes errors to reaction method if specified' do
|
56
|
+
io = StringIO.new
|
57
|
+
Rack::Protection::Base.send(:define_method, :special) { |*args| io << args.inspect }
|
58
|
+
mock_app do
|
59
|
+
use Rack::Protection, :reaction => :special, :logger => Logger.new(io)
|
60
|
+
run DummyApp
|
61
|
+
end
|
62
|
+
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
|
63
|
+
io.string.should match /HTTP_ORIGIN.*malicious.com/
|
64
|
+
io.string.should_not match /reported|prevented/
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#html?" do
|
69
|
+
context "given an appropriate content-type header" do
|
70
|
+
subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/html" }
|
71
|
+
it { should be_true }
|
72
|
+
end
|
73
|
+
|
74
|
+
context "given an inappropriate content-type header" do
|
75
|
+
subject { Rack::Protection::Base.new(nil).html? 'content-type' => "image/gif" }
|
76
|
+
it { should be_false }
|
77
|
+
end
|
78
|
+
|
79
|
+
context "given no content-type header" do
|
80
|
+
subject { Rack::Protection::Base.new(nil).html?({}) }
|
81
|
+
it { should be_false }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#instrument" do
|
86
|
+
let(:env) { { 'rack.protection.attack' => 'base' } }
|
87
|
+
let(:instrumenter) { double('Instrumenter') }
|
88
|
+
|
89
|
+
after do
|
90
|
+
app.instrument(env)
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'with an instrumenter specified' do
|
94
|
+
let(:app) { Rack::Protection::Base.new(nil, :instrumenter => instrumenter) }
|
95
|
+
|
96
|
+
it { instrumenter.should_receive(:instrument).with('rack.protection', env) }
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'with no instrumenter specified' do
|
100
|
+
let(:app) { Rack::Protection::Base.new(nil) }
|
101
|
+
|
102
|
+
it { instrumenter.should_not_receive(:instrument) }
|
103
|
+
end
|
104
|
+
end
|
5
105
|
end
|
@@ -17,11 +17,12 @@ describe Rack::Protection::SessionHijacking do
|
|
17
17
|
session.should be_empty
|
18
18
|
end
|
19
19
|
|
20
|
-
it "
|
20
|
+
it "accepts requests with a changing Accept-Encoding header" do
|
21
|
+
# this is tested because previously it led to clearing the session
|
21
22
|
session = {:foo => :bar}
|
22
23
|
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a'
|
23
24
|
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b'
|
24
|
-
session.
|
25
|
+
session.should_not be_empty
|
25
26
|
end
|
26
27
|
|
27
28
|
it "denies requests with a changing Accept-Language header" do
|
@@ -31,10 +32,24 @@ describe Rack::Protection::SessionHijacking do
|
|
31
32
|
session.should be_empty
|
32
33
|
end
|
33
34
|
|
34
|
-
it "
|
35
|
+
it "accepts requests with the same Accept-Language header" do
|
36
|
+
session = {:foo => :bar}
|
37
|
+
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a'
|
38
|
+
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a'
|
39
|
+
session.should_not be_empty
|
40
|
+
end
|
41
|
+
|
42
|
+
it "comparison of Accept-Language header is not case sensitive" do
|
43
|
+
session = {:foo => :bar}
|
44
|
+
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a'
|
45
|
+
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'A'
|
46
|
+
session.should_not be_empty
|
47
|
+
end
|
48
|
+
|
49
|
+
it "accepts requests with a changing Version header"do
|
35
50
|
session = {:foo => :bar}
|
36
51
|
get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.0'
|
37
52
|
get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.1'
|
38
|
-
session.should
|
53
|
+
session[:foo].should == :bar
|
39
54
|
end
|
40
55
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack/protection'
|
2
2
|
require 'rack/test'
|
3
|
+
require 'rack'
|
3
4
|
require 'forwardable'
|
4
5
|
require 'stringio'
|
5
6
|
|
@@ -21,10 +22,15 @@ if version == "1.3"
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
25
|
+
unless Rack::MockResponse.method_defined? :header
|
26
|
+
Rack::MockResponse.send(:alias_method, :header, :headers)
|
27
|
+
end
|
28
|
+
|
24
29
|
module DummyApp
|
25
30
|
def self.call(env)
|
26
31
|
Thread.current[:last_env] = env
|
27
|
-
[
|
32
|
+
body = (env['REQUEST_METHOD'] == 'HEAD' ? '' : 'ok')
|
33
|
+
[200, {'Content-Type' => env['wants'] || 'text/plain'}, [body]]
|
28
34
|
end
|
29
35
|
end
|
30
36
|
|
data/spec/xss_header_spec.rb
CHANGED
@@ -4,7 +4,15 @@ describe Rack::Protection::XSSHeader do
|
|
4
4
|
it_behaves_like "any rack application"
|
5
5
|
|
6
6
|
it 'should set the X-XSS-Protection' do
|
7
|
-
get('/').headers["X-XSS-Protection"].should == "1; mode=block"
|
7
|
+
get('/', {}, 'wants' => 'text/html;charset=utf-8').headers["X-XSS-Protection"].should == "1; mode=block"
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should set the X-XSS-Protection for XHTML' do
|
11
|
+
get('/', {}, 'wants' => 'application/xhtml+xml').headers["X-XSS-Protection"].should == "1; mode=block"
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should not set the X-XSS-Protection for other content types' do
|
15
|
+
get('/', {}, 'wants' => 'application/foo').headers["X-XSS-Protection"].should be_nil
|
8
16
|
end
|
9
17
|
|
10
18
|
it 'should allow changing the protection mode' do
|
@@ -14,11 +22,35 @@ describe Rack::Protection::XSSHeader do
|
|
14
22
|
run DummyApp
|
15
23
|
end
|
16
24
|
|
17
|
-
get('/').headers["X-XSS-Protection"].should == "1; mode=foo"
|
25
|
+
get('/', {}, 'wants' => 'application/xhtml').headers["X-XSS-Protection"].should == "1; mode=foo"
|
18
26
|
end
|
19
27
|
|
20
28
|
it 'should not override the header if already set' do
|
21
29
|
mock_app with_headers("X-XSS-Protection" => "0")
|
22
|
-
get('/').headers["X-XSS-Protection"].should == "0"
|
30
|
+
get('/', {}, 'wants' => 'text/html').headers["X-XSS-Protection"].should == "0"
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should set the X-Content-Type-Options' do
|
34
|
+
get('/', {}, 'wants' => 'text/html').header["X-Content-Type-Options"].should == "nosniff"
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
it 'should set the X-Content-Type-Options for other content types' do
|
39
|
+
get('/', {}, 'wants' => 'application/foo').header["X-Content-Type-Options"].should == "nosniff"
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
it 'should allow changing the nosniff-mode off' do
|
44
|
+
mock_app do
|
45
|
+
use Rack::Protection::XSSHeader, :nosniff => false
|
46
|
+
run DummyApp
|
47
|
+
end
|
48
|
+
|
49
|
+
get('/').headers["X-Content-Type-Options"].should be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should not override the header if already set X-Content-Type-Options' do
|
53
|
+
mock_app with_headers("X-Content-Type-Options" => "sniff")
|
54
|
+
get('/', {}, 'wants' => 'text/html').headers["X-Content-Type-Options"].should == "sniff"
|
23
55
|
end
|
24
56
|
end
|