secure_headers 1.0.0 → 2.0.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 (129) hide show
  1. data/.gitignore +4 -8
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +8 -3
  5. data/Gemfile +6 -8
  6. data/README.md +208 -134
  7. data/Rakefile +11 -116
  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 +1 -4
  11. data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +2 -1
  12. data/fixtures/rails_3_2_12/app/views/things/index.html.erb +1 -21
  13. data/fixtures/rails_3_2_12/config/application.rb +0 -54
  14. data/fixtures/rails_3_2_12/config/environments/test.rb +2 -2
  15. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +3 -1
  16. data/fixtures/rails_3_2_12/config/routes.rb +0 -57
  17. data/fixtures/rails_3_2_12/config/script_hashes.yml +5 -0
  18. data/fixtures/rails_3_2_12/config.ru +3 -0
  19. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +57 -19
  20. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +18 -16
  21. data/fixtures/rails_3_2_12/spec/spec_helper.rb +1 -5
  22. data/fixtures/rails_3_2_12_no_init/Gemfile +0 -9
  23. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +1 -2
  24. data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +1 -1
  25. data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -2
  26. data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -21
  27. data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -51
  28. data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +2 -2
  29. data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -57
  30. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +17 -12
  31. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +17 -12
  32. data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +3 -18
  33. data/fixtures/rails_4_1_8/Gemfile +5 -0
  34. data/fixtures/rails_4_1_8/README.rdoc +28 -0
  35. data/fixtures/rails_4_1_8/Rakefile +6 -0
  36. data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +4 -0
  37. data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
  38. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +5 -0
  39. data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +5 -0
  40. data/fixtures/rails_4_1_8/app/models/.keep +0 -0
  41. data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
  42. data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +11 -0
  43. data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +2 -0
  44. data/fixtures/rails_4_1_8/app/views/things/index.html.erb +1 -0
  45. data/fixtures/rails_4_1_8/config/application.rb +15 -0
  46. data/fixtures/rails_4_1_8/config/boot.rb +4 -0
  47. data/fixtures/rails_4_1_8/config/environment.rb +5 -0
  48. data/fixtures/rails_4_1_8/config/environments/test.rb +10 -0
  49. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +17 -0
  50. data/fixtures/rails_4_1_8/config/routes.rb +4 -0
  51. data/fixtures/rails_4_1_8/config/script_hashes.yml +5 -0
  52. data/fixtures/rails_4_1_8/config/secrets.yml +22 -0
  53. data/fixtures/rails_4_1_8/config.ru +4 -0
  54. data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
  55. data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
  56. data/fixtures/rails_4_1_8/log/.keep +0 -0
  57. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +83 -0
  58. data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +59 -0
  59. data/fixtures/rails_4_1_8/spec/spec_helper.rb +15 -0
  60. data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
  61. data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
  62. data/lib/secure_headers/hash_helper.rb +7 -0
  63. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +22 -0
  64. data/lib/secure_headers/headers/content_security_policy.rb +160 -161
  65. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  66. data/lib/secure_headers/headers/x_download_options.rb +39 -0
  67. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +40 -0
  68. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  69. data/lib/secure_headers/padrino.rb +14 -0
  70. data/lib/secure_headers/railtie.rb +19 -24
  71. data/lib/secure_headers/version.rb +1 -1
  72. data/lib/secure_headers/view_helper.rb +68 -0
  73. data/lib/secure_headers.rb +70 -29
  74. data/lib/tasks/tasks.rake +48 -0
  75. data/secure_headers.gemspec +4 -5
  76. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +47 -0
  77. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +127 -299
  78. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +21 -20
  79. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +12 -12
  80. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +32 -0
  81. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +12 -13
  82. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +64 -0
  83. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -19
  84. data/spec/lib/secure_headers_spec.rb +68 -83
  85. data/spec/spec_helper.rb +29 -18
  86. metadata +51 -66
  87. data/.rvmrc +0 -1
  88. data/Guardfile +0 -10
  89. data/HISTORY.md +0 -100
  90. data/app/controllers/content_security_policy_controller.rb +0 -53
  91. data/config/curl-ca-bundle.crt +0 -5420
  92. data/config/routes.rb +0 -3
  93. data/fixtures/rails_3_2_12/Guardfile +0 -14
  94. data/fixtures/rails_3_2_12/app/models/thing.rb +0 -3
  95. data/fixtures/rails_3_2_12/config/database.yml +0 -25
  96. data/fixtures/rails_3_2_12/config/environments/development.rb +0 -37
  97. data/fixtures/rails_3_2_12/config/environments/production.rb +0 -67
  98. data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +0 -7
  99. data/fixtures/rails_3_2_12/config/initializers/inflections.rb +0 -15
  100. data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +0 -5
  101. data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +0 -7
  102. data/fixtures/rails_3_2_12/config/initializers/session_store.rb +0 -8
  103. data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +0 -14
  104. data/fixtures/rails_3_2_12/config/locales/en.yml +0 -5
  105. data/fixtures/rails_3_2_12/db/schema.rb +0 -16
  106. data/fixtures/rails_3_2_12/db/seeds.rb +0 -7
  107. data/fixtures/rails_3_2_12_no_init/Guardfile +0 -14
  108. data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +0 -3
  109. data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +0 -17
  110. data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +0 -6
  111. data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +0 -5
  112. data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +0 -5
  113. data/fixtures/rails_3_2_12_no_init/config/database.yml +0 -25
  114. data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +0 -37
  115. data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +0 -67
  116. data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +0 -7
  117. data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +0 -15
  118. data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +0 -5
  119. data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +0 -7
  120. data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +0 -8
  121. data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +0 -14
  122. data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +0 -5
  123. data/fixtures/rails_3_2_12_no_init/db/schema.rb +0 -16
  124. data/fixtures/rails_3_2_12_no_init/db/seeds.rb +0 -7
  125. data/lib/secure_headers/headers/content_security_policy/browser_strategy.rb +0 -78
  126. data/lib/secure_headers/headers/content_security_policy/firefox_browser_strategy.rb +0 -72
  127. data/lib/secure_headers/headers/content_security_policy/ie_browser_strategy.rb +0 -6
  128. data/lib/secure_headers/headers/content_security_policy/standard_browser_strategy.rb +0 -22
  129. data/spec/controllers/content_security_policy_controller_spec.rb +0 -74
@@ -1,16 +1,14 @@
1
1
  require 'spec_helper'
2
- require 'brwsr'
3
2
 
4
3
  module SecureHeaders
5
4
  describe ContentSecurityPolicy do
6
5
  let(:default_opts) do
7
6
  {
8
- :disable_chrome_extension => true,
9
7
  :disable_fill_missing => true,
10
- :default_src => 'https://*',
8
+ :default_src => 'https:',
11
9
  :report_uri => '/csp_report',
12
- :script_src => 'inline eval https://* data:',
13
- :style_src => "inline https://* about:"
10
+ :script_src => 'inline eval https: data:',
11
+ :style_src => "inline https: about:"
14
12
  }
15
13
  end
16
14
 
@@ -31,196 +29,84 @@ module SecureHeaders
31
29
 
32
30
  describe "#name" do
33
31
  context "when supplying options to override request" do
34
- specify { ContentSecurityPolicy.new(default_opts, :ua => IE).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
35
- specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
36
- specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
37
- specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
38
- specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
32
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => IE).name).to eq(HEADER_NAME + "-Report-Only")}
33
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name).to eq(HEADER_NAME + "-Report-Only")}
34
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name).to eq(HEADER_NAME + "-Report-Only")}
35
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME).name).to eq(HEADER_NAME + "-Report-Only")}
36
+ specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name).to eq(HEADER_NAME + "-Report-Only")}
39
37
  end
40
38
 
41
39
  context "when in report-only mode" do
42
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
43
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
44
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
45
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
46
- specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
40
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name).to eq(HEADER_NAME + "-Report-Only")}
41
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name).to eq(HEADER_NAME + "-Report-Only")}
42
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name).to eq(HEADER_NAME + "-Report-Only")}
43
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name).to eq(HEADER_NAME + "-Report-Only")}
44
+ specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name).to eq(HEADER_NAME + "-Report-Only")}
47
45
  end
48
46
 
49
47
  context "when in enforce mode" do
50
48
  let(:opts) { default_opts.merge(:enforce => true)}
51
49
 
52
- specify { ContentSecurityPolicy.new(opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME}
53
- specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name.should == FIREFOX_CSP_HEADER_NAME}
54
- specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name.should == STANDARD_HEADER_NAME}
55
- specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name.should == WEBKIT_CSP_HEADER_NAME}
56
- specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name.should == STANDARD_HEADER_NAME}
50
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(IE)).name).to eq(HEADER_NAME)}
51
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name).to eq(HEADER_NAME)}
52
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name).to eq(HEADER_NAME)}
53
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name).to eq(HEADER_NAME)}
54
+ specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name).to eq(HEADER_NAME)}
57
55
  end
56
+ end
58
57
 
59
- context "when in experimental mode" do
60
- let(:opts) { default_opts.merge(:enforce => true).merge(:experimental => {})}
61
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(IE)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
62
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
63
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX_23)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
64
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
65
- specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME_25)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
58
+ context "when using hash sources" do
59
+ it "adds hashes and unsafe-inline to the script-src" do
60
+ policy = ContentSecurityPolicy.new(default_opts.merge(:script_hashes => ['sha256-abc123']))
61
+ expect(policy.value).to match /script-src[^;]*'sha256-abc123'/
66
62
  end
67
63
  end
68
64
 
69
65
  describe "#normalize_csp_options" do
70
66
  before(:each) do
71
- default_opts.delete(:disable_chrome_extension)
72
67
  default_opts.delete(:disable_fill_missing)
73
68
  default_opts[:script_src] << ' self none'
74
69
  @opts = default_opts
75
70
  end
76
71
 
77
- context "X-Content-Security-Policy" do
78
- it "converts the script values to their equivilents" do
79
- csp = ContentSecurityPolicy.new(@opts, :request => request_for(FIREFOX))
80
- csp.value.should include("script-src https://* data: 'self' 'none'")
81
- csp.value.should include('options inline-script eval-script')
82
- end
83
- end
84
-
85
- context "X-Webkit-CSP" do
72
+ context "Content-Security-Policy" do
86
73
  it "converts the script values to their equivilents" do
87
74
  csp = ContentSecurityPolicy.new(@opts, :request => request_for(CHROME))
88
- csp.value.should include("script-src 'unsafe-inline' 'unsafe-eval' https://* data: 'self' 'none' chrome-extension")
75
+ expect(csp.value).to include("script-src 'unsafe-inline' 'unsafe-eval' https: data: 'self' 'none'")
89
76
  end
90
- end
91
- end
92
-
93
- describe "#build_impl_specific_directives" do
94
- context "X-Content-Security-Policy" do
95
- it "moves script-src inline and eval values to the options directive" do
96
- opts = {
97
- :default_src => 'https://*',
98
- :script_src => "inline eval https://*"
99
- }
100
- csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
101
- browser_specific = csp.send :build_impl_specific_directives
102
- browser_specific.should include('options inline-script eval-script;')
103
- end
104
-
105
- it "does not move values from style-src into options" do
106
- opts = {
107
- :default_src => 'https://*',
108
- :style_src => "inline eval https://*"
109
- }
110
- csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
111
- browser_specific = csp.send :build_impl_specific_directives
112
- browser_specific.should_not include('inline-script')
113
- browser_specific.should_not include('eval-script')
114
- end
115
- end
116
- end
117
-
118
- describe "#same_origin?" do
119
- let(:origin) {"https://example.com:123"}
120
-
121
- it "matches when host, scheme, and port match" do
122
- csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com"))
123
- csp.send(:same_origin?).should be_true
124
-
125
- csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com:443"))
126
- csp.send(:same_origin?).should be_true
127
-
128
- csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com:123'}, :request => request_for(FIREFOX, "https://example.com:123"))
129
- csp.send(:same_origin?).should be_true
130
-
131
- csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com"))
132
- csp.send(:same_origin?).should be_true
133
-
134
- csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com:80'}, :request => request_for(FIREFOX, "http://example.com"))
135
- csp.send(:same_origin?).should be_true
136
-
137
- csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:80"))
138
- csp.send(:same_origin?).should be_true
139
- end
140
77
 
141
- it "does not match port mismatches" do
142
- csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:81"))
143
- csp.send(:same_origin?).should be_false
144
- end
145
-
146
- it "does not match host mismatches" do
147
- csp = ContentSecurityPolicy.new({:report_uri => 'http://twitter.com'}, :request => request_for(FIREFOX, "http://example.com"))
148
- csp.send(:same_origin?).should be_false
149
- end
150
-
151
- it "does not match host mismatches because of subdomains" do
152
- csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://sub.example.com"))
153
- csp.send(:same_origin?).should be_false
154
- end
155
-
156
- it "does not match scheme mismatches" do
157
- csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "ftp://example.com"))
158
- csp.send(:same_origin?).should be_false
159
- end
160
-
161
- it "does not match on substring collisions" do
162
- csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://anotherexample.com"))
163
- csp.send(:same_origin?).should be_false
164
- end
165
-
166
-
167
- end
168
-
169
- describe "#normalize_reporting_endpoint" do
170
- let(:opts) {{:report_uri => 'https://example.com/csp', :forward_endpoint => anything}}
171
-
172
- context "when using firefox" do
173
- it "updates the report-uri when posting to a different host" do
174
- csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://anexample.com"))
175
- csp.report_uri.should == FF_CSP_ENDPOINT
176
- end
177
-
178
- it "doesn't change report-uri if a path supplied" do
179
- csp = ContentSecurityPolicy.new({:report_uri => "/csp_reports"}, :request => request_for(FIREFOX, "https://anexample.com"))
180
- csp.report_uri.should == "/csp_reports"
78
+ it "adds a @enforce and @app_name variables to the report uri" do
79
+ opts = @opts.merge(:tag_report_uri => true, :enforce => true, :app_name => lambda { 'twitter' })
80
+ csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
81
+ expect(csp.value).to include("/csp_report?enforce=true&app_name=twitter")
181
82
  end
182
83
 
183
- it "forwards if the request_uri is set to a non-matching value" do
184
- csp = ContentSecurityPolicy.new({:report_uri => "https://another.example.com", :forward_endpoint => '/somewhere'}, :ua => "Firefox", :request_uri => "https://anexample.com")
185
- csp.report_uri.should == FF_CSP_ENDPOINT
84
+ it "does not add an empty @app_name variable to the report uri" do
85
+ opts = @opts.merge(:tag_report_uri => true, :enforce => true)
86
+ csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
87
+ expect(csp.value).to include("/csp_report?enforce=true")
186
88
  end
187
- end
188
-
189
- it "does not update the URI is the report_uri is on the same origin" do
190
- opts = {:report_uri => 'https://example.com/csp', :forward_endpoint => 'https://anotherexample.com'}
191
- csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://example.com/somewhere"))
192
- csp.report_uri.should == 'https://example.com/csp'
193
- end
194
89
 
195
- it "does not update the report-uri when using a non-firefox browser" do
196
- csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
197
- csp.report_uri.should == 'https://example.com/csp'
198
- end
199
-
200
- context "when using a protocol-relative value for report-uri" do
201
- let(:opts) {
202
- {
90
+ it "accepts procs for report-uris" do
91
+ opts = {
203
92
  :default_src => 'self',
204
- :report_uri => '//example.com/csp'
93
+ :report_uri => lambda { "http://lambda/result" }
205
94
  }
206
- }
207
-
208
- it "uses the current protocol" do
209
- csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, '/', :ssl => true))
210
- csp.value.should =~ %r{report-uri https://example.com/csp;}
211
95
 
212
- csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
213
- csp.value.should =~ %r{report-uri http://example.com/csp;}
96
+ csp = ContentSecurityPolicy.new(opts)
97
+ expect(csp.value).to match("report-uri http://lambda/result")
214
98
  end
215
99
 
216
- it "uses the pre-configured https protocol" do
217
- csp = ContentSecurityPolicy.new(opts, :ua => "Firefox", :ssl => true)
218
- csp.value.should =~ %r{report-uri https://example.com/csp;}
219
- end
100
+ it "accepts procs for other fields" do
101
+ opts = {
102
+ :default_src => lambda { "http://lambda/result" },
103
+ :enforce => lambda { true },
104
+ :disable_fill_missing => lambda { true }
105
+ }
220
106
 
221
- it "uses the pre-configured http protocol" do
222
- csp = ContentSecurityPolicy.new(opts, :ua => "Firefox", :ssl => false)
223
- csp.value.should =~ %r{report-uri http://example.com/csp;}
107
+ csp = ContentSecurityPolicy.new(opts)
108
+ expect(csp.value).to eq("default-src http://lambda/result; img-src http://lambda/result data:;")
109
+ expect(csp.name).to match("Content-Security-Policy")
224
110
  end
225
111
  end
226
112
  end
@@ -228,111 +114,83 @@ module SecureHeaders
228
114
  describe "#value" do
229
115
  it "raises an exception when default-src is missing" do
230
116
  csp = ContentSecurityPolicy.new({:script_src => 'anything'}, :request => request_for(CHROME))
231
- lambda {
117
+ expect {
232
118
  csp.value
233
- }.should raise_error(ContentSecurityPolicyBuildError, "Couldn't build CSP header :( Expected to find default_src directive value")
119
+ }.to raise_error(RuntimeError)
120
+ end
121
+
122
+ context "CSP level 2 directives" do
123
+ let(:config) { {:default_src => 'self'} }
124
+ ::SecureHeaders::ContentSecurityPolicy::Constants::NON_DEFAULT_SOURCES.each do |non_default_source|
125
+ it "supports all level 2 directives" do
126
+ directive_name = ::SecureHeaders::ContentSecurityPolicy.send(:symbol_to_hyphen_case, non_default_source)
127
+ config.merge!({ non_default_source => "value" })
128
+ csp = ContentSecurityPolicy.new(config, :request => request_for(CHROME))
129
+ expect(csp.value).to match(/#{directive_name} value;/)
130
+ end
131
+ end
234
132
  end
235
133
 
236
134
  context "auto-whitelists data: uris for img-src" do
237
135
  it "sets the value if no img-src specified" do
238
- csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
239
- csp.value.should == "default-src 'self'; img-src data:;"
136
+ csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true}, :request => request_for(CHROME))
137
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
240
138
  end
241
139
 
242
140
  it "appends the value if img-src is specified" do
243
- csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
244
- csp.value.should == "default-src 'self'; img-src 'self' data:;"
141
+ csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true}, :request => request_for(CHROME))
142
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
245
143
  end
246
144
  end
247
145
 
248
146
  it "fills in directives without values with default-src value" do
249
147
  options = default_opts.merge(:disable_fill_missing => false)
250
148
  csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
251
- 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;"
252
- csp.value.should == value
149
+ 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;"
150
+ expect(csp.value).to eq(value)
253
151
  end
254
152
 
255
- it "sends the chrome csp header if an unknown browser is supplied" do
153
+ it "sends the standard csp header if an unknown browser is supplied" do
256
154
  csp = ContentSecurityPolicy.new(default_opts, :request => request_for(IE))
257
- csp.value.should match "default-src"
155
+ expect(csp.value).to match "default-src"
258
156
  end
259
157
 
260
158
  context "Firefox" do
261
159
  it "builds a csp header for firefox" do
262
160
  csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX))
263
- csp.value.should == "allow https://*; options inline-script eval-script; img-src data:; script-src https://* data:; style-src https://* about:; report-uri /csp_report;"
264
- end
265
-
266
- it "does not append chrome-extension to directives" do
267
- csp = ContentSecurityPolicy.new(default_opts.merge(:disable_chrome_extension => false), :request => request_for(FIREFOX))
268
- csp.value.should_not match "chrome-extension:"
269
- end
270
-
271
- it "copies connect-src values to xhr_src values" do
272
- opts = {
273
- :default_src => 'http://twitter.com',
274
- :connect_src => 'self http://*.localhost.com:*',
275
- :disable_chrome_extension => true,
276
- :disable_fill_missing => true
277
- }
278
- csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
279
- csp.value.should =~ /xhr-src 'self' http:/
280
- end
281
-
282
- context "Firefox >= 23" do
283
- it "builds a csp header for firefox" do
284
- csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23))
285
- 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;"
286
- end
161
+ 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;")
287
162
  end
288
163
  end
289
164
 
290
165
  context "Chrome" do
291
166
  it "builds a csp header for chrome" do
292
167
  csp = ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME))
293
- 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;"
168
+ 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;")
294
169
  end
170
+ end
295
171
 
296
- it "ignores :forward_endpoint settings" do
297
- csp = ContentSecurityPolicy.new(@options_with_forwarding, :request => request_for(CHROME))
298
- csp.value.should =~ /report-uri #{@options_with_forwarding[:report_uri]};/
172
+ context "when using a nonce" do
173
+ it "adds a nonce and unsafe-inline to the script-src value" do
174
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME))
175
+ expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
299
176
  end
300
177
 
301
- it "whitelists chrome_extensions by default" do
302
- opts = {
303
- :default_src => 'https://*',
304
- :report_uri => '/csp_report',
305
- :script_src => 'inline eval https://* data:',
306
- :style_src => "inline https://* chrome-extension: about:"
307
- }
308
-
309
- csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
310
-
311
- # ignore the report-uri directive
312
- csp.value.split(';')[0...-1].each{|directive| directive.should =~ /chrome-extension:/}
178
+ it "adds a nonce and unsafe-inline to the style-src value" do
179
+ header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME))
180
+ expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
313
181
  end
314
- end
315
-
316
- context "when supplying a experimental values" do
317
- let(:options) {{
318
- :disable_chrome_extension => true,
319
- :disable_fill_missing => true,
320
- :default_src => 'self',
321
- :script_src => 'https://*',
322
- :experimental => {
323
- :script_src => 'self'
324
- }
325
- }}
326
182
 
327
- let(:header) {}
328
- it "returns the original value" do
329
- header = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
330
- header.value.should == "default-src 'self'; img-src data:; script-src https://*;"
183
+ it "adds an identical nonce to the style and script-src directives" do
184
+ header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce", :script_src => "self nonce"), :request => request_for(CHROME))
185
+ nonce = header.nonce
186
+ value = header.value
187
+ expect(value).to include("style-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
188
+ expect(value).to include("script-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
331
189
  end
332
190
 
333
- it "it returns the experimental value if requested" do
334
- header = ContentSecurityPolicy.new(options, {:request => request_for(CHROME), :experimental => true})
335
- header.value.should_not =~ /https/
191
+ it "does not add 'unsafe-inline' twice" do
192
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce inline"), :request => request_for(CHROME))
193
+ expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline';")
336
194
  end
337
195
  end
338
196
 
@@ -340,98 +198,68 @@ module SecureHeaders
340
198
  let(:options) {
341
199
  default_opts.merge({
342
200
  :http_additions => {
343
- :frame_src => "http://*",
344
- :img_src => "http://*"
201
+ :frame_src => "http:",
202
+ :img_src => "http:"
345
203
  }
346
204
  })
347
205
  }
348
206
 
349
207
  it "adds directive values for headers on http" do
350
208
  csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
351
- 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;"
209
+ 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;")
352
210
  end
353
211
 
354
212
  it "does not add the directive values if requesting https" do
355
213
  csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME, '/', :ssl => true))
356
- csp.value.should_not =~ /http:/
214
+ expect(csp.value).not_to match(/http:/)
357
215
  end
358
216
 
359
217
  it "does not add the directive values if requesting https" do
360
218
  csp = ContentSecurityPolicy.new(options, :ua => "Chrome", :ssl => true)
361
- csp.value.should_not =~ /http:/
219
+ expect(csp.value).not_to match(/http:/)
362
220
  end
221
+ end
363
222
 
364
- context "when supplying an experimental block" do
365
- # this simulates the situation where we are enforcing that scripts
366
- # only come from http[s]? depending if we're on ssl or not. The
367
- # report only tag will allow scripts from self over ssl, and
368
- # from a secure CDN over non-ssl
369
- let(:options) {{
370
- :disable_chrome_extension => true,
371
- :disable_fill_missing => true,
372
- :default_src => 'self',
373
- :script_src => 'https://*',
374
- :http_additions => {
375
- :script_src => 'http://*'
376
- },
377
- :experimental => {
378
- :script_src => 'self',
379
- :http_additions => {
380
- :script_src => 'https://mycdn.example.com'
381
- }
382
- }
383
- }}
384
- # for comparison purposes, if not using the experimental header this would produce
385
- # "allow 'self'; script-src https://*" for https requests
386
- # and
387
- # "allow 'self; script-src https://* http://*" for http requests
388
-
389
- it "uses the value in the experimental block over SSL" do
390
- csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX, '/', :ssl => true))
391
- csp.value.should == "allow 'self'; img-src data:; script-src 'self';"
392
- end
393
-
394
- it "detects the :ssl => true option" do
395
- csp = ContentSecurityPolicy.new(options, :experimental => true, :ua => FIREFOX, :ssl => true)
396
- csp.value.should == "allow 'self'; img-src data:; script-src 'self';"
397
- end
398
-
399
- it "merges the values from experimental/http_additions when not over SSL" do
400
- csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX))
401
- csp.value.should == "allow 'self'; img-src data:; script-src 'self' https://mycdn.example.com;"
223
+ describe "class methods" do
224
+ let(:ua) { CHROME }
225
+ let(:env) do
226
+ double.tap do |env|
227
+ allow(env).to receive(:[]).with('HTTP_USER_AGENT').and_return(ua)
402
228
  end
403
229
  end
404
- end
405
-
406
- context "when supplying a script nonce callback" do
407
- let(:options) {
408
- default_opts.merge({
409
- :script_nonce => "random",
410
- })
411
- }
412
-
413
- it "uses the value in the X-Webkit-CSP" do
414
- csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
415
- csp.value.should match "script-nonce random;"
230
+ let(:request) do
231
+ double(
232
+ :ssl? => true,
233
+ :url => 'https://example.com',
234
+ :env => env
235
+ )
416
236
  end
417
237
 
418
- it "uses the value in the X-Content-Security-Policy" do
419
- csp = ContentSecurityPolicy.new(options, :request => request_for(FIREFOX))
420
- csp.value.should match "script-nonce random;"
421
- end
238
+ describe ".add_to_env" do
239
+ let(:controller) { double }
240
+ let(:config) { {:default_src => 'self'} }
241
+ let(:options) { {:controller => controller} }
422
242
 
423
- it "runs a dynamic nonce generator" do
424
- options[:script_nonce] = lambda { 'something' }
425
- csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
426
- csp.value.should match "script-nonce something;"
243
+ it "adds metadata to env" do
244
+ metadata = {
245
+ :config => config,
246
+ :options => options
247
+ }
248
+ expect(ContentSecurityPolicy).to receive(:options_from_request).and_return(options)
249
+ expect(env).to receive(:[]=).with(ContentSecurityPolicy::ENV_KEY, metadata)
250
+ ContentSecurityPolicy.add_to_env(request, controller, config)
251
+ end
427
252
  end
428
253
 
429
- it "runs against the given controller context" do
430
- fake_params = {}
431
- options[:script_nonce] = lambda { params[:script_nonce] = 'something' }
432
- csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME), :controller => double(:params => fake_params))
433
- csp.value.should match "script-nonce something;"
434
- fake_params.should == {:script_nonce => 'something'}
254
+ describe ".options_from_request" do
255
+ it "extracts options from request" do
256
+ options = ContentSecurityPolicy.options_from_request(request)
257
+ expect(options).to eql({
258
+ :ua => ua,
259
+ :ssl => true,
260
+ :request_uri => 'https://example.com'
261
+ })
262
+ end
435
263
  end
436
264
  end
437
265
  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