secure_headers 6.3.4 → 6.5.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: bafd5e6390db0f1975599ab34f1293986a5c0a1ced14f6cfeca47fd114ce87d4
4
+ data.tar.gz: 177dc27fa8238fe1a8baa238f6baa244b2c03393f61269e9b417e5ea3a4a4bba
5
5
  SHA512:
6
- metadata.gz: b04fd60ff28519273f29d335b81b44ebfe938d4e97d82d31b69d8596dc84e38c812573248e7b70bb142fecebab357c7f34f9f10934edaf354e3174915220580f
7
- data.tar.gz: 10748a3ff12365fffe42828fe324e0bfbb3f146635b095a9e8a13b5a57b5bdfe17a5463df3b5009d8591dbc88494c45036a12dd061fc6b8e921c4c99a0d523ef
6
+ metadata.gz: 7a24f853958892e2780dec3cfd8f631d394c959cda6d3f8be5d701792fc1ce57d4141ca88e7b0980fcac979cc855a0c5bc9165a05b314fe281ce092a438f1fe1
7
+ data.tar.gz: c082a3ee192452712f8e9c7ed18d5c21b5b3eb1712c3d9a4df44220093cf1be09a8e5384f5c8a8909820f1e0048c6d3b6915cc29081288bcf13361bf8cf7dbed
@@ -1,5 +1,5 @@
1
1
  name: Build + Test
2
- on: [pull_request]
2
+ on: [pull_request, push]
3
3
 
4
4
  jobs:
5
5
  build:
@@ -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.6', '2.7', '3.0', '3.1' ]
11
11
 
12
12
  steps:
13
13
  - uses: actions/checkout@v2
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.6
1
+ 3.1.1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 6.5.0
2
+
3
+ - CSP: Remove source expression deduplication. (@lgarron) https://github.com/github/secure_headers/pull/499
4
+
5
+ ## 6.4.0
6
+
7
+ - CSP: Add support for trusted-types, require-trusted-types-for directive (@JackMc): https://github.com/github/secure_headers/pull/486
8
+
1
9
  ## 6.3.4
2
10
 
3
11
  - 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)
@@ -129,7 +133,7 @@ module SecureHeaders
129
133
  unless directive == REPORT_URI || @preserve_schemes
130
134
  source_list = strip_source_schemes(source_list)
131
135
  end
132
- dedup_source_list(source_list)
136
+ source_list.uniq
133
137
  end
134
138
  end
135
139
 
@@ -147,24 +151,6 @@ module SecureHeaders
147
151
  end
148
152
  end
149
153
 
150
- # Removes duplicates and sources that already match an existing wild card.
151
- #
152
- # e.g. *.github.com asdf.github.com becomes *.github.com
153
- def dedup_source_list(sources)
154
- sources = sources.uniq
155
- wild_sources = sources.select { |source| source =~ STAR_REGEXP }
156
-
157
- if wild_sources.any?
158
- schemes = sources.map { |source| [source, URI(source).scheme] }.to_h
159
- sources.reject do |source|
160
- !wild_sources.include?(source) &&
161
- wild_sources.any? { |pattern| schemes[pattern] == schemes[source] && File.fnmatch(pattern, source) }
162
- end
163
- else
164
- sources
165
- end
166
- end
167
-
168
154
  # Private: append a nonce to the script/style directories if script_nonce
169
155
  # or style_nonce are provided.
170
156
  def populate_nonces(directive, source_list)
@@ -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.5.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
 
@@ -9,12 +9,12 @@ Gem::Specification.new do |gem|
9
9
  gem.version = SecureHeaders::VERSION
10
10
  gem.authors = ["Neil Matatall"]
11
11
  gem.email = ["neil.matatall@gmail.com"]
12
- gem.description = "Manages application of security headers with many safe defaults."
13
- gem.summary = 'Add easily configured security headers to responses
12
+ gem.summary = "Manages application of security headers with many safe defaults."
13
+ gem.description = 'Add easily configured security headers to responses
14
14
  including content-security-policy, x-frame-options,
15
15
  strict-transport-security, etc.'
16
16
  gem.homepage = "https://github.com/twitter/secureheaders"
17
- gem.license = "Apache Public License 2.0"
17
+ gem.license = "MIT"
18
18
  gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
19
19
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
20
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
@@ -48,12 +48,12 @@ module SecureHeaders
48
48
  expect(csp.value).to eq("default-src * 'unsafe-inline' 'unsafe-eval' data: blob:")
49
49
  end
50
50
 
51
- it "minifies source expressions based on overlapping wildcards" do
51
+ it "does not minify source expressions based on overlapping wildcards" do
52
52
  config = {
53
53
  default_src: %w(a.example.org b.example.org *.example.org https://*.example.org)
54
54
  }
55
55
  csp = ContentSecurityPolicy.new(config)
56
- expect(csp.value).to eq("default-src *.example.org")
56
+ expect(csp.value).to eq("default-src a.example.org b.example.org *.example.org")
57
57
  end
58
58
 
59
59
  it "removes http/s schemes from hosts" do
@@ -101,8 +101,13 @@ module SecureHeaders
101
101
  expect(csp.value).to eq("default-src example.org; block-all-mixed-content")
102
102
  end
103
103
 
104
- it "deduplicates any source expressions" do
105
- csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
104
+ it "handles wildcard subdomain with wildcard port" do
105
+ csp = ContentSecurityPolicy.new(default_src: %w(https://*.example.org:*))
106
+ expect(csp.value).to eq("default-src *.example.org:*")
107
+ end
108
+
109
+ it "deduplicates source expressions that match exactly (after scheme stripping)" do
110
+ csp = ContentSecurityPolicy.new(default_src: %w(example.org https://example.org example.org))
106
111
  expect(csp.value).to eq("default-src example.org")
107
112
  end
108
113
 
@@ -146,6 +151,11 @@ module SecureHeaders
146
151
  expect(csp.value).to eq("default-src 'self'; require-sri-for script style")
147
152
  end
148
153
 
154
+ it "allows style as a require-trusted-types-for source" do
155
+ csp = ContentSecurityPolicy.new(default_src: %w('self'), require_trusted_types_for: %w(script))
156
+ expect(csp.value).to eq("default-src 'self'; require-trusted-types-for script")
157
+ end
158
+
149
159
  it "includes prefetch-src" do
150
160
  csp = ContentSecurityPolicy.new(default_src: %w('self'), prefetch_src: %w(foo.com))
151
161
  expect(csp.value).to eq("default-src 'self'; prefetch-src foo.com")
@@ -185,6 +195,21 @@ module SecureHeaders
185
195
  csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')})
186
196
  expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'")
187
197
  end
198
+
199
+ it "supports trusted-types directive" do
200
+ csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy)})
201
+ expect(csp.value).to eq("trusted-types blahblahpolicy")
202
+ end
203
+
204
+ it "supports trusted-types directive with 'none'" do
205
+ csp = ContentSecurityPolicy.new({trusted_types: %w('none')})
206
+ expect(csp.value).to eq("trusted-types 'none'")
207
+ end
208
+
209
+ it "allows duplicate policy names in trusted-types directive" do
210
+ csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy 'allow-duplicates')})
211
+ expect(csp.value).to eq("trusted-types blahblahpolicy 'allow-duplicates'")
212
+ end
188
213
  end
189
214
  end
190
215
  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.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Matatall
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-28 00:00:00.000000000 Z
11
+ date: 2022-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -24,7 +24,10 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- description: Manages application of security headers with many safe defaults.
27
+ description: |-
28
+ Add easily configured security headers to responses
29
+ including content-security-policy, x-frame-options,
30
+ strict-transport-security, etc.
28
31
  email:
29
32
  - neil.matatall@gmail.com
30
33
  executables: []
@@ -99,9 +102,9 @@ files:
99
102
  - spec/spec_helper.rb
100
103
  homepage: https://github.com/twitter/secureheaders
101
104
  licenses:
102
- - Apache Public License 2.0
105
+ - MIT
103
106
  metadata: {}
104
- post_install_message:
107
+ post_install_message:
105
108
  rdoc_options: []
106
109
  require_paths:
107
110
  - lib
@@ -116,11 +119,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
119
  - !ruby/object:Gem::Version
117
120
  version: '0'
118
121
  requirements: []
119
- rubygems_version: 3.0.3.1
120
- signing_key:
122
+ rubygems_version: 3.3.7
123
+ signing_key:
121
124
  specification_version: 4
122
- summary: Add easily configured security headers to responses including content-security-policy,
123
- x-frame-options, strict-transport-security, etc.
125
+ summary: Manages application of security headers with many safe defaults.
124
126
  test_files:
125
127
  - spec/lib/secure_headers/configuration_spec.rb
126
128
  - spec/lib/secure_headers/headers/clear_site_data_spec.rb