secure_headers 6.1.1 → 6.3.2

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: 7de332e0980c748038f72b7775399f65556521cf37b4392665fdd11a5192df3b
4
- data.tar.gz: a6bea34afe4962462f0a263fd7728fa659ec65c15f8cfbca94c4639bc6452d67
3
+ metadata.gz: 5e68ad14ceec22ceeeabe2f49b47ad9325f43585cbb688f9f6ffe9c9f3536abc
4
+ data.tar.gz: ffab69d446b3935d4cf01ad21c660d8035ab8ecd4343d682841510509bb8b714
5
5
  SHA512:
6
- metadata.gz: 1fcad5939d9cb914adf56965624b4620b9b7146fc0d5cc5ca9a1741f257c6884bd16c940805ac260e7cb3c7752411caae94b84c2bf8f7730d21c35ce3fa5702d
7
- data.tar.gz: 505e0e2907f8eb9f452c3ae6d14a32bf224de7a2f73de41e8903679f586e4cc4e0fed7a6e6c240a7822cb04050ef30165f552363bc63edda6d258743341f3054
6
+ metadata.gz: 02a79d8c96fd8d64eba216bd8785ae2deee7dceb7ef388f65e100f1597ed1f3005ecbeb069e823beef5fcdf89a6998ea296d954e3d80de0ba61456a13445b6e3
7
+ data.tar.gz: 645a0f3aac96761574b57e4464b08c83cbf6839fc4a0503e54dedd768aebdb503a080ee368753add3ab0c2a3eeb31bd4f866f0e39ed7691c17587ad7125299eb
@@ -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.4', '2.5', '2.6', '2.7' ]
11
+
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set up Ruby ${{ matrix.ruby }}
15
+ uses: actions/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,20 @@
1
+ # This workflow ensures the "master" branch is always up-to-date with the
2
+ # "main" branch (our default one)
3
+ name: sync_main_branch
4
+ on:
5
+ push:
6
+ branches: [ main ]
7
+ jobs:
8
+ catch_up:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Check out the repository
12
+ uses: actions/checkout@v2
13
+ with:
14
+ fetch-depth: 0
15
+ - name: Merge development into master, then push it
16
+ run: |
17
+ git pull
18
+ git checkout master
19
+ git merge main
20
+ git push
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.1
1
+ 2.6.6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## 6.3.2
2
+
3
+ Add support for style-src-attr, style-src-elem, script-src-attr, and script-src-elem directives (@ggalmazor)
4
+
5
+ ## 6.3.1
6
+
7
+ Fixes deprecation warnings when running under ruby 2.7
8
+
9
+ ## 6.3.0
10
+
11
+ Fixes newline injection issue
12
+
13
+ ## 6.2.0
14
+
15
+ Fixes semicolon injection issue reported by @mvgijssel see https://github.com/twitter/secure_headers/issues/418
16
+
17
+ ## 6.1.2
18
+
19
+ Adds the ability to specify `SameSite=none` with the same configurability as `Strict`/`Lax` in order to disable Chrome's soon-to-be-lax-by-default state.
20
+
1
21
  ## 6.1.1
2
22
 
3
23
  Adds the ability to disable the automatically-appended `'unsafe-inline'` value when nonces are used #404 (@will)
@@ -360,7 +380,7 @@ Adds `upgrade-insecure-requests` support for requests from Firefox and Chrome (a
360
380
 
361
381
  ## 3.0.0
362
382
 
363
- 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.
383
+ 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.
364
384
 
365
385
  ## 2.5.1 - 2016-02-16 18:11:11 UTC - Remove noisy deprecation warning
366
386
 
data/Gemfile CHANGED
@@ -9,15 +9,16 @@ group :test do
9
9
  gem "pry-nav"
10
10
  gem "rack"
11
11
  gem "rspec"
12
- gem "rubocop"
12
+ gem "rubocop", "< 0.68"
13
13
  gem "rubocop-github"
14
+ gem "rubocop-performance"
14
15
  gem "term-ansicolor"
15
16
  gem "tins"
16
17
  end
17
18
 
18
19
  group :guard do
19
20
  gem "growl"
20
- gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24]
21
+ gem "guard-rspec", platforms: [:ruby]
21
22
  gem "rb-fsevent"
22
23
  gem "terminal-notifier-guard"
23
24
  end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2013, 2014, 2015, 2016, 2017 Twitter, Inc.
1
+ Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Twitter, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Secure Headers [![Build Status](https://travis-ci.org/twitter/secureheaders.svg?branch=master)](http://travis-ci.org/twitter/secureheaders) [![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
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/)
@@ -57,6 +57,7 @@ SecureHeaders::Configuration.default do |config|
57
57
  config.csp = {
58
58
  # "meta" values. these will shape the header, but the values are not included in the header.
59
59
  preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
60
+ disable_nonce_backwards_compatibility: true, # default: false. If false, `unsafe-inline` will be added automatically when using nonces. If true, it won't. See #403 for why you'd want this.
60
61
 
61
62
  # directive values: these values will directly translate into source directives
62
63
  default_src: %w('none'),
@@ -74,7 +75,11 @@ SecureHeaders::Configuration.default do |config|
74
75
  sandbox: true, # true and [] will set a maximally restrictive setting
75
76
  plugin_types: %w(application/x-shockwave-flash),
76
77
  script_src: %w('self'),
78
+ script_src_elem: %w('self'),
79
+ script_src_attr: %w('self'),
77
80
  style_src: %w('unsafe-inline'),
81
+ style_src_elem: %w('unsafe-inline'),
82
+ style_src_attr: %w('unsafe-inline'),
78
83
  worker_src: %w('self'),
79
84
  upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
80
85
  report_uri: %w(https://report-uri.io/example-csp)
@@ -116,14 +121,54 @@ SecureHeaders::Configuration.override(:api) do |config|
116
121
  end
117
122
  ```
118
123
 
119
- 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!
120
166
 
121
167
  ## Similar libraries
122
168
 
123
169
  * Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
124
170
  * Node.js (express) [helmet](https://github.com/helmetjs/helmet) and [hood](https://github.com/seanmonstar/hood)
125
171
  * Node.js (hapi) [blankie](https://github.com/nlf/blankie)
126
- * J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
127
172
  * ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
128
173
  * Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
129
174
  * Go - [secureheader](https://github.com/kr/secureheader)
@@ -131,9 +176,3 @@ However, I would consider these headers anyways depending on your load and bandw
131
176
  * Dropwizard [dropwizard-web-security](https://github.com/palantir/dropwizard-web-security)
132
177
  * Ember.js [ember-cli-content-security-policy](https://github.com/rwjblue/ember-cli-content-security-policy/)
133
178
  * PHP [secure-headers](https://github.com/BePsvPT/secure-headers)
134
-
135
- ## License
136
-
137
- Copyright 2013-2014 Twitter, Inc and other contributors.
138
-
139
- Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
data/docs/cookies.md CHANGED
@@ -25,7 +25,7 @@ Boolean-based configuration is intended to globally enable or disable a specific
25
25
  ```ruby
26
26
  config.cookies = {
27
27
  secure: true, # mark all cookies as Secure
28
- httponly: OPT_OUT, # do not mark any cookies as HttpOnly
28
+ httponly: SecureHeaders::OPT_OUT, # do not mark any cookies as HttpOnly
29
29
  }
30
30
  ```
31
31
 
@@ -52,13 +52,14 @@ config.cookies = {
52
52
  }
53
53
  ```
54
54
 
55
- `Strict` and `Lax` enforcement modes can also be specified using a Hash.
55
+ `Strict`, `Lax`, and `None` enforcement modes can also be specified using a Hash.
56
56
 
57
57
  ```ruby
58
58
  config.cookies = {
59
59
  samesite: {
60
- strict: { only: ['_rails_session'] },
61
- lax: { only: ['_guest'] }
60
+ strict: { only: ['session_id_duplicate'] },
61
+ lax: { only: ['_guest', '_rails_session', 'device_id'] },
62
+ none: { only: ['_tracking', 'saml_cookie', 'session_id'] },
62
63
  }
63
64
  }
64
65
  ```
@@ -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
@@ -76,11 +76,6 @@ class ApplicationController < ActionController::Base
76
76
  SecureHeaders::Configuration.override(:script_from_otherdomain_com) do |config|
77
77
  config.csp[:script_src] << "otherdomain.com"
78
78
  end
79
-
80
- # overrides the :script_from_otherdomain_com configuration
81
- SecureHeaders::Configuration.override(:another_config, :script_from_otherdomain_com) do |config|
82
- config.csp[:script_src] << "evenanotherdomain.com"
83
- end
84
79
  end
85
80
 
86
81
  class MyController < ApplicationController
@@ -96,6 +91,8 @@ class MyController < ApplicationController
96
91
  end
97
92
  ```
98
93
 
94
+ Reusing a configuration name is not allowed and will throw an exception.
95
+
99
96
  By default, a no-op configuration is provided. No headers will be set when this default override is used.
100
97
 
101
98
  ```ruby
@@ -5,7 +5,7 @@ The original implementation of name overrides worked by making a copy of the def
5
5
  ```ruby
6
6
  class ApplicationController < ActionController::Base
7
7
  Configuration.default do |config|
8
- config.x_frame_options = OPT_OUT
8
+ config.x_frame_options = SecureHeaders::OPT_OUT
9
9
  end
10
10
 
11
11
  SecureHeaders::Configuration.override(:dynamic_override) do |config|
@@ -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,6 +74,11 @@ 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)
@@ -126,7 +137,9 @@ module SecureHeaders
126
137
  # The list of attributes that must respond to a `make_header` method
127
138
  HEADERABLE_ATTRIBUTES = (CONFIG_ATTRIBUTES - [:cookies]).freeze
128
139
 
129
- attr_accessor(*CONFIG_ATTRIBUTES_TO_HEADER_CLASSES.keys)
140
+ attr_writer(*(CONFIG_ATTRIBUTES_TO_HEADER_CLASSES.reject { |key| [:csp, :csp_report_only].include?(key) }.keys))
141
+
142
+ attr_reader(*(CONFIG_ATTRIBUTES_TO_HEADER_CLASSES.keys))
130
143
 
131
144
  @script_hashes = nil
132
145
  @style_hashes = nil
@@ -103,10 +103,15 @@ module SecureHeaders
103
103
  # Returns a string representing a directive.
104
104
  def build_source_list_directive(directive)
105
105
  source_list = @config.directive_value(directive)
106
-
107
106
  if source_list != OPT_OUT && source_list && source_list.any?
108
- normalized_source_list = minify_source_list(directive, source_list)
109
- [symbol_to_hyphen_case(directive), normalized_source_list].join(" ")
107
+ minified_source_list = minify_source_list(directive, source_list).join(" ")
108
+
109
+ if minified_source_list =~ /(\n|;)/
110
+ Kernel.warn("#{directive} contains a #{$1} in #{minified_source_list.inspect} which will raise an error in future versions. It has been replaced with a blank space.")
111
+ end
112
+
113
+ escaped_source_list = minified_source_list.gsub(/[\n;]/, " ")
114
+ [symbol_to_hyphen_case(directive), escaped_source_list].join(" ").strip
110
115
  end
111
116
  end
112
117
 
@@ -38,8 +38,12 @@ module SecureHeaders
38
38
  @sandbox = nil
39
39
  @script_nonce = nil
40
40
  @script_src = nil
41
+ @script_src_elem = nil
42
+ @script_src_attr = nil
41
43
  @style_nonce = nil
42
44
  @style_src = nil
45
+ @style_src_elem = nil
46
+ @style_src_attr = nil
43
47
  @worker_src = nil
44
48
  @upgrade_insecure_requests = nil
45
49
  @disable_nonce_backwards_compatibility = nil
@@ -94,12 +94,14 @@ module SecureHeaders
94
94
  "SameSite=Lax"
95
95
  elsif flag_samesite_strict?
96
96
  "SameSite=Strict"
97
+ elsif flag_samesite_none?
98
+ "SameSite=None"
97
99
  end
98
100
  end
99
101
 
100
102
  def flag_samesite?
101
103
  return false if config == OPT_OUT || config[:samesite] == OPT_OUT
102
- flag_samesite_lax? || flag_samesite_strict?
104
+ flag_samesite_lax? || flag_samesite_strict? || flag_samesite_none?
103
105
  end
104
106
 
105
107
  def flag_samesite_lax?
@@ -110,6 +112,10 @@ module SecureHeaders
110
112
  flag_samesite_enforcement?(:strict)
111
113
  end
112
114
 
115
+ def flag_samesite_none?
116
+ flag_samesite_enforcement?(:none)
117
+ end
118
+
113
119
  def flag_samesite_enforcement?(mode)
114
120
  return unless config[:samesite]
115
121
 
@@ -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,7 +91,11 @@ 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
101
  ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0).uniq.sort
@@ -117,7 +125,11 @@ module SecureHeaders
117
125
  PREFETCH_SRC => :source_list,
118
126
  SANDBOX => :sandbox_list,
119
127
  SCRIPT_SRC => :source_list,
128
+ SCRIPT_SRC_ELEM => :source_list,
129
+ SCRIPT_SRC_ATTR => :source_list,
120
130
  STYLE_SRC => :source_list,
131
+ STYLE_SRC_ELEM => :source_list,
132
+ STYLE_SRC_ATTR => :source_list,
121
133
  WORKER_SRC => :source_list,
122
134
  UPGRADE_INSECURE_REQUESTS => :boolean,
123
135
  }.freeze
@@ -43,10 +43,12 @@ module SecureHeaders
43
43
 
44
44
  # when configuring with booleans, only one enforcement is permitted
45
45
  def validate_samesite_boolean_config!
46
- if config[:samesite].key?(:lax) && config[:samesite][:lax].is_a?(TrueClass) && config[:samesite].key?(:strict)
47
- raise CookiesConfigError.new("samesite cookie config is invalid, combination use of booleans and Hash to configure lax and strict enforcement is not permitted.")
48
- elsif config[:samesite].key?(:strict) && config[:samesite][:strict].is_a?(TrueClass) && config[:samesite].key?(:lax)
49
- raise CookiesConfigError.new("samesite cookie config is invalid, combination use of booleans and Hash to configure lax and strict enforcement is not permitted.")
46
+ if config[:samesite].key?(:lax) && config[:samesite][:lax].is_a?(TrueClass) && (config[:samesite].key?(:strict) || config[:samesite].key?(:none))
47
+ raise CookiesConfigError.new("samesite cookie config is invalid, combination use of booleans and Hash to configure lax with strict or no enforcement is not permitted.")
48
+ elsif config[:samesite].key?(:strict) && config[:samesite][:strict].is_a?(TrueClass) && (config[:samesite].key?(:lax) || config[:samesite].key?(:none))
49
+ raise CookiesConfigError.new("samesite cookie config is invalid, combination use of booleans and Hash to configure strict with lax or no enforcement is not permitted.")
50
+ elsif config[:samesite].key?(:none) && config[:samesite][:none].is_a?(TrueClass) && (config[:samesite].key?(:lax) || config[:samesite].key?(:strict))
51
+ raise CookiesConfigError.new("samesite cookie config is invalid, combination use of booleans and Hash to configure no enforcement with lax or strict is not permitted.")
50
52
  end
51
53
  end
52
54
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecureHeaders
4
- VERSION = "6.1.1"
4
+ VERSION = "6.3.2"
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.
@@ -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|
@@ -28,6 +28,16 @@ module SecureHeaders
28
28
  expect(ContentSecurityPolicy.new.value).to eq("default-src https:; form-action 'self'; img-src https: data: 'self'; object-src 'none'; script-src https:; style-src 'self' 'unsafe-inline' https:")
29
29
  end
30
30
 
31
+ it "deprecates and escapes semicolons in directive source lists" do
32
+ expect(Kernel).to receive(:warn).with(%(frame_ancestors contains a ; in "google.com;script-src *;.;" which will raise an error in future versions. It has been replaced with a blank space.))
33
+ expect(ContentSecurityPolicy.new(frame_ancestors: %w(https://google.com;script-src https://*;.;)).value).to eq("frame-ancestors google.com script-src * .")
34
+ end
35
+
36
+ it "deprecates and escapes semicolons in directive source lists" do
37
+ expect(Kernel).to receive(:warn).with(%(frame_ancestors contains a \n in "\\nfoo.com\\nhacked" which will raise an error in future versions. It has been replaced with a blank space.))
38
+ expect(ContentSecurityPolicy.new(frame_ancestors: ["\nfoo.com\nhacked"]).value).to eq("frame-ancestors foo.com hacked")
39
+ end
40
+
31
41
  it "discards 'none' values if any other source expressions are present" do
32
42
  csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
33
43
  expect(csp.value).not_to include("'none'")
@@ -150,6 +160,26 @@ module SecureHeaders
150
160
  csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456, disable_nonce_backwards_compatibility: true })
151
161
  expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
152
162
  end
163
+
164
+ it "supports script-src-elem directive" do
165
+ csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_elem: %w('self')})
166
+ expect(csp.value).to eq("script-src 'self'; script-src-elem 'self'")
167
+ end
168
+
169
+ it "supports script-src-attr directive" do
170
+ csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_attr: %w('self')})
171
+ expect(csp.value).to eq("script-src 'self'; script-src-attr 'self'")
172
+ end
173
+
174
+ it "supports style-src-elem directive" do
175
+ csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_elem: %w('self')})
176
+ expect(csp.value).to eq("style-src 'self'; style-src-elem 'self'")
177
+ end
178
+
179
+ it "supports style-src-attr directive" do
180
+ csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')})
181
+ expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'")
182
+ end
153
183
  end
154
184
  end
155
185
  end
@@ -68,29 +68,21 @@ module SecureHeaders
68
68
  end
69
69
 
70
70
  context "SameSite cookies" do
71
- it "flags SameSite=Lax" do
72
- cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
73
- expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
74
- end
75
-
76
- it "flags SameSite=Lax when configured with a boolean" do
77
- cookie = Cookie.new(raw_cookie, samesite: { lax: true}, secure: OPT_OUT, httponly: OPT_OUT)
78
- expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
79
- end
80
-
81
- it "does not flag cookies as SameSite=Lax when excluded" do
82
- cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
83
- expect(cookie.to_s).to eq("_session=thisisatest")
84
- end
71
+ %w(None Lax Strict).each do |flag|
72
+ it "flags SameSite=#{flag}" do
73
+ cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
74
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
75
+ end
85
76
 
86
- it "flags SameSite=Strict" do
87
- cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
88
- expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
89
- end
77
+ it "flags SameSite=#{flag} when configured with a boolean" do
78
+ cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => true}, secure: OPT_OUT, httponly: OPT_OUT)
79
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
80
+ end
90
81
 
91
- it "does not flag cookies as SameSite=Strict when excluded" do
92
- cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] }}, secure: OPT_OUT, httponly: OPT_OUT)
93
- expect(cookie.to_s).to eq("_session=thisisatest")
82
+ it "does not flag cookies as SameSite=#{flag} when excluded" do
83
+ cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
84
+ expect(cookie.to_s).to eq("_session=thisisatest")
85
+ end
94
86
  end
95
87
 
96
88
  it "flags SameSite=Strict when configured with a boolean" do
@@ -149,10 +141,15 @@ module SecureHeaders
149
141
  end.to raise_error(CookiesConfigError)
150
142
  end
151
143
 
152
- it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
153
- expect do
154
- Cookie.validate_config!(samesite: { lax: true, strict: true})
155
- end.to raise_error(CookiesConfigError)
144
+ cookie_options = %i(none lax strict)
145
+ cookie_options.each do |flag|
146
+ (cookie_options - [flag]).each do |other_flag|
147
+ it "raises an exception when SameSite #{flag} and #{other_flag} enforcement modes are configured with booleans" do
148
+ expect do
149
+ Cookie.validate_config!(samesite: { flag => true, other_flag => true})
150
+ end.to raise_error(CookiesConfigError)
151
+ end
152
+ end
156
153
  end
157
154
 
158
155
  it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
@@ -49,6 +49,10 @@ module SecureHeaders
49
49
  style_src: %w('unsafe-inline'),
50
50
  upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
51
51
  worker_src: %w(worker.com),
52
+ script_src_elem: %w(example.com),
53
+ script_src_attr: %w(example.com),
54
+ style_src_elem: %w(example.com),
55
+ style_src_attr: %w(example.com),
52
56
 
53
57
  report_uri: %w(https://example.com/uri-directive),
54
58
  }
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.1.1
4
+ version: 6.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Matatall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-26 00:00:00.000000000 Z
11
+ date: 2021-02-09 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/sync.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.1
119
+ rubygems_version: 3.0.3
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.6.1
6
- - 2.5.0
7
- - 2.4.3
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