secure_headers 6.1.0 → 6.3.1

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: 26a6b47b87ec772f94c264ef318bf4c20997b3a541898f80110a1bd26a471753
4
- data.tar.gz: 73e40f805bafe030a82cbe37cdf499acea6c2613cce3c25964db89a9fde2f172
3
+ metadata.gz: 7b136a1c21b128826c37c798c9e20db99b1d5a5c035001ed8289692ed8f0096f
4
+ data.tar.gz: d93c60ba6357a9cd8f64c16b53c9f2843753101e1669fb1bbaea56e549d89466
5
5
  SHA512:
6
- metadata.gz: ed3eb3ba011292b668fd786a22a2c9d93a20067e4391881eafc555f978a7ae3c5da33741f82c908b7cfe4c66cb837f9fc1b2f7360be788d320dab0694fdaeb66
7
- data.tar.gz: df5800647d9dc5462d51529461d85374831bdc1fe0755949d4ec9764f69810c7e5456be1095f25d4b34bb2ec2c3c0773243d2ce4ebaf4c11bace6ea035201947
6
+ metadata.gz: 13083c3da3a4f68d445be6012a1fa6e37c052e6ac6bdc457d379d725383142c8698f91c2d1a6fc75f13bfad52298e291131e9828bb6f6a1fc6b8cba9bc3d5892
7
+ data.tar.gz: 92214a6b589ba640e504e58f07b051351a7eea7bce87701730101a68a29045fe81c90d79600f00a36ee7bf3e4c5c7c4071ad74b01bab5e40c53751858bc198d7
@@ -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
@@ -1,3 +1,4 @@
1
1
  inherit_gem:
2
2
  rubocop-github:
3
3
  - config/default.yml
4
+ require: rubocop-performance
@@ -1 +1 @@
1
- 2.6.1
1
+ 2.6.6
@@ -1,7 +1,29 @@
1
+ ## 6.3.1
2
+
3
+ Fixes deprecation warnings when running under ruby 2.7
4
+
5
+ ## 6.3.0
6
+
7
+ Fixes newline injection issue
8
+
9
+ ## 6.2.0
10
+
11
+ Fixes semicolon injection issue reported by @mvgijssel see https://github.com/twitter/secure_headers/issues/418
12
+
13
+ ## 6.1.2
14
+
15
+ 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.
16
+
17
+ ## 6.1.1
18
+
19
+ Adds the ability to disable the automatically-appended `'unsafe-inline'` value when nonces are used #404 (@will)
20
+
1
21
  ## 6.1
2
22
 
3
23
  Adds support for navigate-to, prefetch-src, and require-sri-for #395
4
24
 
25
+ NOTE: this version is a breaking change due to the removal of HPKP. Remove the HPKP config, the standard is dead. Apologies for not doing a proper deprecate/major rev cycle :pray:
26
+
5
27
  ## 6.0
6
28
 
7
29
  - See the [upgrading to 6.0](docs/upgrading-to-6-0.md) guide for the breaking changes.
@@ -354,7 +376,7 @@ Adds `upgrade-insecure-requests` support for requests from Firefox and Chrome (a
354
376
 
355
377
  ## 3.0.0
356
378
 
357
- 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.
379
+ 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.
358
380
 
359
381
  ## 2.5.1 - 2016-02-16 18:11:11 UTC - Remove noisy deprecation warning
360
382
 
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'),
@@ -116,7 +117,48 @@ SecureHeaders::Configuration.override(:api) do |config|
116
117
  end
117
118
  ```
118
119
 
119
- However, I would consider these headers anyways depending on your load and bandwidth requirements.
120
+ However, I would consider these headers anyways depending on your load and bandwidth requirements.
121
+
122
+ ## Acknowledgements
123
+
124
+ 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.
125
+
126
+ Contributors include:
127
+ * Neil Matatall @oreoshake
128
+ * Chris Aniszczyk
129
+ * Artur Dryomov
130
+ * Bjørn Mæland
131
+ * Arthur Chiu
132
+ * Jonathan Viney
133
+ * Jeffrey Horn
134
+ * David Collazo
135
+ * Brendon Murphy
136
+ * William Makley
137
+ * Reed Loden
138
+ * Noah Kantrowitz
139
+ * Wyatt Anderson
140
+ * Salimane Adjao Moustapha
141
+ * Francois Chagnon
142
+ * Jeff Hodges
143
+ * Ian Melven
144
+ * Darío Javier Cravero
145
+ * Logan Hasson
146
+ * Raul E Rangel
147
+ * Steve Agalloco
148
+ * Nate Collings
149
+ * Josh Kalderimis
150
+ * Alex Kwiatkowski
151
+ * Julich Mera
152
+ * Jesse Storimer
153
+ * Tom Daniels
154
+ * Kolja Dummann
155
+ * Jean-Philippe Doyle
156
+ * Blake Hitchcock
157
+ * vanderhoorn
158
+ * orthographic-pedant
159
+ * Narsimham Chelluri
160
+
161
+ If you've made a contribution and see your name missing from the list, make a PR and add it!
120
162
 
121
163
  ## Similar libraries
122
164
 
@@ -131,9 +173,3 @@ However, I would consider these headers anyways depending on your load and bandw
131
173
  * Dropwizard [dropwizard-web-security](https://github.com/palantir/dropwizard-web-security)
132
174
  * Ember.js [ember-cli-content-security-policy](https://github.com/rwjblue/ember-cli-content-security-policy/)
133
175
  * 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
@@ -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|
@@ -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
 
@@ -179,7 +184,8 @@ module SecureHeaders
179
184
  # unsafe-inline, this is more concise.
180
185
  def append_nonce(source_list, nonce)
181
186
  if nonce
182
- source_list.push("'nonce-#{nonce}'", UNSAFE_INLINE)
187
+ source_list.push("'nonce-#{nonce}'")
188
+ source_list.push(UNSAFE_INLINE) unless @config[:disable_nonce_backwards_compatibility]
183
189
  end
184
190
 
185
191
  source_list
@@ -42,6 +42,7 @@ module SecureHeaders
42
42
  @style_src = nil
43
43
  @worker_src = nil
44
44
  @upgrade_insecure_requests = nil
45
+ @disable_nonce_backwards_compatibility = nil
45
46
 
46
47
  from_hash(hash)
47
48
  end
@@ -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
 
@@ -153,7 +153,8 @@ module SecureHeaders
153
153
 
154
154
  META_CONFIGS = [
155
155
  :report_only,
156
- :preserve_schemes
156
+ :preserve_schemes,
157
+ :disable_nonce_backwards_compatibility
157
158
  ].freeze
158
159
 
159
160
  NONCES = [
@@ -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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SecureHeaders
2
- VERSION = "6.1.0"
4
+ VERSION = "6.3.1"
3
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.
@@ -1,9 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path("../lib", __FILE__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require "secure_headers/version"
4
6
 
5
- # -*- encoding: utf-8 -*-
6
- # frozen_string_literal: true
7
7
  Gem::Specification.new do |gem|
8
8
  gem.name = "secure_headers"
9
9
  gem.version = SecureHeaders::VERSION
@@ -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'")
@@ -145,6 +155,11 @@ module SecureHeaders
145
155
  csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456})
146
156
  expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456' 'unsafe-inline'")
147
157
  end
158
+
159
+ it "supports strict-dynamic and opting out of the appended 'unsafe-inline'" do
160
+ csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456, disable_nonce_backwards_compatibility: true })
161
+ expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
162
+ end
148
163
  end
149
164
  end
150
165
  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
@@ -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.0
4
+ version: 6.3.1
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-02-23 00:00:00.000000000 Z
11
+ date: 2020-06-26 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,
@@ -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