secure_headers 3.0.3 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8ce791545252f765e19db9b42b88d0dec3c7f14a
4
- data.tar.gz: 5a677c30789119f527ea04f940de663a98344339
3
+ metadata.gz: 7353ab86b76cba9baabc265f6321990163927fa0
4
+ data.tar.gz: 8081e9d2a284a26191899317d1947cde24d80d9f
5
5
  SHA512:
6
- metadata.gz: c861e7c69de1e69d4a53090a33b9c2f699900ee728d55694f0e7df7c32f430bebc65a9304cf0a8536c4663ed97c678340065a17a1ce6862c000b1c7984989d29
7
- data.tar.gz: cc33e4d244bbd8bdc46960594d5d6de0bae38bcac6b282f5be9a8a32807d6ad2eb737e28553335cc74223a8ede8c6d592207bd5ee168fb05bf91c04118c8ec69
6
+ metadata.gz: 37fd3a8aca07d1ce46bd7cdaf89ddb057d41b94f2f41625287d9a3118d19300708fd33f9d5f029c341a9c80295848101f01558d3dfd370a7bba34c8862e710d6
7
+ data.tar.gz: 0d810d24a48f53a45bec5621291475059e521d0d4f640bf1430ad6c86ecf020fea8dd8eb220e600b71c32539d7cfbc1f87567066477deebdf660d8a0161e2f8a
data/.gitignore CHANGED
@@ -6,17 +6,8 @@
6
6
  .yardoc
7
7
  *.log
8
8
  Gemfile.lock
9
- InstalledFiles
10
9
  _yardoc
11
10
  coverage
12
- doc/
13
- lib/bundler/man
14
11
  pkg
15
12
  rdoc
16
13
  spec/reports
17
- test/tmp
18
- test/version_tmp
19
- *tmp
20
- *.sqlite3
21
- fixtures/rails_3_2_12_no_init/log
22
- fixtures/rails_3_2_12/log
data/.travis.yml CHANGED
@@ -1,13 +1,21 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - "2.2"
5
- - "2.1"
6
- - "2.0.0"
7
- - "1.9.3"
8
- - "jruby-19mode"
4
+ - ruby-head
5
+ - 2.2
6
+ - 2.1
7
+ - 2.0.0
8
+ - 1.9.3
9
+ - jruby-19mode
10
+ - jruby-head
11
+
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: jruby-head
15
+ - rvm: ruby-head
9
16
 
10
17
  before_install: gem update bundler
18
+ bundler_args: --without guard -j 3
11
19
 
12
20
  sudo: false
13
21
  cache: bundler
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 3.1.0 Adding secure cookie support
2
+
3
+ New feature: marking all cookies as secure. Added by @jmera in https://github.com/twitter/secureheaders/pull/231. In the future, we'll probably add the ability to whitelist individual cookies that should not be marked secure. PRs welcome.
4
+
5
+ Internal refactoring: In https://github.com/twitter/secureheaders/pull/232, we changed the way dynamic CSP is handled internally. The biggest benefit is that highly dynamic policies (which can happen with multiple `append/override` calls per request) are handled better:
6
+
7
+ 1. Only the CSP header cache is busted when using a dynamic policy. All other headers are preserved and don't need to be generated. Dynamic X-Frame-Options changes modify the cache directly.
8
+ 1. Idempotency checks for policy modifications are deferred until the end of the request lifecycle and only happen once, instead of per `append/override` call. The idempotency check itself is fairly expensive itself.
9
+ 1. CSP header string is produced at most once per request.
10
+
1
11
  ## 3.0.3
2
12
 
3
13
  Bug fix for handling policy merges where appending a non-default source value (report-uri, plugin-types, frame-ancestors, base-uri, and form-action) would be combined with the default-src value. Appending a directive that doesn't exist in the current policy combines the new value with `default-src` to mimic the actual behavior of the addition. However, this does not make sense for non-default-src values (a.k.a. "fetch directives") and can lead to unexpected behavior like a `report-uri` value of `*`. Previously, this config:
@@ -14,6 +24,7 @@ When appending:
14
24
  {
15
25
  report_uri => %w(https://report-uri.io/asdf)
16
26
  }
27
+ ```
17
28
 
18
29
  Would result in `default-src *; report-uri *` which doesn't make any sense at all.
19
30
 
data/Gemfile CHANGED
@@ -6,9 +6,12 @@ group :test do
6
6
  gem "tins", "~> 1.6.0" # 1.7 requires ruby 2.0
7
7
  gem "pry-nav"
8
8
  gem "rack"
9
+ gem "rspec"
10
+ gem "coveralls"
11
+ end
12
+
13
+ group :guard do
9
14
  gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
10
- gem "rspec", ">= 3.1"
11
15
  gem "growl"
12
16
  gem "rb-fsevent"
13
- gem "coveralls", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
14
17
  end
data/README.md CHANGED
@@ -29,7 +29,7 @@ All `nil` values will fallback to their default values. `SecureHeaders::OPT_OUT`
29
29
 
30
30
  ```ruby
31
31
  SecureHeaders::Configuration.default do |config|
32
- config.hsts = "max-age=#{20.years.to_i}"
32
+ config.hsts = "max-age=#{20.years.to_i}; includeSubdomains; preload"
33
33
  config.x_frame_options = "DENY"
34
34
  config.x_content_type_options = "nosniff"
35
35
  config.x_xss_protection = "1; mode=block"
@@ -57,13 +57,13 @@ SecureHeaders::Configuration.default do |config|
57
57
  plugin_types: %w(application/x-shockwave-flash),
58
58
  block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
59
59
  upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
60
- report_uri: %w(https://example.com/uri-directive)
60
+ report_uri: %w(https://report-uri.io/example-csp)
61
61
  }
62
62
  config.hpkp = {
63
63
  report_only: false,
64
64
  max_age: 60.days.to_i,
65
65
  include_subdomains: true,
66
- report_uri: "https://example.com/uri-directive",
66
+ report_uri: "https://report-uri.io/example-hpkp",
67
67
  pins: [
68
68
  {sha256: "abc"},
69
69
  {sha256: "123"}
@@ -175,7 +175,7 @@ When manipulating content security policy, there are a few things to consider. T
175
175
 
176
176
  #### Append to the policy with a directive other than `default_src`
177
177
 
178
- The value of `default_src` is joined with the addition. Note the `https:` is carried over from the `default-src` config. If you do not want this, use `override_content_security_policy_directives` instead. To illustrate:
178
+ The value of `default_src` is joined with the addition if the it is a [fetch directive](https://w3c.github.io/webappsec-csp/#directives-fetch). Note the `https:` is carried over from the `default-src` config. If you do not want this, use `override_content_security_policy_directives` instead. To illustrate:
179
179
 
180
180
  ```ruby
181
181
  ::SecureHeaders::Configuration.default do |config|
@@ -255,7 +255,7 @@ config.hpkp = {
255
255
  {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
256
256
  ],
257
257
  report_only: true, # defaults to false (report-only mode)
258
- report_uri: '//example.com/uri-directive',
258
+ report_uri: 'https://report-uri.io/example-hpkp',
259
259
  app_name: 'example',
260
260
  tag_report_uri: true
261
261
  }
@@ -287,43 +287,6 @@ class Donkey < Sinatra::Application
287
287
  end
288
288
  ```
289
289
 
290
- ### Using with Padrino
291
-
292
- You can use SecureHeaders for Padrino applications as well:
293
-
294
- In your `Gemfile`:
295
-
296
- ```ruby
297
- gem "secure_headers", require: 'secure_headers'
298
- ```
299
-
300
- then in your `app.rb` file you can:
301
-
302
- ```ruby
303
- Padrino.use(SecureHeaders::Middleware)
304
- require 'secure_headers/padrino'
305
-
306
- module Web
307
- class App < Padrino::Application
308
- register SecureHeaders::Padrino
309
-
310
- get '/' do
311
- render 'index'
312
- end
313
- end
314
- end
315
- ```
316
-
317
- and in `config/boot.rb`:
318
-
319
- ```ruby
320
- def before_load
321
- SecureHeaders::Configuration.default do |config|
322
- ...
323
- end
324
- end
325
- ```
326
-
327
290
  ## Similar libraries
328
291
 
329
292
  * Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
@@ -334,6 +297,8 @@ end
334
297
  * Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
335
298
  * Go - [secureheader](https://github.com/kr/secureheader)
336
299
  * Elixir [secure_headers](https://github.com/anotherhale/secure_headers)
300
+ * Dropwizard [dropwizard-web-security](https://github.com/palantir/dropwizard-web-security)
301
+ * Ember.js [ember-cli-content-security-policy](https://github.com/rwjblue/ember-cli-content-security-policy/)
337
302
 
338
303
  ## License
339
304
 
@@ -48,14 +48,12 @@ module SecureHeaders
48
48
  # script_src: %w(another-host.com)
49
49
  def override_content_security_policy_directives(request, additions)
50
50
  config = config_for(request)
51
- unless CSP.idempotent_additions?(config.csp, additions)
52
- config = config.dup
53
- if config.csp == OPT_OUT
54
- config.csp = {}
55
- end
56
- config.csp.merge!(additions)
57
- override_secure_headers_request_config(request, config)
51
+ if config.current_csp == OPT_OUT
52
+ config.dynamic_csp = {}
58
53
  end
54
+
55
+ config.dynamic_csp = config.current_csp.merge(additions)
56
+ override_secure_headers_request_config(request, config)
59
57
  end
60
58
 
61
59
  # Public: appends source values to the current configuration. If no value
@@ -66,11 +64,8 @@ module SecureHeaders
66
64
  # script_src: %w(another-host.com)
67
65
  def append_content_security_policy_directives(request, additions)
68
66
  config = config_for(request)
69
- unless CSP.idempotent_additions?(config.csp, additions)
70
- config = config.dup
71
- config.csp = CSP.combine_policies(config.csp, additions)
72
- override_secure_headers_request_config(request, config)
73
- end
67
+ config.dynamic_csp = CSP.combine_policies(config.current_csp, additions)
68
+ override_secure_headers_request_config(request, config)
74
69
  end
75
70
 
76
71
  # Public: override X-Frame-Options settings for this request.
@@ -79,16 +74,16 @@ module SecureHeaders
79
74
  #
80
75
  # Returns the current config
81
76
  def override_x_frame_options(request, value)
82
- default_config = config_for(request).dup
83
- default_config.x_frame_options = value
84
- override_secure_headers_request_config(request, default_config)
77
+ config = config_for(request)
78
+ config.update_x_frame_options(value)
79
+ override_secure_headers_request_config(request, config)
85
80
  end
86
81
 
87
82
  # Public: opts out of setting a given header by creating a temporary config
88
83
  # and setting the given headers config to OPT_OUT.
89
84
  def opt_out_of_header(request, header_key)
90
- config = config_for(request).dup
91
- config.send("#{header_key}=", OPT_OUT)
85
+ config = config_for(request)
86
+ config.opt_out(header_key)
92
87
  override_secure_headers_request_config(request, config)
93
88
  end
94
89
 
@@ -109,14 +104,11 @@ module SecureHeaders
109
104
  # in Rack middleware.
110
105
  def header_hash_for(request)
111
106
  config = config_for(request)
112
-
113
- headers = if cached_headers = config.cached_headers
114
- use_cached_headers(cached_headers, request)
115
- else
116
- build_headers(config, request)
107
+ unless ContentSecurityPolicy.idempotent_additions?(config.csp, config.current_csp)
108
+ config.rebuild_csp_header_cache!(request.user_agent)
117
109
  end
118
110
 
119
- headers
111
+ use_cached_headers(config.cached_headers, request)
120
112
  end
121
113
 
122
114
  # Public: specify which named override will be used for this request.
@@ -149,6 +141,22 @@ module SecureHeaders
149
141
  content_security_policy_nonce(request, CSP::STYLE_SRC)
150
142
  end
151
143
 
144
+ # Public: Retreives the config for a given header type:
145
+ #
146
+ # Checks to see if there is an override for this request, then
147
+ # Checks to see if a named override is used for this request, then
148
+ # Falls back to the global config
149
+ def config_for(request)
150
+ config = request.env[SECURE_HEADERS_CONFIG] ||
151
+ Configuration.get(Configuration::DEFAULT_CONFIG)
152
+
153
+ if config.frozen?
154
+ config.dup
155
+ else
156
+ config
157
+ end
158
+ end
159
+
152
160
  private
153
161
 
154
162
  # Private: gets or creates a nonce for CSP.
@@ -181,64 +189,30 @@ module SecureHeaders
181
189
  end
182
190
  end
183
191
 
184
- # Private: do the heavy lifting of converting a configuration object
185
- # to a hash of headers valid for this request.
186
- #
187
- # Returns a hash of header names / values.
188
- def build_headers(config, request)
189
- header_classes_for(request).each_with_object({}) do |klass, hash|
190
- header_config = if config
191
- config.fetch(klass::CONFIG_KEY)
192
- end
193
-
194
- header_name, value = if klass == CSP
195
- make_header(klass, header_config, request.user_agent)
196
- else
197
- make_header(klass, header_config)
198
- end
199
- hash[header_name] = value if value
200
- end
201
- end
202
-
203
192
  # Private: takes a precomputed hash of headers and returns the Headers
204
193
  # customized for the request.
205
194
  #
206
195
  # Returns a hash of header names / values valid for a given request.
207
- def use_cached_headers(default_headers, request)
196
+ def use_cached_headers(headers, request)
208
197
  header_classes_for(request).each_with_object({}) do |klass, hash|
209
- if default_header = default_headers[klass::CONFIG_KEY]
198
+ if header = headers[klass::CONFIG_KEY]
210
199
  header_name, value = if klass == CSP
211
- default_csp_header_for_ua(default_header, request)
200
+ csp_header_for_ua(header, request)
212
201
  else
213
- default_header
202
+ header
214
203
  end
215
204
  hash[header_name] = value
216
205
  end
217
206
  end
218
207
  end
219
208
 
220
- # Private: Retreives the config for a given header type:
221
- #
222
- # Checks to see if there is an override for this request, then
223
- # Checks to see if a named override is used for this request, then
224
- # Falls back to the global config
225
- def config_for(request)
226
- request.env[SECURE_HEADERS_CONFIG] ||
227
- Configuration.get(Configuration::DEFAULT_CONFIG)
228
- end
229
-
230
209
  # Private: chooses the applicable CSP header for the provided user agent.
231
210
  #
232
211
  # headers - a hash of header_config_key => [header_name, header_value]
233
212
  #
234
213
  # Returns a CSP [header, value] array
235
- def default_csp_header_for_ua(headers, request)
236
- family = UserAgent.parse(request.user_agent).browser
237
- if CSP::VARIATIONS.key?(family)
238
- headers[family]
239
- else
240
- headers[CSP::OTHER]
241
- end
214
+ def csp_header_for_ua(headers, request)
215
+ headers[CSP.ua_to_variation(UserAgent.parse(request.user_agent))]
242
216
  end
243
217
 
244
218
  # Private: optionally build a header with a given configure
@@ -3,6 +3,7 @@ module SecureHeaders
3
3
  DEFAULT_CONFIG = :default
4
4
  NOOP_CONFIGURATION = "secure_headers_noop_config"
5
5
  class NotYetConfiguredError < StandardError; end
6
+ class IllegalPolicyModificationError < StandardError; end
6
7
  class << self
7
8
  # Public: Set the global default configuration.
8
9
  #
@@ -23,12 +24,12 @@ module SecureHeaders
23
24
  # if no value is supplied.
24
25
  #
25
26
  # Returns: the newly created config
26
- def override(name, base = DEFAULT_CONFIG)
27
+ def override(name, base = DEFAULT_CONFIG, &block)
27
28
  unless get(base)
28
29
  raise NotYetConfiguredError, "#{base} policy not yet supplied"
29
30
  end
30
31
  override = @configurations[base].dup
31
- yield(override)
32
+ override.instance_eval &block if block_given?
32
33
  add_configuration(name, override)
33
34
  end
34
35
 
@@ -43,18 +44,6 @@ module SecureHeaders
43
44
  @configurations[name]
44
45
  end
45
46
 
46
- # Public: perform a basic deep dup. The shallow copy provided by dup/clone
47
- # can lead to modifying parent objects.
48
- def deep_copy(config)
49
- config.each_with_object({}) do |(key, value), hash|
50
- hash[key] = if value.is_a?(Array)
51
- value.dup
52
- else
53
- value
54
- end
55
- end
56
- end
57
-
58
47
  private
59
48
 
60
49
  # Private: add a valid configuration to the global set of named configs.
@@ -86,16 +75,39 @@ module SecureHeaders
86
75
 
87
76
  add_configuration(NOOP_CONFIGURATION, noop_config)
88
77
  end
78
+
79
+ # Public: perform a basic deep dup. The shallow copy provided by dup/clone
80
+ # can lead to modifying parent objects.
81
+ def deep_copy(config)
82
+ config.each_with_object({}) do |(key, value), hash|
83
+ hash[key] = if value.is_a?(Array)
84
+ value.dup
85
+ else
86
+ value
87
+ end
88
+ end
89
+ end
90
+
91
+ # Private: convenience method purely DRY things up. The value may not be a
92
+ # hash (e.g. OPT_OUT, nil)
93
+ def deep_copy_if_hash(value)
94
+ if value.is_a?(Hash)
95
+ deep_copy(value)
96
+ else
97
+ value
98
+ end
99
+ end
89
100
  end
90
101
 
91
- attr_accessor :hsts, :x_frame_options, :x_content_type_options,
92
- :x_xss_protection, :csp, :x_download_options, :x_permitted_cross_domain_policies,
93
- :hpkp
94
- attr_reader :cached_headers
102
+ attr_writer :hsts, :x_frame_options, :x_content_type_options,
103
+ :x_xss_protection, :x_download_options, :x_permitted_cross_domain_policies,
104
+ :hpkp, :dynamic_csp, :secure_cookies
105
+
106
+ attr_reader :cached_headers, :csp, :dynamic_csp, :secure_cookies
95
107
 
96
108
  def initialize(&block)
97
109
  self.hpkp = OPT_OUT
98
- self.csp = self.class.deep_copy(CSP::DEFAULT_CONFIG)
110
+ self.csp = self.class.send(:deep_copy, CSP::DEFAULT_CONFIG)
99
111
  instance_eval &block if block_given?
100
112
  end
101
113
 
@@ -104,33 +116,37 @@ module SecureHeaders
104
116
  # Returns a deep-dup'd copy of this configuration.
105
117
  def dup
106
118
  copy = self.class.new
107
- copy.hsts = hsts
108
- copy.x_frame_options = x_frame_options
109
- copy.x_content_type_options = x_content_type_options
110
- copy.x_xss_protection = x_xss_protection
111
- copy.x_download_options = x_download_options
112
- copy.x_permitted_cross_domain_policies = x_permitted_cross_domain_policies
113
- copy.csp = if csp.is_a?(Hash)
114
- self.class.deep_copy(csp)
115
- else
116
- csp
119
+ copy.secure_cookies = @secure_cookies
120
+ copy.csp = self.class.send(:deep_copy_if_hash, @csp)
121
+ copy.dynamic_csp = self.class.send(:deep_copy_if_hash, @dynamic_csp)
122
+ copy.cached_headers = self.class.send(:deep_copy_if_hash, @cached_headers)
123
+ copy
124
+ end
125
+
126
+ def opt_out(header)
127
+ send("#{header}=", OPT_OUT)
128
+ if header == CSP::CONFIG_KEY
129
+ dynamic_csp = OPT_OUT
117
130
  end
131
+ self.cached_headers.delete(header)
132
+ end
133
+
134
+ def update_x_frame_options(value)
135
+ self.cached_headers[XFrameOptions::CONFIG_KEY] = XFrameOptions.make_header(value)
136
+ end
118
137
 
119
- copy.hpkp = if hpkp.is_a?(Hash)
120
- self.class.deep_copy(hpkp)
121
- else
122
- hpkp
138
+ # Public: generated cached headers for a specific user agent.
139
+ def rebuild_csp_header_cache!(user_agent)
140
+ self.cached_headers[CSP::CONFIG_KEY] = {}
141
+ unless current_csp == OPT_OUT
142
+ user_agent = UserAgent.parse(user_agent)
143
+ variation = CSP.ua_to_variation(user_agent)
144
+ self.cached_headers[CSP::CONFIG_KEY][variation] = CSP.make_header(current_csp, user_agent)
123
145
  end
124
- copy
125
146
  end
126
147
 
127
- # Public: Retrieve a config based on the CONFIG_KEY for a class
128
- #
129
- # Returns the value if available, and returns a dup of any hash values.
130
- def fetch(key)
131
- config = send(key)
132
- config = self.class.deep_copy(config) if config.is_a?(Hash)
133
- config
148
+ def current_csp
149
+ @dynamic_csp || @csp
134
150
  end
135
151
 
136
152
  # Public: validates all configurations values.
@@ -139,16 +155,32 @@ module SecureHeaders
139
155
  #
140
156
  # Returns nothing
141
157
  def validate_config!
142
- StrictTransportSecurity.validate_config!(hsts)
143
- ContentSecurityPolicy.validate_config!(csp)
144
- XFrameOptions.validate_config!(x_frame_options)
145
- XContentTypeOptions.validate_config!(x_content_type_options)
146
- XXssProtection.validate_config!(x_xss_protection)
147
- XDownloadOptions.validate_config!(x_download_options)
148
- XPermittedCrossDomainPolicies.validate_config!(x_permitted_cross_domain_policies)
149
- PublicKeyPins.validate_config!(hpkp)
158
+ StrictTransportSecurity.validate_config!(@hsts)
159
+ ContentSecurityPolicy.validate_config!(@csp)
160
+ XFrameOptions.validate_config!(@x_frame_options)
161
+ XContentTypeOptions.validate_config!(@x_content_type_options)
162
+ XXssProtection.validate_config!(@x_xss_protection)
163
+ XDownloadOptions.validate_config!(@x_download_options)
164
+ XPermittedCrossDomainPolicies.validate_config!(@x_permitted_cross_domain_policies)
165
+ PublicKeyPins.validate_config!(@hpkp)
150
166
  end
151
167
 
168
+ protected
169
+
170
+ def csp=(new_csp)
171
+ if self.dynamic_csp
172
+ raise IllegalPolicyModificationError, "You are attempting to modify CSP settings directly. Use dynamic_csp= isntead."
173
+ end
174
+
175
+ @csp = new_csp
176
+ end
177
+
178
+ def cached_headers=(headers)
179
+ @cached_headers = headers
180
+ end
181
+
182
+ private
183
+
152
184
  # Public: Precompute the header names and values for this configuraiton.
153
185
  # Ensures that headers generated at configure time, not on demand.
154
186
  #
@@ -156,7 +188,7 @@ module SecureHeaders
156
188
  def cache_headers!
157
189
  # generate defaults for the "easy" headers
158
190
  headers = (ALL_HEADERS_BESIDES_CSP).each_with_object({}) do |klass, hash|
159
- config = fetch(klass::CONFIG_KEY)
191
+ config = instance_variable_get("@#{klass::CONFIG_KEY}")
160
192
  unless config == OPT_OUT
161
193
  hash[klass::CONFIG_KEY] = klass.make_header(config).freeze
162
194
  end
@@ -165,7 +197,7 @@ module SecureHeaders
165
197
  generate_csp_headers(headers)
166
198
 
167
199
  headers.freeze
168
- @cached_headers = headers
200
+ self.cached_headers = headers
169
201
  end
170
202
 
171
203
  # Private: adds CSP headers for each variation of CSP support.
@@ -175,11 +207,10 @@ module SecureHeaders
175
207
  #
176
208
  # Returns nothing
177
209
  def generate_csp_headers(headers)
178
- unless csp == OPT_OUT
210
+ unless @csp == OPT_OUT
179
211
  headers[CSP::CONFIG_KEY] = {}
180
-
212
+ csp_config = self.current_csp
181
213
  CSP::VARIATIONS.each do |name, _|
182
- csp_config = fetch(CSP::CONFIG_KEY)
183
214
  csp = CSP.make_header(csp_config, UserAgent.parse(name))
184
215
  headers[CSP::CONFIG_KEY][name] = csp.freeze
185
216
  end