jekyll_flexible_include 2.0.7 → 2.0.10

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: 38eeb054b62e4c999437208ce95cb7291174167839d344db3df48670954b32ef
4
- data.tar.gz: '0962b93b74c28d6521c655cea29f0646c11051d8f3ddaf690618b9ed91e7276b'
3
+ metadata.gz: d22302901e7a454d82515fd64525479b2932d8856ee572565ea2fae672b6bb28
4
+ data.tar.gz: 22c05d1fb04c976ebfb6a7e005e998bc572a21cb5fce7eed4ac2284c38840c2e
5
5
  SHA512:
6
- metadata.gz: 2e3ec9a8c269bda135c245a54b995abd6f2229658190a5f89e1bad97372e85f4a37af7c7227cbaa8b518f1ae90a6b70ecc3f0f9c881859329fdf241123047ca6
7
- data.tar.gz: b8f6497ee5505ac0f43f28a6c9d0bb639be7b7c2bc712375f8ae6774522ccaae6ecd0108be1bdcb0d72cb1656e0b1a268ffa52d3feb675722295aca6c698961d
6
+ metadata.gz: 04f0e0f7a4ecc3e929a2084311b4c6ee70483256117703a56642c78ab2962b8cfdbc01d3e027dc9fbae188ee8d2adeac202bb0dfa28759ff32fa46262d5c985c
7
+ data.tar.gz: 66b2a6c4383bcc86d5492a0c4155ecbf168e0dedd1c680d005a5f5cfca319b978158c5e71d2027f58109ae9ee02d449fd189044cb4860f793945fd2ce6d7971b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 2.0.10 / 2022-04-15
2
+ * Fixed nil pointer
3
+
4
+ ## 2.0.9 / 2022-04-15
5
+ * Changed how path matching was implemented.
6
+
7
+ ## 2.0.8 / 2022-04-14
8
+ * Added the ability to restrict arbitrary command execution, and specify the allowable directories to read from.
9
+
1
10
  ## 2.0.7 / 2022-04-14
2
11
  * Added `file=` option, so the included file or process is better defined. This option is not required; the file/process can be specified without it as before.
3
12
  * Documented `data-lt-active="false"`.
data/README.md CHANGED
@@ -48,6 +48,37 @@ The following options imply `pre`:
48
48
  * `label` specifies that an automatically generated label be placed above the contents. There is no need to specify this option if `download` or `copy_button` options are provided.
49
49
  * `label="blah blah"` specifies a label for the contents; this value overrides the default label. The value can be enclosed in single or double quotes.
50
50
 
51
+ ### Restricting Directory Access
52
+ By default, `flexible_include` can read from all directories according to the permissions of the user account that launched the `jekyll` process.
53
+ For security-conscience environments, the accessible paths can be restricted.
54
+
55
+ Defining an environment variable called `FLEXIBLE_INCLUDE_PATHS` prior to launching Jekyll will restrict the paths that `flexible_include` will be able to read from.
56
+ This environment variable consists of a colon-delimited set of
57
+ [file and directory glob patterns](https://docs.ruby-lang.org/en/2.7.0/Dir.html#method-c-glob).
58
+ For example, the following restricts access to only the files within:
59
+ 1. The `~/my_dir` directory tree of the account of the user that launched Jekyll.
60
+ 2. The directory tree rooted at `/var/files`.
61
+ 3. The directory tree rooted at the expanded value of the `$work` environment variable.
62
+ ```shell
63
+ export FLEXIBLE_INCLUDE_PATHS='~/.*:$sites/.*:$work/.*'
64
+ ```
65
+ Note that the above matches dot (hidden) files as well as regular files.
66
+ To just match visible files:
67
+ ```shell
68
+ export FLEXIBLE_INCLUDE_PATHS='~/my_dir/**/*:/var/files/**/*:$work/**/*'
69
+ ```
70
+
71
+ #### Note
72
+ The specified directories are traversed when the plugin starts, and the filenames are stored in memory. Directories with lots of files might take a noticable amount to time to enumerate the files.
73
+
74
+
75
+ ### Restricting Arbitrary Processes
76
+ By default, `flexible_include` can execute any command. You can disable that by setting the environment variable `DISABLE_FLEXIBLE_INCLUDE` to any non-empty value.
77
+ ```shell
78
+ export DISABLE_FLEXIBLE_INCLUDE=true
79
+ ```
80
+
81
+ If a potential command execution is intercepted, a big red message will appear on the generated web page that says `Arbitrary command execution denied by DISABLE_FLEXIBLE_INCLUDE value.`, and a red error message will be logged on the console that says something like: `ERROR FlexibleInclude: _posts/2020/2020-10-03-jekyll-plugins.html - Arbitrary command execution denied by DISABLE_FLEXIBLE_INCLUDE value.`
51
82
 
52
83
 
53
84
  ## Installation
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllFlexibleIncludePluginVersion
4
- VERSION = "2.0.7"
4
+ VERSION = "2.0.10"
5
5
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "benchmark"
3
4
  require "jekyll"
4
5
  require "jekyll_plugin_logger"
5
6
  require "securerandom"
@@ -13,6 +14,36 @@ end
13
14
  class FlexibleInclude < Liquid::Tag
14
15
  FlexibleIncludeError = Class.new(Liquid::Error)
15
16
 
17
+ @read_regexes = nil
18
+
19
+ def self.normalize_path(path)
20
+ JekyllTagHelper.expand_env(path)
21
+ .gsub("~", Dir.home)
22
+ end
23
+
24
+ # If FLEXIBLE_INCLUDE_PATHS='~/lib/.*:.*:$WORK/.*'
25
+ # Then @read_regexes will be set to regexes of ["/home/my_user_id/lib/.*", "/pwd/.*", "/work/envar/path/.*"]
26
+ def self.security_check
27
+ @execution_denied = ENV['DISABLE_FLEXIBLE_INCLUDE']
28
+
29
+ unless @read_regexes
30
+ flexible_include_paths = ENV['FLEXIBLE_INCLUDE_PATHS']
31
+ read_paths = normalize_path(flexible_include_paths) if flexible_include_paths
32
+ if read_paths
33
+ @read_regexes = read_paths.split(":").map do |path|
34
+ abs_path = path.start_with?('/') ? path : (Pathname.new(Dir.pwd) + path).to_s
35
+ Regexp.new(abs_path)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.access_allowed(path)
42
+ return true unless @read_regexes
43
+
44
+ @read_regexes.find { |regex| regex.match(normalize_path(path)) }
45
+ end
46
+
16
47
  # @param tag_name [String] the name of the tag, which we already know.
17
48
  # @param markup [String] the arguments from the tag, as a single string.
18
49
  # @param parse_context [Liquid::ParseContext] hash that stores Liquid options.
@@ -24,6 +55,8 @@ class FlexibleInclude < Liquid::Tag
24
55
  super
25
56
  @logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
26
57
  @helper = JekyllTagHelper.new(tag_name, markup, @logger)
58
+
59
+ self.class.security_check
27
60
  end
28
61
 
29
62
  # @param liquid_context [Liquid::Context]
@@ -36,6 +69,7 @@ class FlexibleInclude < Liquid::Tag
36
69
  @label_specified = @label
37
70
  @copy_button = @helper.parameter_specified? "copyButton"
38
71
  @pre = @copy_button || @dark || @download || @label_specified || @helper.parameter_specified?("pre") # Download or label implies pre
72
+
39
73
  filename = @helper.parameter_specified? "file"
40
74
  filename ||= @helper.params.first # Do this after all options have been checked for
41
75
  @label ||= filename
@@ -48,13 +82,19 @@ class FlexibleInclude < Liquid::Tag
48
82
  path = JekyllTagHelper.expand_env(filename)
49
83
  case path
50
84
  when /\A\// # Absolute path
85
+ return denied("Access to #{path} denied by FLEXIBLE_INCLUDE_PATHS value.") unless self.class.access_allowed(path)
86
+
51
87
  @logger.debug { "Absolute path=#{path}, filename=#{filename}" }
52
88
  when /\A~/ # Relative path to user's home directory
89
+ return denied("Access to #{path} denied by FLEXIBLE_INCLUDE_PATHS value.") unless self.class.access_allowed(path)
90
+
53
91
  @logger.debug { "User home start filename=#{filename}, path=#{path}" }
54
92
  filename.slice! "~/"
55
93
  path = File.join(ENV['HOME'], filename)
56
94
  @logger.debug { "User home end filename=#{filename}, path=#{path}" }
57
95
  when /\A!/ # Run command and return response
96
+ return denied("Arbitrary command execution denied by DISABLE_FLEXIBLE_INCLUDE value.") if @execution_denied
97
+
58
98
  filename = JekyllTagHelper.remove_quotes(@helper.argv.first) if @helper.argv.first
59
99
  filename.slice! "!"
60
100
  contents = run(filename)
@@ -72,6 +112,11 @@ class FlexibleInclude < Liquid::Tag
72
112
 
73
113
  private
74
114
 
115
+ def denied(msg)
116
+ @logger.error("#{@helper.page.path} - #{msg}")
117
+ "<p style='color: white; background-color: red; padding: 2pt 1em 2pt 1em;'>#{msg}</p>"
118
+ end
119
+
75
120
  def read_file(file)
76
121
  File.read(file)
77
122
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "shellwords"
4
+ require 'key_value_parser'
4
5
 
5
6
  class JekyllTagHelper
6
7
  attr_reader :argv, :liquid_context, :logger, :params, :tag_name
@@ -9,7 +10,7 @@ class JekyllTagHelper
9
10
  string.gsub("{", "&#123;").gsub("}", "&#125;").gsub("<", "&lt;")
10
11
  end
11
12
 
12
- # Expand environment variable references
13
+ # Expand a environment variable reference
13
14
  def self.expand_env(str)
14
15
  str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) { ENV[Regexp.last_match(1)] }
15
16
  end
data/spec/glob_spec.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/flexible_include"
4
+
5
+ RSpec.describe(FlexibleInclude) do
6
+ it "controls access to files" do
7
+ ENV['FLEXIBLE_INCLUDE_PATHS'] = '~/.*:spec/.*'
8
+
9
+ FlexibleInclude.send(:new, 'my_tag', "", Liquid::ParseContext.new)
10
+ FlexibleInclude.security_check
11
+ expect(FlexibleInclude.access_allowed(__FILE__)).to be_truthy
12
+
13
+ expect(FlexibleInclude.access_allowed("~/.mem_settings.yaml")).to be_truthy
14
+
15
+ home_file = JekyllTagHelper.expand_env("$HOME/.mem_settings.yaml")
16
+ expect(FlexibleInclude.access_allowed(home_file)).to be_truthy
17
+
18
+ expect(FlexibleInclude.access_allowed('/asdf')).to be_falsey
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+
5
+ require_relative "../lib/flexible_include"
6
+
7
+ Jekyll.logger.log_level = :info
8
+
9
+ RSpec.configure do |config|
10
+ config.filter_run :focus
11
+ config.order = "random"
12
+ config.run_all_when_everything_filtered = true
13
+
14
+ # See https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures
15
+ config.example_status_persistence_file_path = "spec/status_persistence.txt"
16
+ end
@@ -0,0 +1,3 @@
1
+ example_id | status | run_time |
2
+ ------------------------ | ------ | --------------- |
3
+ ./spec/glob_spec.rb[1:1] | passed | 0.01217 seconds |
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll_flexible_include
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.7
4
+ version: 2.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Slinn
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2022-04-14 00:00:00.000000000 Z
13
+ date: 2022-04-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: jekyll
@@ -88,6 +88,9 @@ files:
88
88
  - lib/flexible_include.rb
89
89
  - lib/flexible_include/version.rb
90
90
  - lib/jekyll_tag_helper.rb
91
+ - spec/glob_spec.rb
92
+ - spec/spec_helper.rb
93
+ - spec/status_persistence.txt
91
94
  homepage: https://www.mslinn.com/blog/2020/10/03/jekyll-plugins.html#flexibleInclude
92
95
  licenses:
93
96
  - MIT
@@ -120,4 +123,7 @@ signing_key:
120
123
  specification_version: 4
121
124
  summary: Jekyll plugin supports various ways to include content into the generated
122
125
  site.
123
- test_files: []
126
+ test_files:
127
+ - spec/glob_spec.rb
128
+ - spec/spec_helper.rb
129
+ - spec/status_persistence.txt