secure_headers 6.3.4 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3953b8d0c4ce0a01012d4d6475ad94c60ce4b06b9d93994ef14cc2474ced6177
4
- data.tar.gz: 3aa96804cacf26d4a275b19e870755313a3afd055b598014749d0338e4f0f4af
3
+ metadata.gz: e5372953d5180d45526a777c1b66e7e86a8ccfca08f6b7b613549d8ba1509a6e
4
+ data.tar.gz: 0d1cc3b012df7b1cfd549bcf09063a390945b05c03009acda31017f859ff7d2d
5
5
  SHA512:
6
- metadata.gz: b04fd60ff28519273f29d335b81b44ebfe938d4e97d82d31b69d8596dc84e38c812573248e7b70bb142fecebab357c7f34f9f10934edaf354e3174915220580f
7
- data.tar.gz: 10748a3ff12365fffe42828fe324e0bfbb3f146635b095a9e8a13b5a57b5bdfe17a5463df3b5009d8591dbc88494c45036a12dd061fc6b8e921c4c99a0d523ef
6
+ metadata.gz: 35844f24ecb678a83548492545403bc174a6d0ce74896f7817d93cb30f62d97b415ea876dccd3efbed7cad3e5dd5c9cda4f676f7dc2c858b2f2010f2c33a0f73
7
+ data.tar.gz: 196a212de355c06a2a3fee6c22302fb1bfd7471dba402124adc7072f90dbd0c4a5b3ee8fa7604d0b164479717bfb65050d3b4802948410b61aba3382d5c8eb39
@@ -7,7 +7,7 @@ jobs:
7
7
  runs-on: ubuntu-latest
8
8
  strategy:
9
9
  matrix:
10
- ruby: [ '2.5', '2.6', '2.7', '3.0' ]
10
+ ruby: [ '2.5', '2.6', '2.7', '3.0', '3.1' ]
11
11
 
12
12
  steps:
13
13
  - uses: actions/checkout@v2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 6.4.0
2
+
3
+ - CSP: Add support for trusted-types, require-trusted-types-for directive (@JackMc): https://github.com/github/secure_headers/pull/486
4
+
1
5
  ## 6.3.4
2
6
 
3
7
  - CSP: Do not deduplicate alternate schema source expressions (@keithamus): https://github.com/github/secure_headers/pull/478
@@ -84,11 +84,12 @@ module SecureHeaders
84
84
  def deep_copy(config)
85
85
  return unless config
86
86
  config.each_with_object({}) do |(key, value), hash|
87
- hash[key] = if value.is_a?(Array)
88
- value.dup
89
- else
90
- value
91
- end
87
+ hash[key] =
88
+ if value.is_a?(Array)
89
+ value.dup
90
+ else
91
+ value
92
+ end
92
93
  end
93
94
  end
94
95
 
@@ -7,17 +7,18 @@ module SecureHeaders
7
7
  include PolicyManagement
8
8
 
9
9
  def initialize(config = nil)
10
- @config = if config.is_a?(Hash)
11
- if config[:report_only]
12
- ContentSecurityPolicyReportOnlyConfig.new(config || DEFAULT_CONFIG)
10
+ @config =
11
+ if config.is_a?(Hash)
12
+ if config[:report_only]
13
+ ContentSecurityPolicyReportOnlyConfig.new(config || DEFAULT_CONFIG)
14
+ else
15
+ ContentSecurityPolicyConfig.new(config || DEFAULT_CONFIG)
16
+ end
17
+ elsif config.nil?
18
+ ContentSecurityPolicyConfig.new(DEFAULT_CONFIG)
13
19
  else
14
- ContentSecurityPolicyConfig.new(config || DEFAULT_CONFIG)
20
+ config
15
21
  end
16
- elsif config.nil?
17
- ContentSecurityPolicyConfig.new(DEFAULT_CONFIG)
18
- else
19
- config
20
- end
21
22
 
22
23
  @preserve_schemes = @config.preserve_schemes
23
24
  @script_nonce = @config.script_nonce
@@ -34,11 +35,12 @@ module SecureHeaders
34
35
  ##
35
36
  # Return the value of the CSP header
36
37
  def value
37
- @value ||= if @config
38
- build_value
39
- else
40
- DEFAULT_VALUE
41
- end
38
+ @value ||=
39
+ if @config
40
+ build_value
41
+ else
42
+ DEFAULT_VALUE
43
+ end
42
44
  end
43
45
 
44
46
  private
@@ -51,7 +53,9 @@ module SecureHeaders
51
53
  def build_value
52
54
  directives.map do |directive_name|
53
55
  case DIRECTIVE_VALUE_TYPES[directive_name]
54
- when :source_list, :require_sri_for_list # require_sri is a simple set of strings that don't need to deal with symbol casing
56
+ when :source_list,
57
+ :require_sri_for_list, # require_sri is a simple set of strings that don't need to deal with symbol casing
58
+ :require_trusted_types_for_list
55
59
  build_source_list_directive(directive_name)
56
60
  when :boolean
57
61
  symbol_to_hyphen_case(directive_name) if @config.directive_value(directive_name)
@@ -35,6 +35,7 @@ module SecureHeaders
35
35
  @report_only = nil
36
36
  @report_uri = nil
37
37
  @require_sri_for = nil
38
+ @require_trusted_types_for = nil
38
39
  @sandbox = nil
39
40
  @script_nonce = nil
40
41
  @script_src = nil
@@ -44,6 +45,7 @@ module SecureHeaders
44
45
  @style_src = nil
45
46
  @style_src_elem = nil
46
47
  @style_src_attr = nil
48
+ @trusted_types = nil
47
49
  @worker_src = nil
48
50
  @upgrade_insecure_requests = nil
49
51
  @disable_nonce_backwards_compatibility = nil
@@ -98,7 +98,19 @@ module SecureHeaders
98
98
  STYLE_SRC_ATTR
99
99
  ].flatten.freeze
100
100
 
101
- ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0).uniq.sort
101
+ # Experimental directives - these vary greatly in support
102
+ # See MDN for details.
103
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types
104
+ TRUSTED_TYPES = :trusted_types
105
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for
106
+ REQUIRE_TRUSTED_TYPES_FOR = :require_trusted_types_for
107
+
108
+ DIRECTIVES_EXPERIMENTAL = [
109
+ TRUSTED_TYPES,
110
+ REQUIRE_TRUSTED_TYPES_FOR,
111
+ ].flatten.freeze
112
+
113
+ ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0 + DIRECTIVES_EXPERIMENTAL).uniq.sort
102
114
 
103
115
  # Think of default-src and report-uri as the beginning and end respectively,
104
116
  # everything else is in between.
@@ -121,6 +133,7 @@ module SecureHeaders
121
133
  OBJECT_SRC => :source_list,
122
134
  PLUGIN_TYPES => :media_type_list,
123
135
  REQUIRE_SRI_FOR => :require_sri_for_list,
136
+ REQUIRE_TRUSTED_TYPES_FOR => :require_trusted_types_for_list,
124
137
  REPORT_URI => :source_list,
125
138
  PREFETCH_SRC => :source_list,
126
139
  SANDBOX => :sandbox_list,
@@ -130,6 +143,7 @@ module SecureHeaders
130
143
  STYLE_SRC => :source_list,
131
144
  STYLE_SRC_ELEM => :source_list,
132
145
  STYLE_SRC_ATTR => :source_list,
146
+ TRUSTED_TYPES => :source_list,
133
147
  WORKER_SRC => :source_list,
134
148
  UPGRADE_INSECURE_REQUESTS => :boolean,
135
149
  }.freeze
@@ -175,6 +189,7 @@ module SecureHeaders
175
189
  ].freeze
176
190
 
177
191
  REQUIRE_SRI_FOR_VALUES = Set.new(%w(script style))
192
+ REQUIRE_TRUSTED_TYPES_FOR_VALUES = Set.new(%w(script))
178
193
 
179
194
  module ClassMethods
180
195
  # Public: generate a header name, value array that is user-agent-aware.
@@ -270,7 +285,8 @@ module SecureHeaders
270
285
  source_list?(directive) ||
271
286
  sandbox_list?(directive) ||
272
287
  media_type_list?(directive) ||
273
- require_sri_for_list?(directive)
288
+ require_sri_for_list?(directive) ||
289
+ require_trusted_types_for_list?(directive)
274
290
  end
275
291
 
276
292
  # For each directive in additions that does not exist in the original config,
@@ -278,11 +294,12 @@ module SecureHeaders
278
294
  def populate_fetch_source_with_default!(original, additions)
279
295
  # in case we would be appending to an empty directive, fill it with the default-src value
280
296
  additions.each_key do |directive|
281
- directive = if directive.to_s.end_with?("_nonce")
282
- directive.to_s.gsub(/_nonce/, "_src").to_sym
283
- else
284
- directive
285
- end
297
+ directive =
298
+ if directive.to_s.end_with?("_nonce")
299
+ directive.to_s.gsub(/_nonce/, "_src").to_sym
300
+ else
301
+ directive
302
+ end
286
303
  # Don't set a default if directive has an existing value
287
304
  next if original[directive]
288
305
  if FETCH_SOURCES.include?(directive)
@@ -307,6 +324,10 @@ module SecureHeaders
307
324
  DIRECTIVE_VALUE_TYPES[directive] == :require_sri_for_list
308
325
  end
309
326
 
327
+ def require_trusted_types_for_list?(directive)
328
+ DIRECTIVE_VALUE_TYPES[directive] == :require_trusted_types_for_list
329
+ end
330
+
310
331
  # Private: Validates that the configuration has a valid type, or that it is a valid
311
332
  # source expression.
312
333
  def validate_directive!(directive, value)
@@ -324,6 +345,8 @@ module SecureHeaders
324
345
  validate_media_type_expression!(directive, value)
325
346
  when :require_sri_for_list
326
347
  validate_require_sri_source_expression!(directive, value)
348
+ when :require_trusted_types_for_list
349
+ validate_require_trusted_types_for_source_expression!(directive, value)
327
350
  else
328
351
  raise ContentSecurityPolicyConfigError.new("Unknown directive #{directive}")
329
352
  end
@@ -368,6 +391,16 @@ module SecureHeaders
368
391
  end
369
392
  end
370
393
 
394
+ # Private: validates that a require trusted types for expression:
395
+ # 1. is an array of strings
396
+ # 2. is a subset of ["script"]
397
+ def validate_require_trusted_types_for_source_expression!(directive, require_trusted_types_for_expression)
398
+ ensure_array_of_strings!(directive, require_trusted_types_for_expression)
399
+ unless require_trusted_types_for_expression.to_set.subset?(REQUIRE_TRUSTED_TYPES_FOR_VALUES)
400
+ raise ContentSecurityPolicyConfigError.new(%(require-trusted-types-for for must be a subset of #{REQUIRE_TRUSTED_TYPES_FOR_VALUES.to_a} but was #{require_trusted_types_for_expression}))
401
+ end
402
+ end
403
+
371
404
  # Private: validates that a source expression:
372
405
  # 1. is an array of strings
373
406
  # 2. does not contain any deprecated, now invalid values (inline, eval, self, none)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecureHeaders
4
- VERSION = "6.3.4"
4
+ VERSION = "6.4.0"
5
5
  end
@@ -147,12 +147,13 @@ module SecureHeaders
147
147
 
148
148
  def nonced_tag(type, content_or_options, block)
149
149
  options = {}
150
- content = if block
151
- options = content_or_options
152
- capture(&block)
153
- else
154
- content_or_options.html_safe # :'(
155
- end
150
+ content =
151
+ if block
152
+ options = content_or_options
153
+ capture(&block)
154
+ else
155
+ content_or_options.html_safe # :'(
156
+ end
156
157
  content_tag type, content, options.merge(nonce: _content_security_policy_nonce(type))
157
158
  end
158
159
 
@@ -146,6 +146,11 @@ module SecureHeaders
146
146
  expect(csp.value).to eq("default-src 'self'; require-sri-for script style")
147
147
  end
148
148
 
149
+ it "allows style as a require-trusted-types-for source" do
150
+ csp = ContentSecurityPolicy.new(default_src: %w('self'), require_trusted_types_for: %w(script))
151
+ expect(csp.value).to eq("default-src 'self'; require-trusted-types-for script")
152
+ end
153
+
149
154
  it "includes prefetch-src" do
150
155
  csp = ContentSecurityPolicy.new(default_src: %w('self'), prefetch_src: %w(foo.com))
151
156
  expect(csp.value).to eq("default-src 'self'; prefetch-src foo.com")
@@ -185,6 +190,21 @@ module SecureHeaders
185
190
  csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')})
186
191
  expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'")
187
192
  end
193
+
194
+ it "supports trusted-types directive" do
195
+ csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy)})
196
+ expect(csp.value).to eq("trusted-types blahblahpolicy")
197
+ end
198
+
199
+ it "supports trusted-types directive with 'none'" do
200
+ csp = ContentSecurityPolicy.new({trusted_types: %w(none)})
201
+ expect(csp.value).to eq("trusted-types none")
202
+ end
203
+
204
+ it "allows duplicate policy names in trusted-types directive" do
205
+ csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy 'allow-duplicates')})
206
+ expect(csp.value).to eq("trusted-types blahblahpolicy 'allow-duplicates'")
207
+ end
188
208
  end
189
209
  end
190
210
  end
@@ -45,6 +45,7 @@ module SecureHeaders
45
45
  plugin_types: %w(application/x-shockwave-flash),
46
46
  prefetch_src: %w(fetch.com),
47
47
  require_sri_for: %w(script style),
48
+ require_trusted_types_for: %w(script),
48
49
  script_src: %w('self'),
49
50
  style_src: %w('unsafe-inline'),
50
51
  upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
@@ -53,6 +54,7 @@ module SecureHeaders
53
54
  script_src_attr: %w(example.com),
54
55
  style_src_elem: %w(example.com),
55
56
  style_src_attr: %w(example.com),
57
+ trusted_types: %w(abcpolicy),
56
58
 
57
59
  report_uri: %w(https://example.com/uri-directive),
58
60
  }
@@ -120,6 +122,12 @@ module SecureHeaders
120
122
  end.to raise_error(ContentSecurityPolicyConfigError)
121
123
  end
122
124
 
125
+ it "rejects style for trusted types" do
126
+ expect do
127
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(style_src: %w('self'), require_trusted_types_for: %w(script style), trusted_types: %w(abcpolicy))))
128
+ end
129
+ end
130
+
123
131
  # this is mostly to ensure people don't use the antiquated shorthands common in other configs
124
132
  it "performs light validation on source lists" do
125
133
  expect do
@@ -6,7 +6,7 @@ class Message < ERB
6
6
  include SecureHeaders::ViewHelpers
7
7
 
8
8
  def self.template
9
- <<-TEMPLATE
9
+ <<-TEMPLATE
10
10
  <% hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
11
11
  console.log(1)
12
12
  <% end %>
@@ -62,9 +62,10 @@ TEMPLATE
62
62
  end
63
63
 
64
64
  def content_tag(type, content = nil, options = nil, &block)
65
- content = if block_given?
66
- capture(block)
67
- end
65
+ content =
66
+ if block_given?
67
+ capture(block)
68
+ end
68
69
 
69
70
  if options.is_a?(Hash)
70
71
  options = options.map { |k, v| " #{k}=#{v}" }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secure_headers
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.4
4
+ version: 6.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Matatall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-28 00:00:00.000000000 Z
11
+ date: 2022-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -116,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  requirements: []
119
- rubygems_version: 3.0.3.1
119
+ rubygems_version: 3.2.9
120
120
  signing_key:
121
121
  specification_version: 4
122
122
  summary: Add easily configured security headers to responses including content-security-policy,