jekyll-content-security-policy-generator 1.0.0 → 1.5.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: 28a188afee0ba93d3d3bd236bd98488a704b272641f46ec67b8dbedd5c3fd1e4
4
- data.tar.gz: 54139e64294dcdf090b19722c19599d8016f0947ef3ddb5125c6ca6d962f936e
3
+ metadata.gz: ec29dad0ececf9db2097db0a584d176f845047a4e680e64aee0e574606de95e9
4
+ data.tar.gz: 57d57a9c9f96944d3e252161cc167959a2d943debe285339f26f4308e99c6b9f
5
5
  SHA512:
6
- metadata.gz: 8e79e6caadd31fd4db7b47369f9d4a509cc760d869a5d317d55ad2816b58ff956c176827185ffcaa8a24ff41695f7dc02ecfd6a95c9c2285b256356105afc182
7
- data.tar.gz: ddf59999929ba6ee97fc77082674a28444e5f896d35ddc206f29d275b3120bef193097ff4be6d07f0d4e873bdf2036460a07844cb1d59f0b21468ed6b7c6b88e
6
+ metadata.gz: edaf01605a12f29c012a795219467a81d7c785380c205aac2e78e0b3cb8ac1d4a21e34112e0fb94113ba77e011ce412c5cee90dfeac9fc49c73cf83eb5d45ff4
7
+ data.tar.gz: 8ff8a12e60688ba77ae30dbd5c0844f94f8dba0786574549e656f5f3f280bfc9d14f6efc7b6f19e2b027520880a40a5e745886ca493e8444fffb068142072988
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ _site
2
+ .sass-cache
3
+ .jekyll-metadata
4
+ *.gem
5
+ .idea/
6
+ .DS_Store
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 strongscot <mail@strongscot.com> (https://strongscot.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,36 @@
1
+ CWD := $(shell pwd)
2
+
3
+ .PHONY: all
4
+ all: build
5
+
6
+ .PHONY: start
7
+ start:
8
+ @bundle exec jekyll serve --verbose
9
+
10
+ .PHONY: build
11
+ build: clean
12
+ @gem build *.gemspec
13
+ @echo ::: BUILD :::
14
+
15
+ .PHONY: install
16
+ install: deps
17
+ -@rm -f Gemfile.lock &>/dev/null || true
18
+ @bundle install
19
+ @echo ::: INSTALL :::
20
+
21
+ .PHONY: push
22
+ push: build
23
+ @gem push *.gem
24
+ @echo ::: PUSH :::
25
+
26
+ .PHONY: clean
27
+ clean:
28
+ -@rm -rf *.gem &>/dev/null || true
29
+ @echo ::: CLEAN :::
30
+
31
+ .PHONY: deps
32
+ deps: bundle
33
+ @echo ::: DEPS :::
34
+ .PHONY: bundle
35
+ bundle:
36
+ @if ! o=$$(which bundle); then gem install bundle jekyll; fi
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # jekyll-content-security-policy-generator Plugin
2
+
3
+ This Jekyll plugin automatically builds an HTML content-security-policy for a Jekyll site. The plugin
4
+ will scan ```.html``` files generated by Jekyll and attempt to locate images, styles, scripts, frames etc and build a
5
+ content security policy HTML meta tag. The script will also generate SHA256 hashes for inline scripts and styles. If
6
+ the script finds elements with style attributes ```<div style="color: red"></div>```, the script will extract the style
7
+ information and build a style element to which will also pass through the content security policy generation.
8
+
9
+ ## Goal
10
+
11
+ To speed up development of Jekyll based sites whilst also helping to generate secure HTMl files protected from XSS.
12
+
13
+ ## Features
14
+
15
+ * Scans for ```.html``` files generated by Jekyll.
16
+ * Finds inline scripts such as ```<script>alert("Hello World!");</script>``` and generates an SHA256 hash.
17
+ * Finds inline styles such as ```<style>.hello { color: "red"; }</style>``` and generates an SHA256 hash.
18
+ * Creates or reuses an HTTP meta tag for the content security policy.
19
+ * Finds all images, styles, scripts and frames with external URLs and builds CSP.
20
+ * Converts style attributes into ```<style>``` elements.
21
+
22
+ ## Installation
23
+
24
+ Install the gem:
25
+
26
+ ```gem install jekyll-content-security-policy-generator```
27
+
28
+ Then add this to your _config.yml:
29
+
30
+ ```
31
+ plugins:
32
+ - jekyll-content-security-policy-generator
33
+ ```
34
+
35
+ ## Support
36
+
37
+ https://strongscot.com
@@ -0,0 +1,21 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "jekyll-content-security-policy-generator/version"
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "jekyll-content-security-policy-generator"
6
+ spec.summary = "Helps generate a content security policy."
7
+ spec.description = "Helps generate a content security policy. Locates inline scripts, images, frames etc."
8
+ spec.version = JekyllContentSecurityPolicyGenerator::VERSION
9
+ spec.authors = ["strongscot"]
10
+ spec.email = ["mail@strongscot.com"]
11
+ spec.homepage = "https://github.com/strongscot/jekyll-content-security-policy-generator"
12
+ spec.licenses = ["MIT"]
13
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r!^(test|spec|features)/!) }
14
+ spec.require_paths = ["lib"]
15
+ spec.add_dependency "jekyll"
16
+ spec.add_dependency "nokogiri"
17
+ spec.add_dependency "digest"
18
+ spec.add_development_dependency "rake"
19
+ spec.add_development_dependency "rspec"
20
+ spec.add_development_dependency "rubocop"
21
+ end
@@ -0,0 +1,5 @@
1
+ require_relative "jekyll-content-security-policy-generator/version"
2
+ require_relative "jekyll-content-security-policy-generator/hook"
3
+
4
+ module JekyllContentSecurityPolicyGenerator
5
+ end
@@ -0,0 +1,268 @@
1
+ require 'jekyll'
2
+ require 'nokogiri'
3
+ require 'digest'
4
+ require 'open-uri'
5
+
6
+ ##
7
+ # Provides the ability to generate a content security policy for inline scripts and styles.
8
+ # Will reuse an existing CSP or generate a new one and insert in HEAD.
9
+ module Jekyll
10
+
11
+ module JekyllContentSecurityPolicyGenerator
12
+
13
+ ##
14
+ # Provides the ability to generate a content security policy for inline scripts and styles.
15
+ # Will reuse an existing CSP or generate a new one and insert in HEAD.
16
+ class ContentSecurityPolicyGenerator
17
+ def initialize(document_html)
18
+ @document_html = document_html
19
+ @nokogiri = Nokogiri::HTML(document_html)
20
+
21
+ @csp_frame_src = ['\'self\'']
22
+ @csp_image_src = ['\'self\'']
23
+ @csp_style_src = ['\'self\'']
24
+ @csp_script_src = ['\'self\'']
25
+ @csp_unknown = []
26
+ end
27
+
28
+ ##
29
+ # Creates an HTML content security policy meta tag.
30
+ def generate_convert_security_policy_meta_tag
31
+ meta_content = ""
32
+
33
+ if @csp_frame_src.length > 0
34
+ meta_content += "frame-src " + @csp_frame_src.join(' ') + '; '
35
+ end
36
+
37
+ if @csp_image_src.length > 0
38
+ meta_content += "img-src " + @csp_image_src.join(' ') + '; '
39
+ end
40
+
41
+ if @csp_style_src.length > 0
42
+ meta_content += "style-src " + @csp_style_src.join(' ') + '; '
43
+ end
44
+
45
+ if @csp_script_src.length > 0
46
+ meta_content += "script-src " + @csp_script_src.join(' ') + '; '
47
+ end
48
+
49
+ if @csp_unknown.length > 0
50
+ @csp_unknown.each do |find|
51
+ find_name = find[0]
52
+ find = find.drop(1)
53
+ meta_content += find_name + " " + find.join(' ') + '; '
54
+ end
55
+ end
56
+
57
+ if @nokogiri.at("head")
58
+ Jekyll.logger.info "Generated content security policy, inserted in HEAD."
59
+ @nokogiri.at("head") << "<meta http-equiv=\"Content-Security-Policy\" content=\"" + meta_content + "\">"
60
+ elsif @nokogiri.at("body")
61
+ Jekyll.logger.info "Generated content security policy, inserted in BODY."
62
+ @nokogiri.at("body") << "<meta http-equiv=\"Content-Security-Policy\" content=\"" + meta_content + "\">"
63
+ else
64
+ Jekyll.logger.error "Generated content security policy but found no-where to insert it."
65
+ end
66
+
67
+ end
68
+
69
+ ##
70
+ # Parse an existing content security policy meta tag
71
+ def parse_existing_meta_element()
72
+ csp = @nokogiri.at('meta[http-equiv="Content-Security-Policy"]')
73
+
74
+ if csp
75
+ content = csp.attr('content')
76
+ content = content.strip! || content
77
+ policies = content.split(';')
78
+
79
+ policies.each do |policy|
80
+ policy = policy.strip! || policy
81
+
82
+ if policy.include? ' '
83
+ policy_parts = policy.split(' ')
84
+
85
+ if policy_parts[0] == 'script-src'
86
+ @csp_script_src.concat(policy_parts.drop(1))
87
+ @csp_script_src = @csp_script_src.uniq
88
+ elsif policy_parts[0] == 'style-src'
89
+ @csp_style_src.concat(policy_parts.drop(1))
90
+ @csp_style_src = @csp_style_src.uniq
91
+ elsif policy_parts[0] == 'image-src'
92
+ @csp_image_src.concat(policy_parts.drop(1))
93
+ @csp_image_src = @csp_image_src.uniq
94
+ elsif policy_parts[0] == 'frame-src'
95
+ @csp_frame_src.concat(policy_parts.drop(1))
96
+ @csp_frame_src = @csp_frame_src.uniq
97
+ else
98
+ @csp_unknown.concat([policy_parts])
99
+ end
100
+
101
+ else
102
+ Jekyll.logger.warn "Incorrect existing content security policy meta tag found, skipping."
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ ##
109
+ # This function converts elements with style="color:red" attributes into inline styles
110
+ def convert_all_inline_styles_attributes
111
+ @nokogiri.css('*').each do |find|
112
+ find_src = find.attr('style')
113
+
114
+ if find_src
115
+ if find.attr('id')
116
+ element_id = find.attr('id')
117
+ else
118
+ element_id = Digest::MD5.hexdigest find_src + "#{Random.rand(11)}"
119
+ find["id"] = element_id
120
+ end
121
+
122
+ new_element = "<style>#" + element_id + " { " + find_src + " } </style>"
123
+ find.remove_attribute("style")
124
+
125
+ if @nokogiri.at('head')
126
+ @nokogiri.at('head') << new_element
127
+ Jekyll.logger.info'Converting style attribute to inline style, inserted into HEAD.'
128
+ else
129
+ if @nokogiri.at('body')
130
+ @nokogiri.at('body') << new_element
131
+ Jekyll.logger.info'Converting style attribute to inline style, inserted into BODY.'
132
+ else
133
+ Jekyll.logger.warn'Unable to convert style attribute to inline style, no HEAD or BODY found.'
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ ##
141
+ # Find all images
142
+ def find_images
143
+ @nokogiri.css('img').each do |find|
144
+ find_src = find.attr('src')
145
+
146
+ if find_src.start_with?('http', 'https')
147
+ @csp_image_src.push find_src.match(/(.*\/)+(.*$)/)[1]
148
+ end
149
+ end
150
+ end
151
+
152
+ ##
153
+ # Find all scripts
154
+ def find_scripts
155
+ @nokogiri.css('script').each do |find|
156
+ if find.attr('src')
157
+ find_src = find.attr('src')
158
+
159
+ if find_src.start_with?('http', 'https')
160
+ @csp_script_src.push find_src.match(/(.*\/)+(.*$)/)[1]
161
+ end
162
+
163
+ else
164
+ @csp_script_src.push self.generate_sha256_content_hash find.content
165
+ end
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Find all stylesheets
171
+ def find_styles
172
+ @nokogiri.css('style').each do |find|
173
+ if find.attr('src')
174
+ find_src = find.attr('src')
175
+
176
+ if find_src.start_with?('http', 'https')
177
+ @csp_style_src.push find_src.match(/(.*\/)+(.*$)/)[1]
178
+ end
179
+
180
+ else
181
+ @csp_style_src.push self.generate_sha256_content_hash find.content
182
+ end
183
+ end
184
+ end
185
+
186
+ ##
187
+ # Find all iframes
188
+ def find_iframes
189
+ @nokogiri.css('iframe').each do |find|
190
+ find_src = find.attr('src')
191
+
192
+ if find_src.start_with?('http', 'https')
193
+ @csp_frame_src.push find_src.match(/(.*\/)+(.*$)/)[1]
194
+ end
195
+ end
196
+ end
197
+
198
+ ##
199
+ # Generate a content hash
200
+ def generate_sha256_content_hash(content)
201
+ hash = Digest::SHA2.base64digest content
202
+ "'sha256-#{hash}'"
203
+ end
204
+
205
+ ##
206
+ # Builds an HTML meta tag based on the found inline scripts and style hashes
207
+ def run
208
+ self.parse_existing_meta_element
209
+
210
+ self.convert_all_inline_styles_attributes
211
+
212
+ # Find elements in document
213
+ self.find_images
214
+ self.find_styles
215
+ self.find_scripts
216
+ self.find_iframes
217
+
218
+ self.generate_convert_security_policy_meta_tag
219
+
220
+ @nokogiri.to_html
221
+ end
222
+ end
223
+
224
+ ##
225
+ # Write the file contents back.
226
+ def write_file_contents(dest, content)
227
+ FileUtils.mkdir_p(File.dirname(dest))
228
+ File.open(dest, 'w') do |f|
229
+ f.write(content)
230
+ end
231
+ end
232
+
233
+ ##
234
+ # Write document contents
235
+ def write(dest)
236
+ dest_path = destination(dest)
237
+ if File.extname(dest_path) == ".html"
238
+ content_security_policy_generator = ContentSecurityPolicyGenerator.new output
239
+ output = content_security_policy_generator.run
240
+ end
241
+
242
+ write_file_contents(dest_path, output)
243
+ end
244
+
245
+ end
246
+
247
+ class Document
248
+ include JekyllContentSecurityPolicyGenerator
249
+
250
+ ##
251
+ # Write document contents
252
+ def write(dest)
253
+ super dest
254
+ trigger_hooks(:post_write)
255
+ end
256
+ end
257
+
258
+ class Page
259
+ include JekyllContentSecurityPolicyGenerator
260
+
261
+ ##
262
+ # Write page contents
263
+ def write(dest)
264
+ super dest
265
+ Jekyll::Hooks.trigger hook_owner, :post_write, self
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,3 @@
1
+ module JekyllContentSecurityPolicyGenerator
2
+ VERSION = "1.5.0".freeze
3
+ end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-content-security-policy-generator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - strongscot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-05 00:00:00.000000000 Z
11
+ date: 2021-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.0'
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '3.0'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -56,51 +56,60 @@ dependencies:
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '11.0'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '11.0'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '3.5'
75
+ version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '3.5'
82
+ version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '0.52'
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '0.52'
97
- description: Helps generate a content security policy for inline scripts and styles.
96
+ version: '0'
97
+ description: Helps generate a content security policy. Locates inline scripts, images,
98
+ frames etc.
98
99
  email:
99
100
  - mail@strongscot.com
100
101
  executables: []
101
102
  extensions: []
102
103
  extra_rdoc_files: []
103
- files: []
104
+ files:
105
+ - ".gitignore"
106
+ - LICENSE
107
+ - Makefile
108
+ - README.md
109
+ - jekyll-content-security-policy-generator.gemspec
110
+ - lib/jekyll-content-security-policy-generator.rb
111
+ - lib/jekyll-content-security-policy-generator/hook.rb
112
+ - lib/jekyll-content-security-policy-generator/version.rb
104
113
  homepage: https://github.com/strongscot/jekyll-content-security-policy-generator
105
114
  licenses:
106
115
  - MIT
@@ -123,5 +132,5 @@ requirements: []
123
132
  rubygems_version: 3.0.3
124
133
  signing_key:
125
134
  specification_version: 4
126
- summary: Helps generate a content security policy for inline scripts and styles.
135
+ summary: Helps generate a content security policy.
127
136
  test_files: []