secure_headers 6.3.1 → 6.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +2 -2
- data/.github/workflows/github-release.yml +28 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -1
- data/README.md +7 -4
- data/docs/per_action_configuration.md +2 -4
- data/docs/upgrading-to-6-0.md +3 -3
- data/lib/secure_headers/headers/content_security_policy.rb +2 -1
- data/lib/secure_headers/headers/content_security_policy_config.rb +4 -0
- data/lib/secure_headers/headers/policy_management.rb +13 -1
- data/lib/secure_headers/version.rb +1 -1
- data/lib/tasks/tasks.rake +6 -7
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +25 -0
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +4 -0
- metadata +4 -4
- data/.github/workflows/sync.yml +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3953b8d0c4ce0a01012d4d6475ad94c60ce4b06b9d93994ef14cc2474ced6177
|
4
|
+
data.tar.gz: 3aa96804cacf26d4a275b19e870755313a3afd055b598014749d0338e4f0f4af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b04fd60ff28519273f29d335b81b44ebfe938d4e97d82d31b69d8596dc84e38c812573248e7b70bb142fecebab357c7f34f9f10934edaf354e3174915220580f
|
7
|
+
data.tar.gz: 10748a3ff12365fffe42828fe324e0bfbb3f146635b095a9e8a13b5a57b5bdfe17a5463df3b5009d8591dbc88494c45036a12dd061fc6b8e921c4c99a0d523ef
|
data/.github/workflows/build.yml
CHANGED
@@ -7,12 +7,12 @@ jobs:
|
|
7
7
|
runs-on: ubuntu-latest
|
8
8
|
strategy:
|
9
9
|
matrix:
|
10
|
-
ruby: [ '2.
|
10
|
+
ruby: [ '2.5', '2.6', '2.7', '3.0' ]
|
11
11
|
|
12
12
|
steps:
|
13
13
|
- uses: actions/checkout@v2
|
14
14
|
- name: Set up Ruby ${{ matrix.ruby }}
|
15
|
-
uses:
|
15
|
+
uses: ruby/setup-ruby@v1
|
16
16
|
with:
|
17
17
|
ruby-version: ${{ matrix.ruby }}
|
18
18
|
- name: Build and test with Rake
|
@@ -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/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 6.3.4
|
2
|
+
|
3
|
+
- CSP: Do not deduplicate alternate schema source expressions (@keithamus): https://github.com/github/secure_headers/pull/478
|
4
|
+
|
5
|
+
## 6.3.3
|
6
|
+
|
7
|
+
Fix hash generation for indented helper methods (@rahearn)
|
8
|
+
|
9
|
+
## 6.3.2
|
10
|
+
|
11
|
+
Add support for style-src-attr, style-src-elem, script-src-attr, and script-src-elem directives (@ggalmazor)
|
12
|
+
|
1
13
|
## 6.3.1
|
2
14
|
|
3
15
|
Fixes deprecation warnings when running under ruby 2.7
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
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](
|
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
|
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)
|
@@ -165,9 +169,8 @@ If you've made a contribution and see your name missing from the list, make a PR
|
|
165
169
|
* Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
|
166
170
|
* Node.js (express) [helmet](https://github.com/helmetjs/helmet) and [hood](https://github.com/seanmonstar/hood)
|
167
171
|
* Node.js (hapi) [blankie](https://github.com/nlf/blankie)
|
168
|
-
* J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
|
169
172
|
* ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
|
170
|
-
* 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)
|
171
174
|
* Go - [secureheader](https://github.com/kr/secureheader)
|
172
175
|
* Elixir [secure_headers](https://github.com/anotherhale/secure_headers)
|
173
176
|
* Dropwizard [dropwizard-web-security](https://github.com/palantir/dropwizard-web-security)
|
@@ -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
|
|
data/docs/upgrading-to-6-0.md
CHANGED
@@ -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
|
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
|
-
##
|
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
|
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).
|
@@ -155,9 +155,10 @@ module SecureHeaders
|
|
155
155
|
wild_sources = sources.select { |source| source =~ STAR_REGEXP }
|
156
156
|
|
157
157
|
if wild_sources.any?
|
158
|
+
schemes = sources.map { |source| [source, URI(source).scheme] }.to_h
|
158
159
|
sources.reject do |source|
|
159
160
|
!wild_sources.include?(source) &&
|
160
|
-
wild_sources.any? { |pattern| File.fnmatch(pattern, source) }
|
161
|
+
wild_sources.any? { |pattern| schemes[pattern] == schemes[source] && File.fnmatch(pattern, source) }
|
161
162
|
end
|
162
163
|
else
|
163
164
|
sources
|
@@ -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
|
@@ -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
|
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
|
-
|
42
|
-
|
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
|
-
|
52
|
-
|
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
|
@@ -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")
|
@@ -160,6 +165,26 @@ module SecureHeaders
|
|
160
165
|
csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456, disable_nonce_backwards_compatibility: true })
|
161
166
|
expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
|
162
167
|
end
|
168
|
+
|
169
|
+
it "supports script-src-elem directive" do
|
170
|
+
csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_elem: %w('self')})
|
171
|
+
expect(csp.value).to eq("script-src 'self'; script-src-elem 'self'")
|
172
|
+
end
|
173
|
+
|
174
|
+
it "supports script-src-attr directive" do
|
175
|
+
csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_attr: %w('self')})
|
176
|
+
expect(csp.value).to eq("script-src 'self'; script-src-attr 'self'")
|
177
|
+
end
|
178
|
+
|
179
|
+
it "supports style-src-elem directive" do
|
180
|
+
csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_elem: %w('self')})
|
181
|
+
expect(csp.value).to eq("style-src 'self'; style-src-elem 'self'")
|
182
|
+
end
|
183
|
+
|
184
|
+
it "supports style-src-attr directive" do
|
185
|
+
csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')})
|
186
|
+
expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'")
|
187
|
+
end
|
163
188
|
end
|
164
189
|
end
|
165
190
|
end
|
@@ -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
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secure_headers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.3.
|
4
|
+
version: 6.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Matatall
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -34,7 +34,7 @@ files:
|
|
34
34
|
- ".github/ISSUE_TEMPLATE.md"
|
35
35
|
- ".github/PULL_REQUEST_TEMPLATE.md"
|
36
36
|
- ".github/workflows/build.yml"
|
37
|
-
- ".github/workflows/
|
37
|
+
- ".github/workflows/github-release.yml"
|
38
38
|
- ".gitignore"
|
39
39
|
- ".rspec"
|
40
40
|
- ".rubocop.yml"
|
@@ -116,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '0'
|
118
118
|
requirements: []
|
119
|
-
rubygems_version: 3.0.3
|
119
|
+
rubygems_version: 3.0.3.1
|
120
120
|
signing_key:
|
121
121
|
specification_version: 4
|
122
122
|
summary: Add easily configured security headers to responses including content-security-policy,
|
data/.github/workflows/sync.yml
DELETED
@@ -1,20 +0,0 @@
|
|
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
|