secure_headers 3.0.3 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.

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