secure_headers 6.3.0 → 6.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce02dddd749c70448cb50004e8ee21cfd5676283c5c119c595efe882985c9d63
4
- data.tar.gz: 59f44239dc9f07aa93280c7455ca6561f96cba05834cdf43f3b2cda37ca2d70d
3
+ metadata.gz: e5372953d5180d45526a777c1b66e7e86a8ccfca08f6b7b613549d8ba1509a6e
4
+ data.tar.gz: 0d1cc3b012df7b1cfd549bcf09063a390945b05c03009acda31017f859ff7d2d
5
5
  SHA512:
6
- metadata.gz: b3d764be6a8dd9715feed0efd02216567d5c75c87a4a7016de6d43fe2e907a20425ea2708ea6384b3fe8e2c47b5a91d964febb0ea227ba3d257f821eb766b2ff
7
- data.tar.gz: f157d965478100e2eebbd3c875e94d2e6c8b66631fa2cbc06bb5316815b5c33234a15a0956087902f39f74b9d38a6f834cae76b338e8c8355bca21261914973c
6
+ metadata.gz: 35844f24ecb678a83548492545403bc174a6d0ce74896f7817d93cb30f62d97b415ea876dccd3efbed7cad3e5dd5c9cda4f676f7dc2c858b2f2010f2c33a0f73
7
+ data.tar.gz: 196a212de355c06a2a3fee6c22302fb1bfd7471dba402124adc7072f90dbd0c4a5b3ee8fa7604d0b164479717bfb65050d3b4802948410b61aba3382d5c8eb39
@@ -0,0 +1,24 @@
1
+ name: Build + Test
2
+ on: [pull_request]
3
+
4
+ jobs:
5
+ build:
6
+ name: Build + Test
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby: [ '2.5', '2.6', '2.7', '3.0', '3.1' ]
11
+
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set up Ruby ${{ matrix.ruby }}
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ - name: Build and test with Rake
19
+ run: |
20
+ gem install bundler
21
+ bundle install --jobs 4 --retry 3 --without guard
22
+ bundle exec rspec spec
23
+ bundle exec rubocop
24
+
@@ -0,0 +1,28 @@
1
+ name: GitHub Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - v*
7
+
8
+ jobs:
9
+ Publish:
10
+ permissions:
11
+ contents: write
12
+ runs-on: ubuntu-latest
13
+ if: startsWith(github.ref, 'refs/tags/v')
14
+ steps:
15
+ - name: Calculate release name
16
+ run: |
17
+ GITHUB_REF=${{ github.ref }}
18
+ RELEASE_NAME=${GITHUB_REF#"refs/tags/"}
19
+ echo "RELEASE_NAME=${RELEASE_NAME}" >> $GITHUB_ENV
20
+ - name: Publish release
21
+ uses: actions/create-release@v1
22
+ env:
23
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24
+ with:
25
+ tag_name: ${{ github.ref }}
26
+ release_name: ${{ env.RELEASE_NAME }}
27
+ draft: false
28
+ prerelease: false
data/.rubocop.yml CHANGED
@@ -1,3 +1,4 @@
1
1
  inherit_gem:
2
2
  rubocop-github:
3
3
  - config/default.yml
4
+ require: rubocop-performance
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.5
1
+ 2.6.6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
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
+
5
+ ## 6.3.4
6
+
7
+ - CSP: Do not deduplicate alternate schema source expressions (@keithamus): https://github.com/github/secure_headers/pull/478
8
+
9
+ ## 6.3.3
10
+
11
+ Fix hash generation for indented helper methods (@rahearn)
12
+
13
+ ## 6.3.2
14
+
15
+ Add support for style-src-attr, style-src-elem, script-src-attr, and script-src-elem directives (@ggalmazor)
16
+
17
+ ## 6.3.1
18
+
19
+ Fixes deprecation warnings when running under ruby 2.7
20
+
1
21
  ## 6.3.0
2
22
 
3
23
  Fixes newline injection issue
@@ -372,7 +392,7 @@ Adds `upgrade-insecure-requests` support for requests from Firefox and Chrome (a
372
392
 
373
393
  ## 3.0.0
374
394
 
375
- secure_headers 3.0.0 is a near-complete, not-entirely-backward-compatible rewrite. Please see the [upgrade guide](https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-3-0.md) for an in-depth explanation of the changes and the suggested upgrade path.
395
+ secure_headers 3.0.0 is a near-complete, not-entirely-backward-compatible rewrite. Please see the [upgrade guide](https://github.com/twitter/secureheaders/blob/main/docs/upgrading-to-3-0.md) for an in-depth explanation of the changes and the suggested upgrade path.
376
396
 
377
397
  ## 2.5.1 - 2016-02-16 18:11:11 UTC - Remove noisy deprecation warning
378
398
 
data/Gemfile CHANGED
@@ -9,8 +9,9 @@ group :test do
9
9
  gem "pry-nav"
10
10
  gem "rack"
11
11
  gem "rspec"
12
- gem "rubocop", "< 0.68"
12
+ gem "rubocop"
13
13
  gem "rubocop-github"
14
+ gem "rubocop-performance"
14
15
  gem "term-ansicolor"
15
16
  gem "tins"
16
17
  end
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Secure Headers [![Build Status](https://travis-ci.org/twitter/secure_headers.svg?branch=master)](http://travis-ci.org/twitter/secure_headers) [![Code Climate](https://codeclimate.com/github/twitter/secureheaders.svg)](https://codeclimate.com/github/twitter/secureheaders) [![Coverage Status](https://coveralls.io/repos/twitter/secureheaders/badge.svg)](https://coveralls.io/r/twitter/secureheaders)
1
+ # Secure Headers ![Build + Test](https://github.com/github/secure_headers/workflows/Build%20+%20Test/badge.svg?branch=main)
2
2
 
3
- **master represents 6.x line**. See the [upgrading to 4.x doc](docs/upgrading-to-4-0.md), [upgrading to 5.x doc](docs/upgrading-to-5-0.md), or [upgrading to 6.x doc](docs/upgrading-to-6-0.md) for instructions on how to upgrade. Bug fixes should go in the 5.x branch for now.
3
+ **main branch represents 6.x line**. See the [upgrading to 4.x doc](docs/upgrading-to-4-0.md), [upgrading to 5.x doc](docs/upgrading-to-5-0.md), or [upgrading to 6.x doc](docs/upgrading-to-6-0.md) for instructions on how to upgrade. Bug fixes should go in the 5.x branch for now.
4
4
 
5
5
  The gem will automatically apply several headers that are related to security. This includes:
6
- - Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](http://www.w3.org/TR/CSP2/)
6
+ - Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](https://www.w3.org/TR/CSP2/)
7
7
  - https://csp.withgoogle.com
8
8
  - https://csp.withgoogle.com/docs/strict-csp.html
9
9
  - https://csp-evaluator.withgoogle.com
@@ -62,7 +62,7 @@ SecureHeaders::Configuration.default do |config|
62
62
  # directive values: these values will directly translate into source directives
63
63
  default_src: %w('none'),
64
64
  base_uri: %w('self'),
65
- block_all_mixed_content: true, # see http://www.w3.org/TR/mixed-content/
65
+ block_all_mixed_content: true, # see https://www.w3.org/TR/mixed-content/
66
66
  child_src: %w('self'), # if child-src isn't supported, the value for frame-src will be set.
67
67
  connect_src: %w(wss:),
68
68
  font_src: %w('self' data:),
@@ -75,7 +75,11 @@ SecureHeaders::Configuration.default do |config|
75
75
  sandbox: true, # true and [] will set a maximally restrictive setting
76
76
  plugin_types: %w(application/x-shockwave-flash),
77
77
  script_src: %w('self'),
78
+ script_src_elem: %w('self'),
79
+ script_src_attr: %w('self'),
78
80
  style_src: %w('unsafe-inline'),
81
+ style_src_elem: %w('unsafe-inline'),
82
+ style_src_attr: %w('unsafe-inline'),
79
83
  worker_src: %w('self'),
80
84
  upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
81
85
  report_uri: %w(https://report-uri.io/example-csp)
@@ -117,16 +121,56 @@ SecureHeaders::Configuration.override(:api) do |config|
117
121
  end
118
122
  ```
119
123
 
120
- However, I would consider these headers anyways depending on your load and bandwidth requirements.
124
+ However, I would consider these headers anyways depending on your load and bandwidth requirements.
125
+
126
+ ## Acknowledgements
127
+
128
+ This project originated within the Security team at Twitter. An archived fork from the point of transition is here: https://github.com/twitter-archive/secure_headers.
129
+
130
+ Contributors include:
131
+ * Neil Matatall @oreoshake
132
+ * Chris Aniszczyk
133
+ * Artur Dryomov
134
+ * Bjørn Mæland
135
+ * Arthur Chiu
136
+ * Jonathan Viney
137
+ * Jeffrey Horn
138
+ * David Collazo
139
+ * Brendon Murphy
140
+ * William Makley
141
+ * Reed Loden
142
+ * Noah Kantrowitz
143
+ * Wyatt Anderson
144
+ * Salimane Adjao Moustapha
145
+ * Francois Chagnon
146
+ * Jeff Hodges
147
+ * Ian Melven
148
+ * Darío Javier Cravero
149
+ * Logan Hasson
150
+ * Raul E Rangel
151
+ * Steve Agalloco
152
+ * Nate Collings
153
+ * Josh Kalderimis
154
+ * Alex Kwiatkowski
155
+ * Julich Mera
156
+ * Jesse Storimer
157
+ * Tom Daniels
158
+ * Kolja Dummann
159
+ * Jean-Philippe Doyle
160
+ * Blake Hitchcock
161
+ * vanderhoorn
162
+ * orthographic-pedant
163
+ * Narsimham Chelluri
164
+
165
+ If you've made a contribution and see your name missing from the list, make a PR and add it!
121
166
 
122
167
  ## Similar libraries
123
168
 
124
169
  * Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
125
170
  * Node.js (express) [helmet](https://github.com/helmetjs/helmet) and [hood](https://github.com/seanmonstar/hood)
126
171
  * Node.js (hapi) [blankie](https://github.com/nlf/blankie)
127
- * J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
128
172
  * ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
129
- * Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
173
+ * Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security), [secure](https://github.com/TypeError/secure)
130
174
  * Go - [secureheader](https://github.com/kr/secureheader)
131
175
  * Elixir [secure_headers](https://github.com/anotherhale/secure_headers)
132
176
  * Dropwizard [dropwizard-web-security](https://github.com/palantir/dropwizard-web-security)
@@ -1,6 +1,6 @@
1
1
  ## Named Appends
2
2
 
3
- Named Appends are blocks of code that can be reused and composed during requests. e.g. If a certain partial is rendered conditionally, and the csp needs to be adjusted for that partial, you can create a named append for that situation. The value returned by the block will be passed into `append_content_security_policy_directives`. The current request object is passed as an argument to the block for even more flexibility.
3
+ Named Appends are blocks of code that can be reused and composed during requests. e.g. If a certain partial is rendered conditionally, and the csp needs to be adjusted for that partial, you can create a named append for that situation. The value returned by the block will be passed into `append_content_security_policy_directives`. The current request object is passed as an argument to the block for even more flexibility. Reusing a configuration name is not allowed and will throw an exception.
4
4
 
5
5
  ```ruby
6
6
  def show
@@ -91,6 +91,8 @@ class MyController < ApplicationController
91
91
  end
92
92
  ```
93
93
 
94
+ Reusing a configuration name is not allowed and will throw an exception.
95
+
94
96
  By default, a no-op configuration is provided. No headers will be set when this default override is used.
95
97
 
96
98
  ```ruby
@@ -54,7 +54,7 @@ Code | Result
54
54
 
55
55
  #### Nonce
56
56
 
57
- You can use a view helper to automatically add nonces to script tags:
57
+ You can use a view helper to automatically add nonces to script tags. Currently, using a nonce helper or calling `content_security_policy_nonce` will populate all configured CSP headers, including report-only and enforced policies.
58
58
 
59
59
  ```erb
60
60
  <%= nonced_javascript_tag do %>
@@ -120,9 +120,7 @@ You can clear the browser cache after the logout request by using the following.
120
120
  class ApplicationController < ActionController::Base
121
121
  # Configuration override to send the Clear-Site-Data header.
122
122
  SecureHeaders::Configuration.override(:clear_browser_cache) do |config|
123
- config.clear_site_data = [
124
- SecureHeaders::ClearSiteData::ALL_TYPES
125
- ]
123
+ config.clear_site_data = SecureHeaders::ClearSiteData::ALL_TYPES
126
124
  end
127
125
 
128
126
 
@@ -29,7 +29,7 @@ Prior to 6.0.0, the response would NOT include a `X-Frame-Options` header since
29
29
 
30
30
  ## `ContentSecurityPolicyConfig#merge` and `ContentSecurityPolicyReportOnlyConfig#merge` work more like `Hash#merge`
31
31
 
32
- These classes are typically not directly instantiated by users of SecureHeaders. But, if you access `config.csp` you end up accessing one of these objects. Prior to 6.0.0, `#merge` worked more like `#append` in that it would combine policies (i.e. if both policies contained the same key the values would be combined rather than overwritten). This was not consistent with `#merge!`, which worked more like ruby's `Hash#merge!` (overwriting duplicate keys). As of 6.0.0, `#merge` works the same as `#merge!`, but returns a new object instead of mutating `self`.
32
+ These classes are typically not directly instantiated by users of SecureHeaders. But, if you access `config.csp` you end up accessing one of these objects. Prior to 6.0.0, `#merge` worked more like `#append` in that it would combine policies (i.e. if both policies contained the same key the values would be combined rather than overwritten). This was not consistent with `#merge!`, which worked more like Ruby's `Hash#merge!` (overwriting duplicate keys). As of 6.0.0, `#merge` works the same as `#merge!`, but returns a new object instead of mutating `self`.
33
33
 
34
34
  ## `Configuration#get` has been removed
35
35
 
@@ -39,7 +39,7 @@ This method is not typically directly called by users of SecureHeaders. Given th
39
39
 
40
40
  Prior to 6.0.0 SecureHeaders pre-built and cached the headers that corresponded to the default configuration. The same was also done for named overrides. However, now that named overrides are applied dynamically, those can no longer be cached. As a result, caching has been removed in the name of simplicity. Some micro-benchmarks indicate this shouldn't be a performance problem and will help to eliminate a class of bugs entirely.
41
41
 
42
- ## Configuration the default configuration more than once will result in an Exception
42
+ ## Calling the default configuration more than once will result in an Exception
43
43
 
44
44
  Prior to 6.0.0 you could conceivably, though unlikely, have `Configure#default` called more than once. Because configurations are dynamic, configuring more than once could result in unexpected behavior. So, as of 6.0.0 we raise `AlreadyConfiguredError` if the default configuration is setup more than once.
45
45
 
@@ -47,4 +47,4 @@ Prior to 6.0.0 you could conceivably, though unlikely, have `Configure#default`
47
47
 
48
48
  The policy configured is the policy that is delivered in terms of which directives are sent. We still dedup, strip schemes, and look for other optimizations but we will not e.g. conditionally send `frame-src` / `child-src` or apply `nonce`s / `unsafe-inline`.
49
49
 
50
- The primary reason for these per-browser customization was to reduce console warnings. This has lead to many bugs and results inc confusing behavior. Also, console logs are incredibly noisy today and increasingly warn you about perfectly valid things (like sending `X-Frame-Options` and `frame-ancestors` together).
50
+ The primary reason for these per-browser customization was to reduce console warnings. This has lead to many bugs and results in confusing behavior. Also, console logs are incredibly noisy today and increasingly warn you about perfectly valid things (like sending `X-Frame-Options` and `frame-ancestors` together).
@@ -43,6 +43,9 @@ module SecureHeaders
43
43
  def override(name, &block)
44
44
  @overrides ||= {}
45
45
  raise "Provide a configuration block" unless block_given?
46
+ if named_append_or_override_exists?(name)
47
+ raise AlreadyConfiguredError, "Configuration already exists"
48
+ end
46
49
  @overrides[name] = block
47
50
  end
48
51
 
@@ -59,6 +62,9 @@ module SecureHeaders
59
62
  def named_append(name, &block)
60
63
  @appends ||= {}
61
64
  raise "Provide a configuration block" unless block_given?
65
+ if named_append_or_override_exists?(name)
66
+ raise AlreadyConfiguredError, "Configuration already exists"
67
+ end
62
68
  @appends[name] = block
63
69
  end
64
70
 
@@ -68,16 +74,22 @@ module SecureHeaders
68
74
 
69
75
  private
70
76
 
77
+ def named_append_or_override_exists?(name)
78
+ (defined?(@appends) && @appends.key?(name)) ||
79
+ (defined?(@overrides) && @overrides.key?(name))
80
+ end
81
+
71
82
  # Public: perform a basic deep dup. The shallow copy provided by dup/clone
72
83
  # can lead to modifying parent objects.
73
84
  def deep_copy(config)
74
85
  return unless config
75
86
  config.each_with_object({}) do |(key, value), hash|
76
- hash[key] = if value.is_a?(Array)
77
- value.dup
78
- else
79
- value
80
- end
87
+ hash[key] =
88
+ if value.is_a?(Array)
89
+ value.dup
90
+ else
91
+ value
92
+ end
81
93
  end
82
94
  end
83
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)
@@ -155,9 +159,10 @@ module SecureHeaders
155
159
  wild_sources = sources.select { |source| source =~ STAR_REGEXP }
156
160
 
157
161
  if wild_sources.any?
162
+ schemes = sources.map { |source| [source, URI(source).scheme] }.to_h
158
163
  sources.reject do |source|
159
164
  !wild_sources.include?(source) &&
160
- wild_sources.any? { |pattern| File.fnmatch(pattern, source) }
165
+ wild_sources.any? { |pattern| schemes[pattern] == schemes[source] && File.fnmatch(pattern, source) }
161
166
  end
162
167
  else
163
168
  sources
@@ -35,11 +35,17 @@ 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
42
+ @script_src_elem = nil
43
+ @script_src_attr = nil
41
44
  @style_nonce = nil
42
45
  @style_src = nil
46
+ @style_src_elem = nil
47
+ @style_src_attr = nil
48
+ @trusted_types = nil
43
49
  @worker_src = nil
44
50
  @upgrade_insecure_requests = nil
45
51
  @disable_nonce_backwards_compatibility = nil
@@ -78,6 +78,10 @@ module SecureHeaders
78
78
  REQUIRE_SRI_FOR = :require_sri_for
79
79
  UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
80
80
  WORKER_SRC = :worker_src
81
+ SCRIPT_SRC_ELEM = :script_src_elem
82
+ SCRIPT_SRC_ATTR = :script_src_attr
83
+ STYLE_SRC_ELEM = :style_src_elem
84
+ STYLE_SRC_ATTR = :style_src_attr
81
85
 
82
86
  DIRECTIVES_3_0 = [
83
87
  DIRECTIVES_2_0,
@@ -87,10 +91,26 @@ module SecureHeaders
87
91
  PREFETCH_SRC,
88
92
  REQUIRE_SRI_FOR,
89
93
  WORKER_SRC,
90
- UPGRADE_INSECURE_REQUESTS
94
+ UPGRADE_INSECURE_REQUESTS,
95
+ SCRIPT_SRC_ELEM,
96
+ SCRIPT_SRC_ATTR,
97
+ STYLE_SRC_ELEM,
98
+ STYLE_SRC_ATTR
91
99
  ].flatten.freeze
92
100
 
93
- 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
94
114
 
95
115
  # Think of default-src and report-uri as the beginning and end respectively,
96
116
  # everything else is in between.
@@ -113,11 +133,17 @@ module SecureHeaders
113
133
  OBJECT_SRC => :source_list,
114
134
  PLUGIN_TYPES => :media_type_list,
115
135
  REQUIRE_SRI_FOR => :require_sri_for_list,
136
+ REQUIRE_TRUSTED_TYPES_FOR => :require_trusted_types_for_list,
116
137
  REPORT_URI => :source_list,
117
138
  PREFETCH_SRC => :source_list,
118
139
  SANDBOX => :sandbox_list,
119
140
  SCRIPT_SRC => :source_list,
141
+ SCRIPT_SRC_ELEM => :source_list,
142
+ SCRIPT_SRC_ATTR => :source_list,
120
143
  STYLE_SRC => :source_list,
144
+ STYLE_SRC_ELEM => :source_list,
145
+ STYLE_SRC_ATTR => :source_list,
146
+ TRUSTED_TYPES => :source_list,
121
147
  WORKER_SRC => :source_list,
122
148
  UPGRADE_INSECURE_REQUESTS => :boolean,
123
149
  }.freeze
@@ -163,6 +189,7 @@ module SecureHeaders
163
189
  ].freeze
164
190
 
165
191
  REQUIRE_SRI_FOR_VALUES = Set.new(%w(script style))
192
+ REQUIRE_TRUSTED_TYPES_FOR_VALUES = Set.new(%w(script))
166
193
 
167
194
  module ClassMethods
168
195
  # Public: generate a header name, value array that is user-agent-aware.
@@ -258,7 +285,8 @@ module SecureHeaders
258
285
  source_list?(directive) ||
259
286
  sandbox_list?(directive) ||
260
287
  media_type_list?(directive) ||
261
- require_sri_for_list?(directive)
288
+ require_sri_for_list?(directive) ||
289
+ require_trusted_types_for_list?(directive)
262
290
  end
263
291
 
264
292
  # For each directive in additions that does not exist in the original config,
@@ -266,11 +294,12 @@ module SecureHeaders
266
294
  def populate_fetch_source_with_default!(original, additions)
267
295
  # in case we would be appending to an empty directive, fill it with the default-src value
268
296
  additions.each_key do |directive|
269
- directive = if directive.to_s.end_with?("_nonce")
270
- directive.to_s.gsub(/_nonce/, "_src").to_sym
271
- else
272
- directive
273
- 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
274
303
  # Don't set a default if directive has an existing value
275
304
  next if original[directive]
276
305
  if FETCH_SOURCES.include?(directive)
@@ -295,6 +324,10 @@ module SecureHeaders
295
324
  DIRECTIVE_VALUE_TYPES[directive] == :require_sri_for_list
296
325
  end
297
326
 
327
+ def require_trusted_types_for_list?(directive)
328
+ DIRECTIVE_VALUE_TYPES[directive] == :require_trusted_types_for_list
329
+ end
330
+
298
331
  # Private: Validates that the configuration has a valid type, or that it is a valid
299
332
  # source expression.
300
333
  def validate_directive!(directive, value)
@@ -312,6 +345,8 @@ module SecureHeaders
312
345
  validate_media_type_expression!(directive, value)
313
346
  when :require_sri_for_list
314
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)
315
350
  else
316
351
  raise ContentSecurityPolicyConfigError.new("Unknown directive #{directive}")
317
352
  end
@@ -356,6 +391,16 @@ module SecureHeaders
356
391
  end
357
392
  end
358
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
+
359
404
  # Private: validates that a source expression:
360
405
  # 1. is an array of strings
361
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.0"
4
+ VERSION = "6.4.0"
5
5
  end
@@ -21,7 +21,7 @@ module SecureHeaders
21
21
  def nonced_stylesheet_link_tag(*args, &block)
22
22
  opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:style))
23
23
 
24
- stylesheet_link_tag(*args, opts, &block)
24
+ stylesheet_link_tag(*args, **opts, &block)
25
25
  end
26
26
 
27
27
  # Public: create a script tag using the content security policy nonce.
@@ -39,7 +39,7 @@ module SecureHeaders
39
39
  def nonced_javascript_include_tag(*args, &block)
40
40
  opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:script))
41
41
 
42
- javascript_include_tag(*args, opts, &block)
42
+ javascript_include_tag(*args, **opts, &block)
43
43
  end
44
44
 
45
45
  # Public: create a script Webpacker pack tag using the content security policy nonce.
@@ -49,7 +49,7 @@ module SecureHeaders
49
49
  def nonced_javascript_pack_tag(*args, &block)
50
50
  opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:script))
51
51
 
52
- javascript_pack_tag(*args, opts, &block)
52
+ javascript_pack_tag(*args, **opts, &block)
53
53
  end
54
54
 
55
55
  # Public: create a stylesheet Webpacker link tag using the content security policy nonce.
@@ -59,7 +59,7 @@ module SecureHeaders
59
59
  def nonced_stylesheet_pack_tag(*args, &block)
60
60
  opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:style))
61
61
 
62
- stylesheet_pack_tag(*args, opts, &block)
62
+ stylesheet_pack_tag(*args, **opts, &block)
63
63
  end
64
64
 
65
65
  # Public: use the content security policy nonce for this request directly.
@@ -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
 
data/lib/tasks/tasks.rake CHANGED
@@ -20,10 +20,11 @@ namespace :secure_headers do
20
20
  (is_erb?(filename) && inline_script =~ /<%.*%>/)
21
21
  end
22
22
 
23
- def find_inline_content(filename, regex, hashes)
23
+ def find_inline_content(filename, regex, hashes, strip_trailing_whitespace)
24
24
  file = File.read(filename)
25
25
  file.scan(regex) do # TODO don't use gsub
26
26
  inline_script = Regexp.last_match.captures.last
27
+ inline_script.gsub!(/(\r?\n)[\t ]+\z/, '\1') if strip_trailing_whitespace
27
28
  if dynamic_content?(filename, inline_script)
28
29
  puts "Looks like there's some dynamic content inside of a tag :-/"
29
30
  puts "That pretty much means the hash value will never match."
@@ -38,9 +39,8 @@ namespace :secure_headers do
38
39
  def generate_inline_script_hashes(filename)
39
40
  hashes = []
40
41
 
41
- [INLINE_SCRIPT_REGEX, INLINE_HASH_SCRIPT_HELPER_REGEX].each do |regex|
42
- find_inline_content(filename, regex, hashes)
43
- end
42
+ find_inline_content(filename, INLINE_SCRIPT_REGEX, hashes, false)
43
+ find_inline_content(filename, INLINE_HASH_SCRIPT_HELPER_REGEX, hashes, true)
44
44
 
45
45
  hashes
46
46
  end
@@ -48,9 +48,8 @@ namespace :secure_headers do
48
48
  def generate_inline_style_hashes(filename)
49
49
  hashes = []
50
50
 
51
- [INLINE_STYLE_REGEX, INLINE_HASH_STYLE_HELPER_REGEX].each do |regex|
52
- find_inline_content(filename, regex, hashes)
53
- end
51
+ find_inline_content(filename, INLINE_STYLE_REGEX, hashes, false)
52
+ find_inline_content(filename, INLINE_HASH_STYLE_HELPER_REGEX, hashes, true)
54
53
 
55
54
  hashes
56
55
  end
@@ -34,6 +34,60 @@ module SecureHeaders
34
34
  expect(Configuration.overrides(:test_override)).to_not be_nil
35
35
  end
36
36
 
37
+ describe "#override" do
38
+ it "raises on configuring an existing override" do
39
+ set_override = Proc.new {
40
+ Configuration.override(:test_override) do |config|
41
+ config.x_frame_options = "DENY"
42
+ end
43
+ }
44
+
45
+ set_override.call
46
+
47
+ expect { set_override.call }
48
+ .to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
49
+ end
50
+
51
+ it "raises when a named append with the given name exists" do
52
+ Configuration.named_append(:test_override) do |config|
53
+ config.x_frame_options = "DENY"
54
+ end
55
+
56
+ expect do
57
+ Configuration.override(:test_override) do |config|
58
+ config.x_frame_options = "SAMEORIGIN"
59
+ end
60
+ end.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
61
+ end
62
+ end
63
+
64
+ describe "#named_append" do
65
+ it "raises on configuring an existing append" do
66
+ set_override = Proc.new {
67
+ Configuration.named_append(:test_override) do |config|
68
+ config.x_frame_options = "DENY"
69
+ end
70
+ }
71
+
72
+ set_override.call
73
+
74
+ expect { set_override.call }
75
+ .to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
76
+ end
77
+
78
+ it "raises when an override with the given name exists" do
79
+ Configuration.override(:test_override) do |config|
80
+ config.x_frame_options = "DENY"
81
+ end
82
+
83
+ expect do
84
+ Configuration.named_append(:test_override) do |config|
85
+ config.x_frame_options = "SAMEORIGIN"
86
+ end
87
+ end.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
88
+ end
89
+ end
90
+
37
91
  it "deprecates the secure_cookies configuration" do
38
92
  expect {
39
93
  Configuration.default do |config|
@@ -106,6 +106,11 @@ module SecureHeaders
106
106
  expect(csp.value).to eq("default-src example.org")
107
107
  end
108
108
 
109
+ it "does not deduplicate non-matching schema source expressions" do
110
+ csp = ContentSecurityPolicy.new(default_src: %w(*.example.org wss://example.example.org))
111
+ expect(csp.value).to eq("default-src *.example.org wss://example.example.org")
112
+ end
113
+
109
114
  it "creates maximally strict sandbox policy when passed no sandbox token values" do
110
115
  csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: [])
111
116
  expect(csp.value).to eq("default-src example.org; sandbox")
@@ -141,6 +146,11 @@ module SecureHeaders
141
146
  expect(csp.value).to eq("default-src 'self'; require-sri-for script style")
142
147
  end
143
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
+
144
154
  it "includes prefetch-src" do
145
155
  csp = ContentSecurityPolicy.new(default_src: %w('self'), prefetch_src: %w(foo.com))
146
156
  expect(csp.value).to eq("default-src 'self'; prefetch-src foo.com")
@@ -160,6 +170,41 @@ module SecureHeaders
160
170
  csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456, disable_nonce_backwards_compatibility: true })
161
171
  expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
162
172
  end
173
+
174
+ it "supports script-src-elem directive" do
175
+ csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_elem: %w('self')})
176
+ expect(csp.value).to eq("script-src 'self'; script-src-elem 'self'")
177
+ end
178
+
179
+ it "supports script-src-attr directive" do
180
+ csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_attr: %w('self')})
181
+ expect(csp.value).to eq("script-src 'self'; script-src-attr 'self'")
182
+ end
183
+
184
+ it "supports style-src-elem directive" do
185
+ csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_elem: %w('self')})
186
+ expect(csp.value).to eq("style-src 'self'; style-src-elem 'self'")
187
+ end
188
+
189
+ it "supports style-src-attr directive" do
190
+ csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')})
191
+ expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'")
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
163
208
  end
164
209
  end
165
210
  end
@@ -45,10 +45,16 @@ 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/
51
52
  worker_src: %w(worker.com),
53
+ script_src_elem: %w(example.com),
54
+ script_src_attr: %w(example.com),
55
+ style_src_elem: %w(example.com),
56
+ style_src_attr: %w(example.com),
57
+ trusted_types: %w(abcpolicy),
52
58
 
53
59
  report_uri: %w(https://example.com/uri-directive),
54
60
  }
@@ -116,6 +122,12 @@ module SecureHeaders
116
122
  end.to raise_error(ContentSecurityPolicyConfigError)
117
123
  end
118
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
+
119
131
  # this is mostly to ensure people don't use the antiquated shorthands common in other configs
120
132
  it "performs light validation on source lists" do
121
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}" }
data/spec/spec_helper.rb CHANGED
@@ -45,10 +45,20 @@ module SecureHeaders
45
45
  def clear_default_config
46
46
  remove_instance_variable(:@default_config) if defined?(@default_config)
47
47
  end
48
+
49
+ def clear_overrides
50
+ remove_instance_variable(:@overrides) if defined?(@overrides)
51
+ end
52
+
53
+ def clear_appends
54
+ remove_instance_variable(:@appends) if defined?(@appends)
55
+ end
48
56
  end
49
57
  end
50
58
  end
51
59
 
52
60
  def reset_config
53
61
  SecureHeaders::Configuration.clear_default_config
62
+ SecureHeaders::Configuration.clear_overrides
63
+ SecureHeaders::Configuration.clear_appends
54
64
  end
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.0
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: 2020-01-21 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
@@ -33,12 +33,13 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - ".github/ISSUE_TEMPLATE.md"
35
35
  - ".github/PULL_REQUEST_TEMPLATE.md"
36
+ - ".github/workflows/build.yml"
37
+ - ".github/workflows/github-release.yml"
36
38
  - ".gitignore"
37
39
  - ".rspec"
38
40
  - ".rubocop.yml"
39
41
  - ".ruby-gemset"
40
42
  - ".ruby-version"
41
- - ".travis.yml"
42
43
  - CHANGELOG.md
43
44
  - CODE_OF_CONDUCT.md
44
45
  - CONTRIBUTING.md
@@ -115,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0'
117
118
  requirements: []
118
- rubygems_version: 3.0.3
119
+ rubygems_version: 3.2.9
119
120
  signing_key:
120
121
  specification_version: 4
121
122
  summary: Add easily configured security headers to responses including content-security-policy,
data/.travis.yml DELETED
@@ -1,28 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - ruby-head
5
- - 2.5
6
- - 2.6
7
- - 2.7
8
- - jruby-head
9
-
10
- env:
11
- - SUITE=rspec spec
12
- - SUITE=rubocop
13
-
14
- script: bundle exec $SUITE
15
-
16
- matrix:
17
- allow_failures:
18
- - rvm: jruby-head
19
- - rvm: ruby-head
20
-
21
- before_install:
22
- - gem update --system
23
- - gem --version
24
- - gem update bundler
25
- bundler_args: --without guard -j 3
26
-
27
- sudo: false
28
- cache: bundler