jekyll-content-security-policy-generator 1.0.0 → 1.5.0

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: 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: []