secure_headers 1.1.1 → 1.2.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.

Potentially problematic release.


This version of secure_headers might be problematic. Click here for more details.

checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDFmNDM2NjIwYzU4MjcyYWU0ODkwMjkxMjM0MDMwYTAyNjY3YWM2ZA==
5
+ data.tar.gz: !binary |-
6
+ N2VjN2M3MGMyY2Y5ODliOTVmZTIxZDgwOTdjYTM1NTg3MDZiMjdjNw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTRiN2Y5NDFhYTE3ZmM2MjE4MThmMDQ3OTZmOWVhMWM5OTgxOThlNzFiOGNm
10
+ MzQ2ZmFiMjUyZjdmNWU4MWQwZTY3Yjc4YmM4NzVjMWNhZWE0YzI3YjlmMjcy
11
+ MDk3YmNiZmI3NGNmMzFmMDkxNjEyOTFkMmE5YjNlNjJlMDBkYWY=
12
+ data.tar.gz: !binary |-
13
+ NzRhM2Y5NTk0OTZhMmRlYjk0ZThkYTM3ZTQxYzc3Mjk3ZTY2MjQ4MjUwN2Rm
14
+ YzUwMGY4ZTdlY2Q2M2M4NDgyODQ1MTc2ZWM3ZmEyOWU5NWIwNWExMjBlMDBi
15
+ YWNlYjllMmZmN2M4MTU0NWE3NjIwOGNjMmQ5ODBlYzE4NzM1Njk=
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ secureheaders
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p484
data/.travis.yml CHANGED
@@ -1,4 +1,6 @@
1
1
  rvm:
2
+ - "2.1.0"
3
+ - "2.0.0"
2
4
  - "1.9.3"
3
5
  - "1.8.7"
4
6
  # - jruby-19mode
data/Gemfile CHANGED
@@ -8,9 +8,7 @@ group :test do
8
8
  gem 'jdbc-sqlite3', :platform => :jruby
9
9
  gem 'rspec-rails'
10
10
  gem 'spork'
11
- gem 'pry'
12
11
  gem 'rspec'
13
- gem 'guard-spork', :platform => :ruby_19
14
12
  gem 'guard-rspec', :platform => :ruby_19
15
13
  gem 'growl'
16
14
  gem 'rb-fsevent'
data/Guardfile CHANGED
@@ -1,8 +1,4 @@
1
- guard 'spork', :aggressive_kill => false do
2
- watch('spec/spec_helper.rb') { :rspec }
3
- end
4
-
5
- guard 'rspec', :cli => "--color --drb --debug", :keep_failed => true, :all_after_pass => true, :focus_on_failed => true do
1
+ guard 'rspec' do
6
2
  watch(%r{^spec/.+_spec\.rb$})
7
3
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
8
4
  watch(%r{^app/controllers/(.+)\.rb$}) { |m| "spec/controllers/#{m[1]}_spec.rb" }
data/HISTORY.md CHANGED
@@ -1,3 +1,19 @@
1
+ 1.2.0
2
+ ======
3
+
4
+ Add support for procs (anything that respond_to?(:call)) as config values.
5
+
6
+ csp = {
7
+ :default_src => 'self',
8
+ :report_uri => lambda {
9
+ if FeatureToggle.available?(:new_csp_endpoint)
10
+ '//example.com/new_csp'
11
+ else
12
+ '//example.com/old_csp'
13
+ end
14
+ }
15
+ }
16
+
1
17
  1.1.1
2
18
  ======
3
19
 
data/README.md CHANGED
@@ -85,9 +85,10 @@ Or simply add it to application controller
85
85
 
86
86
  ```ruby
87
87
  ensure_security_headers(
88
- :hsts => {:include_subdomains, :x_frame_options => false},
88
+ :hsts => {:include_subdomains => true, :max_age => 20.years.to_i},
89
89
  :x_frame_options => 'DENY',
90
- :csp => false)
90
+ :csp => false
91
+ )
91
92
  ```
92
93
 
93
94
  ## Options for ensure\_security\_headers
@@ -117,6 +118,9 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
117
118
 
118
119
  ```ruby
119
120
  :csp => {
121
+
122
+ # All values can be a String of space-delimited values, an Array of Strings, or procs.
123
+
120
124
  :enforce => false, # sets header to report-only, by default
121
125
  # default_src is required!
122
126
  :default_src => nil, # sets the default-src/allow+options directives
@@ -162,13 +166,6 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
162
166
  :img_src => 'http://mycdn.example.com'
163
167
  }
164
168
  }
165
-
166
- # script-nonce is an experimental feature of CSP 1.1 available in Chrome. It allows
167
- # you to whitelist inline script blocks. For more information, see
168
- # https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#script-nonce
169
- :script_nonce => lambda { @script_nonce = SecureRandom.hex }
170
- # which can be used to whitelist a script block:
171
- # script_tag :nonce = @script_nonce { inline_script_call() }
172
169
  }
173
170
  ```
174
171
 
@@ -196,17 +193,23 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
196
193
  :csp => {
197
194
  :default_src => 'self',
198
195
  :img_src => '*',
196
+ :connect_src => 'none'
199
197
  :object_src => ['media1.com', 'media2.com', '*.cdn.com'],
200
- # alternatively (NOT csv) :object_src => 'media1.com media2.com *.cdn.com'
201
- :script_src => 'trustedscripts.example.com'
198
+ # alternatively :object_src => 'media1.com media2.com *.cdn.com'
199
+ :script_src => 'trustedscripts.example.com',
200
+ :report_uri => lambda {
201
+ if FeatureToggle.available?(:new_csp_endpoint)
202
+ '//example.com/new_csp'
203
+ else
204
+ '//example.com/old_csp'
205
+ end
206
+ }
202
207
  }
203
- "default-src 'self'; img-src *; object-src media1.com media2.com *.cdn.com; script-src trustedscripts.example.com;"
208
+ "default-src 'self'; connect-src 'none'; img-src *; object-src media1.com media2.com *.cdn.com; script-src trustedscripts.example.com; report-uri [one of the two values in the example]"
204
209
  ```
205
210
 
206
211
  ## Note on Firefox handling of CSP
207
212
 
208
- Currently, Firefox does not support the w3c draft standard. So there are a few steps taken to make the two interchangeable.
209
-
210
213
  * CSP reports will not POST cross\-origin. This sets up an internal endpoint in the application that will forward the request. Set the `forward_endpoint` value in the CSP section if you need to post cross origin for firefox. The internal endpoint that receives the initial request will forward the request to `forward_endpoint`
211
214
 
212
215
  ### Adding the Firefox report forwarding endpoint
@@ -304,7 +307,7 @@ end
304
307
  * Node.js (express) [helmet](https://github.com/evilpacket/helmet) and [hood](https://github.com/seanmonstar/hood)
305
308
  * J2EE Servlet >= 3.0 [highlines](https://github.com/sourceclear/headlines)
306
309
  * ASP.NET - [NWebsec](http://nwebsec.codeplex.com/)
307
- * Python - [django-csp](https://github.com/mozilla/django-csp/tree/master/csp) + [commonware](https://github.com/jsocol/commonware/tree/master/commonware/request)
310
+ * Python - [django-csp](https://github.com/mozilla/django-csp/) + [commonware](https://github.com/jsocol/commonware/)
308
311
  * Go - [secureheader](https://github.com/kr/secureheader)
309
312
 
310
313
  ## Authors
@@ -1,44 +1,39 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe OtherThingsController do
3
+ describe OtherThingsController, :type => :controller do
4
4
  describe "headers" do
5
- before(:each) do
6
- # Chrome
7
- request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5'
8
- end
9
-
10
5
  it "sets the X-XSS-Protection header" do
11
6
  get :index
12
- response.headers['X-XSS-Protection'].should == '1; mode=block'
7
+ expect(response.headers['X-XSS-Protection']).to eq('1; mode=block')
13
8
  end
14
9
 
15
10
  it "sets the X-Frame-Options header" do
16
11
  get :index
17
- response.headers['X-Frame-Options'].should == 'SAMEORIGIN'
12
+ expect(response.headers['X-Frame-Options']).to eq('SAMEORIGIN')
18
13
  end
19
14
 
20
15
  it "sets the X-WebKit-CSP header" do
21
16
  get :index
22
- response.headers['Content-Security-Policy-Report-Only'].should == "default-src 'self'; img-src data:; report-uri somewhere;"
17
+ expect(response.headers['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; img-src data:; report-uri somewhere;")
23
18
  end
24
19
 
25
20
  #mock ssl
26
21
  it "sets the Strict-Transport-Security header" do
27
22
  request.env['HTTPS'] = 'on'
28
23
  get :index
29
- response.headers['Strict-Transport-Security'].should == "max-age=315576000"
24
+ expect(response.headers['Strict-Transport-Security']).to eq("max-age=315576000")
30
25
  end
31
26
 
32
27
  it "sets the X-Content-Type-Options header" do
33
28
  get :index
34
- response.headers['X-Content-Type-Options'].should == "nosniff"
29
+ expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
35
30
  end
36
31
 
37
32
  context "using IE" do
38
33
  it "sets the X-Content-Type-Options header" do
39
34
  request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
40
35
  get :index
41
- response.headers['X-Content-Type-Options'].should == "nosniff"
36
+ expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
42
37
  end
43
38
  end
44
39
  end
@@ -4,45 +4,40 @@ require 'spec_helper'
4
4
  # all values are defaulted because no initializer is configured, and the values in app controller
5
5
  # only provide csp => false
6
6
 
7
- describe ThingsController do
7
+ describe ThingsController, :type => :controller do
8
8
  describe "headers" do
9
- before(:each) do
10
- # Chrome
11
- request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5'
12
- end
13
-
14
9
  it "sets the X-XSS-Protection header" do
15
10
  get :index
16
- response.headers['X-XSS-Protection'].should == '1; mode=block'
11
+ expect(response.headers['X-XSS-Protection']).to eq('1; mode=block')
17
12
  end
18
13
 
19
14
  it "sets the X-Frame-Options header" do
20
15
  get :index
21
- response.headers['X-Frame-Options'].should == 'SAMEORIGIN'
16
+ expect(response.headers['X-Frame-Options']).to eq('SAMEORIGIN')
22
17
  end
23
18
 
24
19
  it "sets the X-WebKit-CSP header" do
25
20
  get :index
26
- response.headers['Content-Security-Policy-Report-Only'].should == nil
21
+ expect(response.headers['Content-Security-Policy-Report-Only']).to eq(nil)
27
22
  end
28
23
 
29
24
  #mock ssl
30
25
  it "sets the Strict-Transport-Security header" do
31
26
  request.env['HTTPS'] = 'on'
32
27
  get :index
33
- response.headers['Strict-Transport-Security'].should == "max-age=315576000"
28
+ expect(response.headers['Strict-Transport-Security']).to eq("max-age=315576000")
34
29
  end
35
30
 
36
31
  it "sets the X-Content-Type-Options header" do
37
32
  get :index
38
- response.headers['X-Content-Type-Options'].should == "nosniff"
33
+ expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
39
34
  end
40
35
 
41
36
  context "using IE" do
42
37
  it "sets the X-Content-Type-Options header" do
43
38
  request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
44
39
  get :index
45
- response.headers['X-Content-Type-Options'].should == "nosniff"
40
+ expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
46
41
  end
47
42
  end
48
43
  end
@@ -11,7 +11,6 @@ require 'spork'
11
11
  ENV["RAILS_ENV"] ||= 'test'
12
12
  require File.expand_path("../../config/environment", __FILE__)
13
13
  require 'rspec/rails'
14
- require 'rspec/autorun'
15
14
  # end
16
15
 
17
16
  Spork.each_run do
@@ -1,44 +1,39 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe OtherThingsController do
3
+ describe OtherThingsController, :type => :controller do
4
4
  describe "headers" do
5
- before(:each) do
6
- # Chrome
7
- request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5'
8
- end
9
-
10
5
  it "sets the X-XSS-Protection header" do
11
6
  get :index
12
- response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
7
+ expect(response.headers['X-XSS-Protection']).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
13
8
  end
14
9
 
15
10
  it "sets the X-Frame-Options header" do
16
11
  get :index
17
- response.headers['X-Frame-Options'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
12
+ expect(response.headers['X-Frame-Options']).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
18
13
  end
19
14
 
20
15
  it "sets the X-WebKit-CSP header" do
21
16
  get :index
22
- response.headers['Content-Security-Policy-Report-Only'].should == "default-src 'self'; img-src data:;"
17
+ expect(response.headers['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; img-src data:;")
23
18
  end
24
19
 
25
20
  #mock ssl
26
21
  it "sets the Strict-Transport-Security header" do
27
22
  request.env['HTTPS'] = 'on'
28
23
  get :index
29
- response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
24
+ expect(response.headers['Strict-Transport-Security']).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
30
25
  end
31
26
 
32
27
  it "sets the X-Content-Type-Options header" do
33
28
  get :index
34
- response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
29
+ expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
35
30
  end
36
31
 
37
32
  context "using IE" do
38
33
  it "sets the X-Content-Type-Options header" do
39
34
  request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
40
35
  get :index
41
- response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
36
+ expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
42
37
  end
43
38
  end
44
39
  end
@@ -4,45 +4,40 @@ require 'spec_helper'
4
4
  # all values are defaulted because no initializer is configured, and the values in app controller
5
5
  # only provide csp => false
6
6
 
7
- describe ThingsController do
7
+ describe ThingsController, :type => :controller do
8
8
  describe "headers" do
9
- before(:each) do
10
- # Chrome
11
- request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5'
12
- end
13
-
14
9
  it "sets the X-XSS-Protection header" do
15
10
  get :index
16
- response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
11
+ expect(response.headers['X-XSS-Protection']).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
17
12
  end
18
13
 
19
14
  it "sets the X-Frame-Options header" do
20
15
  get :index
21
- response.headers['X-Frame-Options'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
16
+ expect(response.headers['X-Frame-Options']).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
22
17
  end
23
18
 
24
19
  it "sets the X-WebKit-CSP header" do
25
20
  get :index
26
- response.headers['Content-Security-Policy-Report-Only'].should == nil
21
+ expect(response.headers['Content-Security-Policy-Report-Only']).to eq(nil)
27
22
  end
28
23
 
29
24
  #mock ssl
30
25
  it "sets the Strict-Transport-Security header" do
31
26
  request.env['HTTPS'] = 'on'
32
27
  get :index
33
- response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
28
+ expect(response.headers['Strict-Transport-Security']).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
34
29
  end
35
30
 
36
31
  it "sets the X-Content-Type-Options header" do
37
32
  get :index
38
- response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
33
+ expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
39
34
  end
40
35
 
41
36
  context "using IE" do
42
37
  it "sets the X-Content-Type-Options header" do
43
38
  request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
44
39
  get :index
45
- response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
40
+ expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
46
41
  end
47
42
  end
48
43
  end
@@ -11,7 +11,6 @@ Spork.prefork do
11
11
  ENV["RAILS_ENV"] ||= 'test'
12
12
  require File.expand_path("../../config/environment", __FILE__)
13
13
  require 'rspec/rails'
14
- require 'rspec/autorun'
15
14
  # require 'ruby-debug'
16
15
  end
17
16
 
@@ -58,9 +58,6 @@ module SecureHeaders
58
58
  self.send("#{meta}=", @config.delete(meta))
59
59
  end
60
60
 
61
- @report_uri = @config.delete(:report_uri)
62
- @script_nonce = @config.delete(:script_nonce)
63
-
64
61
  normalize_csp_options
65
62
  normalize_reporting_endpoint
66
63
  fill_directives unless disable_fill_missing?
@@ -92,8 +89,7 @@ module SecureHeaders
92
89
  # ensure default-src is first
93
90
  build_directive(:default_src),
94
91
  generic_directives(@config),
95
- report_uri_directive,
96
- script_nonce_directive,
92
+ report_uri_directive
97
93
  ].join
98
94
 
99
95
  #store the value for next time
@@ -123,12 +119,23 @@ module SecureHeaders
123
119
  end
124
120
 
125
121
  def normalize_csp_options
126
- @config.each do |k,v|
127
- @config[k] = v.split if v.is_a? String
128
- @config[k] = @config[k].map do |val|
122
+ @config = @config.inject({}) do |hash, (k, v)|
123
+ config_val = if v.respond_to?(:call)
124
+ v.call
125
+ else
126
+ v
127
+ end
128
+
129
+ config_val = config_val.split if config_val.is_a? String
130
+ config_val = config_val.map do |val|
129
131
  translate_dir_value(val)
130
132
  end
133
+
134
+ hash[k] = config_val
135
+ hash
131
136
  end
137
+
138
+ @report_uri = @config.delete(:report_uri).join(" ") if @config[:report_uri]
132
139
  end
133
140
 
134
141
  # translates 'inline','self', 'none' and 'eval' to their respective impl-specific values.
@@ -180,18 +187,6 @@ module SecureHeaders
180
187
  "report-uri #{@report_uri};"
181
188
  end
182
189
 
183
- def script_nonce_directive
184
- return '' if @script_nonce.nil?
185
- nonce_value = if @script_nonce.is_a?(String)
186
- @script_nonce
187
- elsif @controller
188
- @controller.instance_exec(&@script_nonce)
189
- else
190
- @script_nonce.call
191
- end
192
- "script-nonce #{nonce_value};"
193
- end
194
-
195
190
  def generic_directives(config)
196
191
  header_value = ''
197
192
  if config[:img_src]
@@ -1,3 +1,3 @@
1
1
  module SecureHeaders
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end