secure_headers 1.1.1 → 1.4.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 (80) hide show
  1. data/.ruby-gemset +1 -0
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +2 -3
  4. data/Gemfile +2 -7
  5. data/HISTORY.md +47 -0
  6. data/README.md +74 -27
  7. data/app/controllers/content_security_policy_controller.rb +1 -0
  8. data/fixtures/rails_3_2_12/Gemfile +0 -8
  9. data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -1
  10. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +0 -3
  11. data/fixtures/rails_3_2_12/app/views/things/index.html.erb +1 -21
  12. data/fixtures/rails_3_2_12/config/application.rb +0 -54
  13. data/fixtures/rails_3_2_12/config/environments/test.rb +2 -2
  14. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +1 -0
  15. data/fixtures/rails_3_2_12/config/routes.rb +0 -57
  16. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +15 -14
  17. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +12 -12
  18. data/fixtures/rails_3_2_12/spec/spec_helper.rb +1 -5
  19. data/fixtures/rails_3_2_12_no_init/Gemfile +0 -9
  20. data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +1 -1
  21. data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -2
  22. data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -21
  23. data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -51
  24. data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +2 -2
  25. data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -57
  26. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +12 -12
  27. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +12 -12
  28. data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +3 -18
  29. data/lib/secure_headers/headers/content_security_policy.rb +54 -35
  30. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  31. data/lib/secure_headers/headers/x_download_options.rb +39 -0
  32. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  33. data/lib/secure_headers/padrino.rb +14 -0
  34. data/lib/secure_headers/railtie.rb +6 -2
  35. data/lib/secure_headers/version.rb +1 -1
  36. data/lib/secure_headers.rb +9 -2
  37. data/spec/controllers/content_security_policy_controller_spec.rb +19 -19
  38. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +117 -85
  39. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +21 -20
  40. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +12 -12
  41. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +32 -0
  42. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +12 -12
  43. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -19
  44. data/spec/lib/secure_headers_spec.rb +38 -12
  45. data/spec/spec_helper.rb +10 -24
  46. metadata +9 -37
  47. data/.rvmrc +0 -1
  48. data/Guardfile +0 -10
  49. data/fixtures/rails_3_2_12/Guardfile +0 -14
  50. data/fixtures/rails_3_2_12/app/models/thing.rb +0 -3
  51. data/fixtures/rails_3_2_12/config/database.yml +0 -25
  52. data/fixtures/rails_3_2_12/config/environments/development.rb +0 -37
  53. data/fixtures/rails_3_2_12/config/environments/production.rb +0 -67
  54. data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +0 -7
  55. data/fixtures/rails_3_2_12/config/initializers/inflections.rb +0 -15
  56. data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +0 -5
  57. data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +0 -7
  58. data/fixtures/rails_3_2_12/config/initializers/session_store.rb +0 -8
  59. data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +0 -14
  60. data/fixtures/rails_3_2_12/config/locales/en.yml +0 -5
  61. data/fixtures/rails_3_2_12/db/schema.rb +0 -16
  62. data/fixtures/rails_3_2_12/db/seeds.rb +0 -7
  63. data/fixtures/rails_3_2_12_no_init/Guardfile +0 -14
  64. data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +0 -3
  65. data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +0 -17
  66. data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +0 -6
  67. data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +0 -5
  68. data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +0 -5
  69. data/fixtures/rails_3_2_12_no_init/config/database.yml +0 -25
  70. data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +0 -37
  71. data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +0 -67
  72. data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +0 -7
  73. data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +0 -15
  74. data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +0 -5
  75. data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +0 -7
  76. data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +0 -8
  77. data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +0 -14
  78. data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +0 -5
  79. data/fixtures/rails_3_2_12_no_init/db/schema.rb +0 -16
  80. data/fixtures/rails_3_2_12_no_init/db/seeds.rb +0 -7
@@ -32,58 +32,58 @@ describe ContentSecurityPolicyController do
32
32
  let(:secondary_endpoint) { "https://internal.example.com" }
33
33
 
34
34
  before(:each) do
35
- SecureHeaders::Configuration.stub(:csp).and_return({:report_uri => endpoint, :forward_endpoint => secondary_endpoint})
36
- subject.should_receive :head
37
- subject.stub(:params).and_return(params)
38
- subject.stub(:request).and_return(FakeRequest.new)
39
- Net::HTTP.any_instance.stub(:request)
35
+ allow(SecureHeaders::Configuration).to receive(:csp).and_return({:report_uri => endpoint, :forward_endpoint => secondary_endpoint})
36
+ expect(subject).to receive :head
37
+ allow(subject).to receive(:params).and_return(params)
38
+ allow(subject).to receive(:request).and_return(FakeRequest.new)
39
+ allow_any_instance_of(Net::HTTP).to receive(:request)
40
40
  end
41
41
 
42
42
  context "delivery endpoint" do
43
43
  it "posts over ssl" do
44
- subject.should_receive(:use_ssl)
44
+ expect(subject).to receive(:use_ssl)
45
45
  subject.scribe
46
46
  end
47
47
 
48
48
  it "posts over plain http" do
49
- SecureHeaders::Configuration.stub(:csp).and_return(:report_uri => 'http://example.com')
50
- subject.should_not_receive(:use_ssl)
49
+ allow(SecureHeaders::Configuration).to receive(:csp).and_return(:report_uri => 'http://example.com')
50
+ expect(subject).not_to receive(:use_ssl)
51
51
  subject.scribe
52
52
  end
53
53
  end
54
54
 
55
55
  it "makes a POST request" do
56
- Net::HTTP.stub(:new).and_return(request)
57
- request.should_receive(:request).with(instance_of(::Net::HTTP::Post))
58
- params.stub(:to_json)
56
+ allow(Net::HTTP).to receive(:new).and_return(request)
57
+ expect(request).to receive(:request).with(instance_of(::Net::HTTP::Post))
58
+ allow(params).to receive(:to_json)
59
59
  subject.scribe
60
60
  end
61
61
 
62
62
  it "POSTs to the configured forward_endpoint" do
63
- Net::HTTP::Post.should_receive(:new).with(secondary_endpoint).and_return(request)
63
+ expect(Net::HTTP::Post).to receive(:new).with(secondary_endpoint).and_return(request)
64
64
  subject.scribe
65
65
  end
66
66
 
67
67
  it "does not POST if there is no forwarder configured" do
68
- SecureHeaders::Configuration.stub(:csp).and_return({})
69
- Net::HTTP::Post.should_not_receive(:new)
68
+ allow(SecureHeaders::Configuration).to receive(:csp).and_return({})
69
+ expect(Net::HTTP::Post).not_to receive(:new)
70
70
  subject.scribe
71
71
  end
72
72
 
73
73
  it "eliminates known phony CSP reports" do
74
- SecureHeaders::Configuration.stub(:csp).and_return(:report_uri => nil)
75
- Net::HTTP::Post.should_not_receive :new
74
+ allow(SecureHeaders::Configuration).to receive(:csp).and_return(:report_uri => nil)
75
+ expect(Net::HTTP::Post).not_to receive :new
76
76
  subject.scribe
77
77
  end
78
78
 
79
79
  it "logs errors when it cannot forward the CSP report" do
80
80
  class Rails; def logger; end; end
81
81
  logger = double(:repond_to? => true)
82
- Rails.stub(:logger).and_return(logger)
82
+ allow(Rails).to receive(:logger).and_return(logger)
83
83
 
84
- SecureHeaders::Configuration.stub(:csp).and_raise(StandardError)
84
+ allow(SecureHeaders::Configuration).to receive(:csp).and_raise(StandardError)
85
85
 
86
- logger.should_receive(:warn)
86
+ expect(logger).to receive(:warn)
87
87
  subject.scribe
88
88
  end
89
89
  end
@@ -30,38 +30,38 @@ module SecureHeaders
30
30
 
31
31
  describe "#name" do
32
32
  context "when supplying options to override request" do
33
- specify { ContentSecurityPolicy.new(default_opts, :ua => IE).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
34
- specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
35
- specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
36
- specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
37
- specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
33
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => IE).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
34
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
35
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
36
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
37
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
38
38
  end
39
39
 
40
40
  context "when in report-only mode" do
41
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
42
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
43
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
44
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
45
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
41
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
42
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
43
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
44
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
45
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
46
46
  end
47
47
 
48
48
  context "when in enforce mode" do
49
49
  let(:opts) { default_opts.merge(:enforce => true)}
50
50
 
51
- specify { ContentSecurityPolicy.new(opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME}
52
- specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name.should == STANDARD_HEADER_NAME}
53
- specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name.should == STANDARD_HEADER_NAME}
54
- specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name.should == STANDARD_HEADER_NAME}
55
- specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name.should == STANDARD_HEADER_NAME}
51
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(IE)).name).to eq(STANDARD_HEADER_NAME)}
52
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name).to eq(STANDARD_HEADER_NAME)}
53
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name).to eq(STANDARD_HEADER_NAME)}
54
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name).to eq(STANDARD_HEADER_NAME)}
55
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name).to eq(STANDARD_HEADER_NAME)}
56
56
  end
57
57
 
58
58
  context "when in experimental mode" do
59
59
  let(:opts) { default_opts.merge(:enforce => true).merge(:experimental => {})}
60
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(IE)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
61
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
62
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX_23)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
63
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
64
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME_25)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
60
+ specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(IE)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
61
+ specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
62
+ specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX_23)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
63
+ specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
64
+ specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME_25)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
65
65
  end
66
66
  end
67
67
 
@@ -76,7 +76,41 @@ module SecureHeaders
76
76
  context "Content-Security-Policy" do
77
77
  it "converts the script values to their equivilents" do
78
78
  csp = ContentSecurityPolicy.new(@opts, :request => request_for(CHROME))
79
- csp.value.should include("script-src 'unsafe-inline' 'unsafe-eval' https://* data: 'self' 'none'")
79
+ expect(csp.value).to include("script-src 'unsafe-inline' 'unsafe-eval' https://* data: 'self' 'none'")
80
+ end
81
+
82
+ it "adds a @enforce and @app_name variables to the report uri" do
83
+ opts = @opts.merge(:tag_report_uri => true, :enforce => true, :app_name => 'twitter')
84
+ csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
85
+ expect(csp.value).to include("/csp_report?enforce=true&app_name=twitter")
86
+ end
87
+
88
+ it "does not add an empty @app_name variable to the report uri" do
89
+ opts = @opts.merge(:tag_report_uri => true, :enforce => true)
90
+ csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
91
+ expect(csp.value).to include("/csp_report?enforce=true")
92
+ end
93
+
94
+ it "accepts procs for report-uris" do
95
+ opts = {
96
+ :default_src => 'self',
97
+ :report_uri => lambda { "http://lambda/result" }
98
+ }
99
+
100
+ csp = ContentSecurityPolicy.new(opts)
101
+ expect(csp.report_uri).to eq("http://lambda/result")
102
+ end
103
+
104
+ it "accepts procs for other fields" do
105
+ opts = {
106
+ :default_src => lambda { "http://lambda/result" },
107
+ :enforce => lambda { true },
108
+ :disable_fill_missing => lambda { true }
109
+ }
110
+
111
+ csp = ContentSecurityPolicy.new(opts)
112
+ expect(csp.value).to eq("default-src http://lambda/result; img-src http://lambda/result data:;")
113
+ expect(csp.name).to match("Content-Security-Policy")
80
114
  end
81
115
  end
82
116
  end
@@ -86,47 +120,47 @@ module SecureHeaders
86
120
 
87
121
  it "matches when host, scheme, and port match" do
88
122
  csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com"))
89
- csp.send(:same_origin?).should be_true
123
+ expect(csp.send(:same_origin?)).to be true
90
124
 
91
125
  csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com:443"))
92
- csp.send(:same_origin?).should be_true
126
+ expect(csp.send(:same_origin?)).to be true
93
127
 
94
128
  csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com:123'}, :request => request_for(FIREFOX, "https://example.com:123"))
95
- csp.send(:same_origin?).should be_true
129
+ expect(csp.send(:same_origin?)).to be true
96
130
 
97
131
  csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com"))
98
- csp.send(:same_origin?).should be_true
132
+ expect(csp.send(:same_origin?)).to be true
99
133
 
100
134
  csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com:80'}, :request => request_for(FIREFOX, "http://example.com"))
101
- csp.send(:same_origin?).should be_true
135
+ expect(csp.send(:same_origin?)).to be true
102
136
 
103
137
  csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:80"))
104
- csp.send(:same_origin?).should be_true
138
+ expect(csp.send(:same_origin?)).to be true
105
139
  end
106
140
 
107
141
  it "does not match port mismatches" do
108
142
  csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:81"))
109
- csp.send(:same_origin?).should be_false
143
+ expect(csp.send(:same_origin?)).to be false
110
144
  end
111
145
 
112
146
  it "does not match host mismatches" do
113
147
  csp = ContentSecurityPolicy.new({:report_uri => 'http://twitter.com'}, :request => request_for(FIREFOX, "http://example.com"))
114
- csp.send(:same_origin?).should be_false
148
+ expect(csp.send(:same_origin?)).to be false
115
149
  end
116
150
 
117
151
  it "does not match host mismatches because of subdomains" do
118
152
  csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://sub.example.com"))
119
- csp.send(:same_origin?).should be_false
153
+ expect(csp.send(:same_origin?)).to be false
120
154
  end
121
155
 
122
156
  it "does not match scheme mismatches" do
123
157
  csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "ftp://example.com"))
124
- csp.send(:same_origin?).should be_false
158
+ expect(csp.send(:same_origin?)).to be false
125
159
  end
126
160
 
127
161
  it "does not match on substring collisions" do
128
162
  csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://anotherexample.com"))
129
- csp.send(:same_origin?).should be_false
163
+ expect(csp.send(:same_origin?)).to be false
130
164
  end
131
165
  end
132
166
 
@@ -136,29 +170,29 @@ module SecureHeaders
136
170
  context "when using firefox" do
137
171
  it "updates the report-uri when posting to a different host" do
138
172
  csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://anexample.com"))
139
- csp.report_uri.should == FF_CSP_ENDPOINT
173
+ expect(csp.report_uri).to eq(FF_CSP_ENDPOINT)
140
174
  end
141
175
 
142
176
  it "doesn't change report-uri if a path supplied" do
143
177
  csp = ContentSecurityPolicy.new({:report_uri => "/csp_reports"}, :request => request_for(FIREFOX, "https://anexample.com"))
144
- csp.report_uri.should == "/csp_reports"
178
+ expect(csp.report_uri).to eq("/csp_reports")
145
179
  end
146
180
 
147
181
  it "forwards if the request_uri is set to a non-matching value" do
148
182
  csp = ContentSecurityPolicy.new({:report_uri => "https://another.example.com", :forward_endpoint => '/somewhere'}, :ua => "Firefox", :request_uri => "https://anexample.com")
149
- csp.report_uri.should == FF_CSP_ENDPOINT
183
+ expect(csp.report_uri).to eq(FF_CSP_ENDPOINT)
150
184
  end
151
185
  end
152
186
 
153
187
  it "does not update the URI is the report_uri is on the same origin" do
154
188
  opts = {:report_uri => 'https://example.com/csp', :forward_endpoint => 'https://anotherexample.com'}
155
189
  csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://example.com/somewhere"))
156
- csp.report_uri.should == 'https://example.com/csp'
190
+ expect(csp.report_uri).to eq('https://example.com/csp')
157
191
  end
158
192
 
159
193
  it "does not update the report-uri when using a non-firefox browser" do
160
194
  csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
161
- csp.report_uri.should == 'https://example.com/csp'
195
+ expect(csp.report_uri).to eq('https://example.com/csp')
162
196
  end
163
197
 
164
198
  context "when using a protocol-relative value for report-uri" do
@@ -171,20 +205,20 @@ module SecureHeaders
171
205
 
172
206
  it "uses the current protocol" do
173
207
  csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, '/', :ssl => true))
174
- csp.value.should =~ %r{report-uri https://example.com/csp;}
208
+ expect(csp.value).to match(%r{report-uri https://example.com/csp;})
175
209
 
176
210
  csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
177
- csp.value.should =~ %r{report-uri http://example.com/csp;}
211
+ expect(csp.value).to match(%r{report-uri http://example.com/csp;})
178
212
  end
179
213
 
180
214
  it "uses the pre-configured https protocol" do
181
215
  csp = ContentSecurityPolicy.new(opts, :ua => "Firefox", :ssl => true)
182
- csp.value.should =~ %r{report-uri https://example.com/csp;}
216
+ expect(csp.value).to match(%r{report-uri https://example.com/csp;})
183
217
  end
184
218
 
185
219
  it "uses the pre-configured http protocol" do
186
220
  csp = ContentSecurityPolicy.new(opts, :ua => "Firefox", :ssl => false)
187
- csp.value.should =~ %r{report-uri http://example.com/csp;}
221
+ expect(csp.value).to match(%r{report-uri http://example.com/csp;})
188
222
  end
189
223
  end
190
224
  end
@@ -192,20 +226,20 @@ module SecureHeaders
192
226
  describe "#value" do
193
227
  it "raises an exception when default-src is missing" do
194
228
  csp = ContentSecurityPolicy.new({:script_src => 'anything'}, :request => request_for(CHROME))
195
- lambda {
229
+ expect {
196
230
  csp.value
197
- }.should raise_error(ContentSecurityPolicyBuildError, "Couldn't build CSP header :( Expected to find default_src directive value")
231
+ }.to raise_error(ContentSecurityPolicyBuildError, "Couldn't build CSP header :( Expected to find default_src directive value")
198
232
  end
199
233
 
200
234
  context "auto-whitelists data: uris for img-src" do
201
235
  it "sets the value if no img-src specified" do
202
236
  csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
203
- csp.value.should == "default-src 'self'; img-src data:;"
237
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
204
238
  end
205
239
 
206
240
  it "appends the value if img-src is specified" do
207
241
  csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
208
- csp.value.should == "default-src 'self'; img-src 'self' data:;"
242
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
209
243
  end
210
244
  end
211
245
 
@@ -213,30 +247,55 @@ module SecureHeaders
213
247
  options = default_opts.merge(:disable_fill_missing => false)
214
248
  csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
215
249
  value = "default-src https://*; connect-src https://*; font-src https://*; frame-src https://*; img-src https://* data:; media-src https://*; object-src https://*; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
216
- csp.value.should == value
250
+ expect(csp.value).to eq(value)
217
251
  end
218
252
 
219
253
  it "sends the standard csp header if an unknown browser is supplied" do
220
254
  csp = ContentSecurityPolicy.new(default_opts, :request => request_for(IE))
221
- csp.value.should match "default-src"
255
+ expect(csp.value).to match "default-src"
222
256
  end
223
257
 
224
258
  context "Firefox" do
225
259
  it "builds a csp header for firefox" do
226
260
  csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX))
227
- csp.value.should == "default-src https://*; img-src data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
261
+ expect(csp.value).to eq("default-src https://*; img-src https://* data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;")
228
262
  end
229
263
  end
230
264
 
231
265
  context "Chrome" do
232
266
  it "builds a csp header for chrome" do
233
267
  csp = ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME))
234
- csp.value.should == "default-src https://*; img-src data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
268
+ expect(csp.value).to eq("default-src https://*; img-src https://* data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;")
235
269
  end
236
270
 
237
271
  it "ignores :forward_endpoint settings" do
238
272
  csp = ContentSecurityPolicy.new(@options_with_forwarding, :request => request_for(CHROME))
239
- csp.value.should =~ /report-uri #{@options_with_forwarding[:report_uri]};/
273
+ expect(csp.value).to match(/report-uri #{@options_with_forwarding[:report_uri]};/)
274
+ end
275
+ end
276
+
277
+ context "when using a nonce" do
278
+ it "adds a nonce and unsafe-inline to the script-src value" do
279
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME))
280
+ expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
281
+ end
282
+
283
+ it "adds a nonce and unsafe-inline to the style-src value" do
284
+ header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME))
285
+ expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
286
+ end
287
+
288
+ it "adds an identical nonce to the style and script-src directives" do
289
+ header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce", :script_src => "self nonce"), :request => request_for(CHROME))
290
+ nonce = header.nonce
291
+ value = header.value
292
+ expect(value).to include("style-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
293
+ expect(value).to include("script-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
294
+ end
295
+
296
+ it "does not add 'unsafe-inline' twice" do
297
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce inline"), :request => request_for(CHROME))
298
+ expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline';")
240
299
  end
241
300
  end
242
301
 
@@ -253,12 +312,12 @@ module SecureHeaders
253
312
 
254
313
  it "returns the original value" do
255
314
  header = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
256
- header.value.should == "default-src 'self'; img-src data:; script-src https://*;"
315
+ expect(header.value).to eq("default-src 'self'; img-src 'self' data:; script-src https://*;")
257
316
  end
258
317
 
259
318
  it "it returns the experimental value if requested" do
260
319
  header = ContentSecurityPolicy.new(options, {:request => request_for(CHROME), :experimental => true})
261
- header.value.should_not =~ /https/
320
+ expect(header.value).not_to match(/https/)
262
321
  end
263
322
  end
264
323
 
@@ -274,17 +333,17 @@ module SecureHeaders
274
333
 
275
334
  it "adds directive values for headers on http" do
276
335
  csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
277
- csp.value.should == "default-src https://*; frame-src http://*; img-src http://* data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
336
+ expect(csp.value).to eq("default-src https://*; frame-src http://*; img-src http://* data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;")
278
337
  end
279
338
 
280
339
  it "does not add the directive values if requesting https" do
281
340
  csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME, '/', :ssl => true))
282
- csp.value.should_not =~ /http:/
341
+ expect(csp.value).not_to match(/http:/)
283
342
  end
284
343
 
285
344
  it "does not add the directive values if requesting https" do
286
345
  csp = ContentSecurityPolicy.new(options, :ua => "Chrome", :ssl => true)
287
- csp.value.should_not =~ /http:/
346
+ expect(csp.value).not_to match(/http:/)
288
347
  end
289
348
 
290
349
  context "when supplying an experimental block" do
@@ -314,47 +373,20 @@ module SecureHeaders
314
373
 
315
374
  it "uses the value in the experimental block over SSL" do
316
375
  csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX, '/', :ssl => true))
317
- csp.value.should == "default-src 'self'; img-src data:; script-src 'self';"
376
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:; script-src 'self';")
318
377
  end
319
378
 
320
379
  it "detects the :ssl => true option" do
321
380
  csp = ContentSecurityPolicy.new(options, :experimental => true, :ua => FIREFOX, :ssl => true)
322
- csp.value.should == "default-src 'self'; img-src data:; script-src 'self';"
381
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:; script-src 'self';")
323
382
  end
324
383
 
325
384
  it "merges the values from experimental/http_additions when not over SSL" do
326
385
  csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX))
327
- csp.value.should == "default-src 'self'; img-src data:; script-src 'self' https://mycdn.example.com;"
386
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:; script-src 'self' https://mycdn.example.com;")
328
387
  end
329
388
  end
330
389
  end
331
-
332
- context "when supplying a script nonce callback" do
333
- let(:options) {
334
- default_opts.merge({
335
- :script_nonce => "random",
336
- })
337
- }
338
-
339
- it "uses the value in the X-Webkit-CSP" do
340
- csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
341
- csp.value.should match "script-nonce random;"
342
- end
343
-
344
- it "runs a dynamic nonce generator" do
345
- options[:script_nonce] = lambda { 'something' }
346
- csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
347
- csp.value.should match "script-nonce something;"
348
- end
349
-
350
- it "runs against the given controller context" do
351
- fake_params = {}
352
- options[:script_nonce] = lambda { params[:script_nonce] = 'something' }
353
- csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME), :controller => double(:params => fake_params))
354
- csp.value.should match "script-nonce something;"
355
- fake_params.should == {:script_nonce => 'something'}
356
- end
357
- end
358
390
  end
359
391
  end
360
392
  end
@@ -2,59 +2,60 @@ require 'spec_helper'
2
2
 
3
3
  module SecureHeaders
4
4
  describe StrictTransportSecurity do
5
- specify{ StrictTransportSecurity.new.name.should == "Strict-Transport-Security" }
5
+ specify{ expect(StrictTransportSecurity.new.name).to eq("Strict-Transport-Security") }
6
6
 
7
7
  describe "#value" do
8
- specify { StrictTransportSecurity.new.value.should == StrictTransportSecurity::Constants::DEFAULT_VALUE}
9
- specify { StrictTransportSecurity.new("max-age=1234").value.should == "max-age=1234"}
10
- specify { StrictTransportSecurity.new(:max_age => '1234').value.should == "max-age=1234"}
11
- specify { StrictTransportSecurity.new(:max_age => 1234).value.should == "max-age=1234"}
12
- specify { StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true).value.should == "max-age=#{HSTS_MAX_AGE}; includeSubdomains"}
8
+ specify { expect(StrictTransportSecurity.new.value).to eq(StrictTransportSecurity::Constants::DEFAULT_VALUE)}
9
+ specify { expect(StrictTransportSecurity.new("max-age=1234").value).to eq("max-age=1234")}
10
+ specify { expect(StrictTransportSecurity.new(:max_age => '1234').value).to eq("max-age=1234")}
11
+ specify { expect(StrictTransportSecurity.new(:max_age => 1234).value).to eq("max-age=1234")}
12
+ specify { expect(StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true).value).to eq("max-age=#{HSTS_MAX_AGE}; includeSubdomains")}
13
+ specify { expect(StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true).value).to eq("max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")}
13
14
 
14
15
  context "with an invalid configuration" do
15
16
  context "with a hash argument" do
16
17
  it "should allow string values for max-age" do
17
- lambda {
18
+ expect {
18
19
  StrictTransportSecurity.new(:max_age => '1234')
19
- }.should_not raise_error
20
+ }.not_to raise_error
20
21
  end
21
22
 
22
23
  it "should allow integer values for max-age" do
23
- lambda {
24
+ expect {
24
25
  StrictTransportSecurity.new(:max_age => 1234)
25
- }.should_not raise_error
26
+ }.not_to raise_error
26
27
  end
27
28
 
28
29
  it "raises an exception with an invalid max-age" do
29
- lambda {
30
+ expect {
30
31
  StrictTransportSecurity.new(:max_age => 'abc123')
31
- }.should raise_error(STSBuildError)
32
+ }.to raise_error(STSBuildError)
32
33
  end
33
34
 
34
35
  it "raises an exception if max-age is not supplied" do
35
- lambda {
36
+ expect {
36
37
  StrictTransportSecurity.new(:includeSubdomains => true)
37
- }.should raise_error(STSBuildError)
38
+ }.to raise_error(STSBuildError)
38
39
  end
39
40
  end
40
41
 
41
42
  context "with a string argument" do
42
43
  it "raises an exception with an invalid max-age" do
43
- lambda {
44
+ expect {
44
45
  StrictTransportSecurity.new('max-age=abc123')
45
- }.should raise_error(STSBuildError)
46
+ }.to raise_error(STSBuildError)
46
47
  end
47
48
 
48
49
  it "raises an exception if max-age is not supplied" do
49
- lambda {
50
+ expect {
50
51
  StrictTransportSecurity.new('includeSubdomains')
51
- }.should raise_error(STSBuildError)
52
+ }.to raise_error(STSBuildError)
52
53
  end
53
54
 
54
55
  it "raises an exception with an invalid format" do
55
- lambda {
56
+ expect {
56
57
  StrictTransportSecurity.new('max-age=123includeSubdomains')
57
- }.should raise_error(STSBuildError)
58
+ }.to raise_error(STSBuildError)
58
59
  end
59
60
  end
60
61
  end
@@ -1,33 +1,33 @@
1
1
  module SecureHeaders
2
2
  describe XContentTypeOptions do
3
- specify{ XContentTypeOptions.new.name.should == "X-Content-Type-Options" }
3
+ specify{ expect(XContentTypeOptions.new.name).to eq("X-Content-Type-Options") }
4
4
 
5
5
  describe "#value" do
6
- specify { XContentTypeOptions.new.value.should == XContentTypeOptions::Constants::DEFAULT_VALUE}
7
- specify { XContentTypeOptions.new("nosniff").value.should == "nosniff"}
8
- specify { XContentTypeOptions.new(:value => 'nosniff').value.should == "nosniff"}
6
+ specify { expect(XContentTypeOptions.new.value).to eq(XContentTypeOptions::Constants::DEFAULT_VALUE)}
7
+ specify { expect(XContentTypeOptions.new("nosniff").value).to eq("nosniff")}
8
+ specify { expect(XContentTypeOptions.new(:value => 'nosniff').value).to eq("nosniff")}
9
9
 
10
10
  context "invalid configuration values" do
11
11
  it "accepts nosniff" do
12
- lambda {
12
+ expect {
13
13
  XContentTypeOptions.new("nosniff")
14
- }.should_not raise_error
14
+ }.not_to raise_error
15
15
 
16
- lambda {
16
+ expect {
17
17
  XContentTypeOptions.new(:value => "nosniff")
18
- }.should_not raise_error
18
+ }.not_to raise_error
19
19
  end
20
20
 
21
21
  it "accepts nil" do
22
- lambda {
22
+ expect {
23
23
  XContentTypeOptions.new
24
- }.should_not raise_error
24
+ }.not_to raise_error
25
25
  end
26
26
 
27
27
  it "doesn't accept anything besides no-sniff" do
28
- lambda {
28
+ expect {
29
29
  XContentTypeOptions.new("donkey")
30
- }.should raise_error
30
+ }.to raise_error
31
31
  end
32
32
  end
33
33
  end
@@ -0,0 +1,32 @@
1
+ module SecureHeaders
2
+ describe XDownloadOptions do
3
+ specify { expect(XDownloadOptions.new.name).to eq(XDO_HEADER_NAME)}
4
+ specify { expect(XDownloadOptions.new.value).to eq("noopen")}
5
+ specify { expect(XDownloadOptions.new('noopen').value).to eq('noopen')}
6
+ specify { expect(XDownloadOptions.new(:value => 'noopen').value).to eq('noopen') }
7
+
8
+ context "invalid configuration values" do
9
+ it "accepts noopen" do
10
+ expect {
11
+ XDownloadOptions.new("noopen")
12
+ }.not_to raise_error
13
+
14
+ expect {
15
+ XDownloadOptions.new(:value => "noopen")
16
+ }.not_to raise_error
17
+ end
18
+
19
+ it "accepts nil" do
20
+ expect {
21
+ XDownloadOptions.new
22
+ }.not_to raise_error
23
+ end
24
+
25
+ it "doesn't accept anything besides noopen" do
26
+ expect {
27
+ XContentTypeOptions.new("open")
28
+ }.to raise_error
29
+ end
30
+ end
31
+ end
32
+ end