safemode 1.6.0 → 2.0.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 +4 -4
- data/lib/safemode/parser.rb +56 -17
- data/lib/safemode.rb +2 -4
- data/safemode.gemspec +4 -4
- data/test/test_helper.rb +3 -1
- data/test/test_safemode_eval.rb +105 -0
- data/test/test_safemode_parser.rb +101 -0
- metadata +8 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31ed45049398953b35fafe5ff837df2332712caa9534c5278e971d9031a61836
|
|
4
|
+
data.tar.gz: 49b507273a0ef6d03578be21a26f26388fb4cd54d253670a8932ae7891ae40f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eefb3d5121ad93910e1468cd635a5d77320dfbd23c341469af7acc1d90c80947ab1f8ec058bf1775b9269566d2503cfec0118bb6143e9d35ee6ba5afbb055caa
|
|
7
|
+
data.tar.gz: ab5f0385d055999f6a6463381d24494bab377fe12aebec639b3be5fc78faf4355c5485105ba6c73bbc0a0023c2db3dd063bc35abee19d4564748d15a925650d8
|
data/lib/safemode/parser.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
module Safemode
|
|
2
2
|
class Parser < Ruby2Ruby
|
|
3
|
-
@@parser = 'RubyParser'
|
|
4
|
-
|
|
5
3
|
class << self
|
|
6
4
|
def jail(code, allowed_fcalls = [])
|
|
7
5
|
@@allowed_fcalls = allowed_fcalls
|
|
@@ -10,16 +8,7 @@ module Safemode
|
|
|
10
8
|
end
|
|
11
9
|
|
|
12
10
|
def parse(code)
|
|
13
|
-
|
|
14
|
-
when 'RubyParser'
|
|
15
|
-
RubyParser.new.parse(code)
|
|
16
|
-
else
|
|
17
|
-
raise "unknown parser #{@@parser}"
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def parser=(parser)
|
|
22
|
-
@@parser = parser
|
|
11
|
+
Prism::Translation::RubyParser.parse(code)
|
|
23
12
|
end
|
|
24
13
|
end
|
|
25
14
|
|
|
@@ -86,9 +75,13 @@ module Safemode
|
|
|
86
75
|
# :colon2 is used for module constants
|
|
87
76
|
:colon2,
|
|
88
77
|
# unnecessarily advanced?
|
|
89
|
-
:argscat, :argspush, :splat,
|
|
78
|
+
:argscat, :argspush, :splat, :kwsplat,
|
|
79
|
+
# NOTE: op_asgn*/safe_op_asgn* do not insert .to_jail on the
|
|
80
|
+
# receiver. Setters are called directly on the real object, bypassing Jail.
|
|
90
81
|
:op_asgn, :op_asgn1, :op_asgn2, :op_asgn_and, :op_asgn_or,
|
|
91
|
-
:safe_op_asgn,
|
|
82
|
+
:safe_op_asgn, :safe_op_asgn2,
|
|
83
|
+
# pattern matching (Ruby 3.0+)
|
|
84
|
+
:in, :array_pat, :hash_pat, :find_pat, :kwrest,
|
|
92
85
|
# needed for haml
|
|
93
86
|
:block ]
|
|
94
87
|
|
|
@@ -96,18 +89,21 @@ module Safemode
|
|
|
96
89
|
# see below for :const handling
|
|
97
90
|
:defn, :defs, :alias, :valias, :undef, :class, :attrset,
|
|
98
91
|
:module, :sclass, :colon3,
|
|
99
|
-
:fbody, :scope, :block_arg, :postexe,
|
|
92
|
+
:fbody, :scope, :block_arg, :postexe, :preexe,
|
|
100
93
|
:redo, :retry, :begin, :rescue, :resbody, :ensure,
|
|
101
94
|
:defined, :super, :zsuper, :return,
|
|
102
95
|
:dmethod, :bmethod, :to_ary, :svalue, :match,
|
|
103
|
-
:attrasgn, :
|
|
96
|
+
:attrasgn, :safe_attrasgn,
|
|
97
|
+
:cdecl, :cvasgn, :cvdecl, :cvar, :gvar, :gasgn,
|
|
104
98
|
:xstr, :dxstr,
|
|
105
99
|
# not sure how secure ruby regexp is, so leave it out for now
|
|
106
100
|
:dregx, :dregx_once, :match2, :match3, :nth_ref, :back_ref,
|
|
107
101
|
# block_pass represents &:method, which would bypass the whitelist e.g. by array.each(&:destroy)
|
|
108
102
|
# at this point we don't know the receiver so we rather disable it completely,
|
|
109
103
|
# use array.each { |item| item.destroy } instead
|
|
110
|
-
:block_pass
|
|
104
|
+
:block_pass,
|
|
105
|
+
# lambda creates Proc objects
|
|
106
|
+
:lambda ]
|
|
111
107
|
|
|
112
108
|
# SexpProcessor bails when we overwrite these ... but they are listed as
|
|
113
109
|
# "internal nodes that you can't get to" in sexp_processor.rb
|
|
@@ -205,6 +201,49 @@ module Safemode
|
|
|
205
201
|
end
|
|
206
202
|
end
|
|
207
203
|
|
|
204
|
+
# Ruby2Ruby bug: __var adds ^ (pin) to all lvars inside :in context,
|
|
205
|
+
# including the body after "then" where ^ is invalid syntax.
|
|
206
|
+
# Fix: process body outside the :in context.
|
|
207
|
+
def process_in(exp)
|
|
208
|
+
_, pattern, *body = exp
|
|
209
|
+
|
|
210
|
+
guard = extract_guard(pattern)
|
|
211
|
+
cond = process pattern
|
|
212
|
+
body = body.compact.map { |sexp|
|
|
213
|
+
in_context :in_body do
|
|
214
|
+
indent process sexp
|
|
215
|
+
end
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
body << indent("# do nothing") if body.empty?
|
|
219
|
+
body = body.join "\n"
|
|
220
|
+
|
|
221
|
+
header = "in #{cond}"
|
|
222
|
+
header << " #{guard}" if guard
|
|
223
|
+
"#{header} then\n#{body.chomp}"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Prism translates guard clauses (in pattern if cond / in pattern unless cond)
|
|
227
|
+
# as s(:if, guard, pattern, nil) / s(:if, guard, nil, pattern). We detect this
|
|
228
|
+
# and rewrite the sexp in-place so process_if renders only the pattern, then
|
|
229
|
+
# return the guard string for process_in to append.
|
|
230
|
+
def extract_guard(pattern)
|
|
231
|
+
return unless pattern.sexp_type == :if
|
|
232
|
+
|
|
233
|
+
_, guard_sexp, if_body, else_body = pattern
|
|
234
|
+
if if_body && !else_body
|
|
235
|
+
guard_str = in_context(:in_body) { "if #{process guard_sexp.deep_clone}" }
|
|
236
|
+
pattern.clear
|
|
237
|
+
if_body.each { |node| pattern << node }
|
|
238
|
+
elsif else_body && !if_body
|
|
239
|
+
guard_str = in_context(:in_body) { "unless #{process guard_sexp.deep_clone}" }
|
|
240
|
+
pattern.clear
|
|
241
|
+
else_body.each { |node| pattern << node }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
guard_str
|
|
245
|
+
end
|
|
246
|
+
|
|
208
247
|
# Ruby2Ruby process_if rewrites if and unless statements in a way that
|
|
209
248
|
# makes the result unusable for evaluation in, e.g. ERB which appends a
|
|
210
249
|
# call to to_s when using <%= %> tags. We'd need to either enclose the
|
data/lib/safemode.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
|
|
3
3
|
require 'ruby2ruby'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
rescue LoadError => e
|
|
7
|
-
end
|
|
4
|
+
require 'prism'
|
|
5
|
+
require 'prism/translation/ruby_parser'
|
|
8
6
|
|
|
9
7
|
require 'safemode/core_ext'
|
|
10
8
|
require 'safemode/blankslate'
|
data/safemode.gemspec
CHANGED
|
@@ -4,10 +4,10 @@ require 'date'
|
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |s|
|
|
6
6
|
s.name = "safemode".freeze
|
|
7
|
-
s.version = "
|
|
7
|
+
s.version = "2.0.0"
|
|
8
8
|
s.date = Date.today
|
|
9
9
|
|
|
10
|
-
s.summary = "A library for safe evaluation of Ruby code based on
|
|
10
|
+
s.summary = "A library for safe evaluation of Ruby code based on Prism and Ruby2Ruby"
|
|
11
11
|
s.description = "A library for safe evaluation of Ruby code based on RubyParser and Ruby2Ruby. Provides Rails ActionView template handlers for ERB and Haml."
|
|
12
12
|
s.homepage = "https://github.com/svenfuchs/safemode"
|
|
13
13
|
s.licenses = ["MIT"]
|
|
@@ -52,10 +52,10 @@ Gem::Specification.new do |s|
|
|
|
52
52
|
"test/test_safemode_parser.rb"
|
|
53
53
|
]
|
|
54
54
|
|
|
55
|
-
s.required_ruby_version = ">=
|
|
55
|
+
s.required_ruby_version = ">= 3.0", "< 3.4"
|
|
56
56
|
|
|
57
57
|
s.add_runtime_dependency "ruby2ruby", "~> 2.4"
|
|
58
|
-
s.add_runtime_dependency "
|
|
58
|
+
s.add_runtime_dependency "prism", "~> 1.0"
|
|
59
59
|
s.add_runtime_dependency "sexp_processor", "~> 4.10"
|
|
60
60
|
|
|
61
61
|
s.add_development_dependency "rake", "~> 13.4"
|
data/test/test_helper.rb
CHANGED
|
@@ -112,6 +112,8 @@ class Article
|
|
|
112
112
|
Comment
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
+
attr_accessor :status
|
|
116
|
+
|
|
115
117
|
def method_with_kwargs(a_keyword: false)
|
|
116
118
|
a_keyword
|
|
117
119
|
end
|
|
@@ -150,7 +152,7 @@ class Comment
|
|
|
150
152
|
end
|
|
151
153
|
|
|
152
154
|
class Article::Jail < Safemode::Jail
|
|
153
|
-
allow :title, :comments, :is_article?, :comment_class, :method_with_kwargs
|
|
155
|
+
allow :title, :comments, :is_article?, :comment_class, :method_with_kwargs, :status
|
|
154
156
|
|
|
155
157
|
def author_name
|
|
156
158
|
"this article's author name"
|
data/test/test_safemode_eval.rb
CHANGED
|
@@ -94,6 +94,111 @@ class TestSafemodeEval < Test::Unit::TestCase
|
|
|
94
94
|
assert @box.eval("@article.method_with_kwargs(a_keyword: true)", @assigns)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
def test_pattern_matching_with_literal
|
|
98
|
+
assert_equal "matched", @box.eval('case 1; in 1; "matched"; in 2; "nope"; end')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_pattern_matching_with_array_destructuring
|
|
102
|
+
assert_equal 1, @box.eval('case [1, 2, 3]; in [a, b, c]; a; end')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_pattern_matching_with_hash_destructuring
|
|
106
|
+
assert_equal 1, @box.eval('case({a: 1, b: 2}); in {a:}; a; end')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_pattern_matching_with_assign
|
|
110
|
+
assert_equal "two", @box.eval('case @val; in 1; "one"; in 2; "two"; end', { val: 2 })
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_pattern_matching_with_find_pattern
|
|
114
|
+
assert_equal "found", @box.eval('case [1, 2, 3]; in [*, 2, *]; "found"; end')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_pattern_matching_with_pin_operator
|
|
118
|
+
assert_equal "matched", @box.eval('y = 1; case 1; in ^y; "matched"; end')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_pattern_matching_with_multiple_clauses
|
|
122
|
+
assert_equal "second", @box.eval('case [3, 4]; in [1, 2]; "first"; in [3, 4]; "second"; end')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_pattern_matching_no_match_returns_nil
|
|
126
|
+
assert_nil @box.eval('case 3; in 1; "one"; in 2; "two"; else; nil; end')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_pattern_matching_with_if_guard
|
|
130
|
+
assert_equal "positive", @box.eval('case 1; in x if x > 0; "positive"; end')
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_pattern_matching_with_unless_guard
|
|
134
|
+
assert_equal "not negative", @box.eval('case 1; in x unless x < 0; "not negative"; end')
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_pattern_matching_guard_no_match
|
|
138
|
+
assert_nil @box.eval('case 1; in x if x < 0; "negative"; else; nil; end')
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_pattern_matching_guard_with_array_pattern
|
|
142
|
+
assert_equal "yes", @box.eval('case [1, 2]; in [a, b] if a > 0; "yes"; end')
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_pattern_matching_guard_with_hash_pattern
|
|
146
|
+
assert_equal "alice", @box.eval('case({name: "alice"}); in {name:} if name.start_with?("a"); name; end')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_rightward_assignment
|
|
150
|
+
assert_equal 1, @box.eval('[1, 2] => [a, b]; a')
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def test_pattern_matching_with_jailed_hash
|
|
154
|
+
assert_equal "an article title", @box.eval('case @data; in {title:}; title; end', { data: { title: "an article title" } })
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_hash_shorthand
|
|
158
|
+
# TODO: Remove the check once Ruby 3.1 is the minimum
|
|
159
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.1')
|
|
160
|
+
assert_equal({ a: 1, b: 2 }, @box.eval('a = 1; b = 2; { a:, b: }'))
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_endless_range
|
|
165
|
+
assert_equal [3, 4, 5], @box.eval('[1,2,3,4,5][2..]')
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_beginless_range
|
|
169
|
+
assert_equal [1, 2, 3], @box.eval('[1,2,3,4,5][..2]')
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# attrasgn (@article.status = val) is blocked at parse time
|
|
173
|
+
def test_attrasgn_is_blocked
|
|
174
|
+
assert_raise(Safemode::SecurityError) { @box.eval('@article.status = "new_value"', @assigns) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def test_safe_attrasgn_is_blocked
|
|
178
|
+
assert_raise(Safemode::SecurityError) { @box.eval('@article&.status = "new_value"', @assigns) }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# op_asgn2 (@article.status ||= val) is NOT blocked at parse time.
|
|
182
|
+
# NOTE: .to_jail is not inserted on the receiver, so the setter is called
|
|
183
|
+
# directly on the real object, bypassing the Jail whitelist.
|
|
184
|
+
def test_op_asgn2_bypasses_jail
|
|
185
|
+
article = Article.new
|
|
186
|
+
assert_nil article.status
|
|
187
|
+
@box.eval('@article.status ||= "new_value"', { article: article })
|
|
188
|
+
assert_equal "new_value", article.status
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def test_safe_op_asgn2_bypasses_jail
|
|
192
|
+
article = Article.new
|
|
193
|
+
assert_nil article.status
|
|
194
|
+
@box.eval('@article&.status ||= "new_value"', { article: article })
|
|
195
|
+
assert_equal "new_value", article.status
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_lambda_is_blocked
|
|
199
|
+
assert_raise(Safemode::SecurityError) { @box.eval('-> { 1 }') }
|
|
200
|
+
end
|
|
201
|
+
|
|
97
202
|
def test_sending_to_method_missing
|
|
98
203
|
assert_raise_with_message(Safemode::NoMethodError, /#no_such_method/) do
|
|
99
204
|
@box.eval("@article.no_such_method('arg', key: 'value')", @assigns)
|
|
@@ -67,6 +67,107 @@ class TestSafemodeParser < Test::Unit::TestCase
|
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
def test_safe_attrasgn_is_disabled
|
|
71
|
+
assert_raise Safemode::SecurityError do
|
|
72
|
+
jail('@article&.title = "new_value"')
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_safe_op_asgn2_is_allowed
|
|
77
|
+
assert_nothing_raised do
|
|
78
|
+
jail('@article&.title ||= "new_value"')
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_lambda_is_disabled
|
|
83
|
+
assert_raise Safemode::SecurityError do
|
|
84
|
+
jail('-> { 1 }')
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_case_in_with_literal
|
|
89
|
+
jailed = jail("case x; in 1; \"one\"; in 2; \"two\"; end")
|
|
90
|
+
assert_match(/in 1 then/, jailed)
|
|
91
|
+
assert_match(/in 2 then/, jailed)
|
|
92
|
+
assert_match(/to_jail\.x/, jailed)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_case_in_with_array_pattern
|
|
96
|
+
jailed = jail("case x; in [a, b]; a; end")
|
|
97
|
+
assert_match(/in \[a, b\] then/, jailed)
|
|
98
|
+
assert_no_match(/\^a/, jailed)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_case_in_with_hash_pattern
|
|
102
|
+
jailed = jail("case x; in {name:}; name; end")
|
|
103
|
+
assert_match(/in \{ name: \} then/, jailed)
|
|
104
|
+
assert_no_match(/\^name/, jailed)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def test_case_in_with_find_pattern
|
|
108
|
+
jailed = jail("case x; in [*, 2, *]; \"found\"; end")
|
|
109
|
+
assert_match(/in \[\*, 2, \*\] then/, jailed)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_case_in_pin_operator
|
|
113
|
+
jailed = jail("y = 1; case x; in ^y; true; end")
|
|
114
|
+
assert_match(/in \^y then/, jailed)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_case_in_body_does_not_pin_variables
|
|
118
|
+
jailed = jail("case x; in [a, b]; a; end")
|
|
119
|
+
lines = jailed.lines
|
|
120
|
+
body_start = lines.index { |l| l.match?(/^in \[/) } + 1
|
|
121
|
+
lines[body_start..].each { |line| assert_no_match(/\^[a-z]/, line) }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_case_in_multiple_clauses
|
|
125
|
+
jailed = jail("case x; in [a, b]; a; in {c:}; c; end")
|
|
126
|
+
assert_match(/in \[a, b\] then/, jailed)
|
|
127
|
+
assert_match(/in \{ c: \} then/, jailed)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def test_case_in_with_if_guard
|
|
131
|
+
jailed = jail("case x; in 1 if true; \"matched\"; end")
|
|
132
|
+
assert_match(/in 1 if true then/, jailed)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_case_in_with_unless_guard
|
|
136
|
+
jailed = jail("case x; in 1 unless false; \"matched\"; end")
|
|
137
|
+
assert_match(/in 1 unless false then/, jailed)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_case_in_guard_does_not_pin_variables
|
|
141
|
+
jailed = jail("case x; in [a, b] if a; a; end")
|
|
142
|
+
guard_line = jailed.lines.find { |l| l.match?(/^in \[/) }
|
|
143
|
+
assert_match(/if a then/, guard_line)
|
|
144
|
+
assert_no_match(/\^a/, guard_line)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_case_in_guard_jails_method_calls
|
|
148
|
+
jailed = jail('case x; in {name: n} if n.start_with?("a"); n; end')
|
|
149
|
+
assert_match(/if n\.to_jail\.start_with\?/, jailed)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_rightward_assignment
|
|
153
|
+
jailed = jail("x => a")
|
|
154
|
+
assert_match(/case to_jail\.x/, jailed)
|
|
155
|
+
assert_match(/in a then/, jailed)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def test_hash_shorthand_in_literal
|
|
159
|
+
jailed = jail("a = 1; b = 2; { a:, b: }")
|
|
160
|
+
assert_match(/a:,/, jailed)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_endless_range
|
|
164
|
+
assert_jailed "(1..)", "(1..)"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def test_beginless_range
|
|
168
|
+
assert_jailed "(..5)", "(..5)"
|
|
169
|
+
end
|
|
170
|
+
|
|
70
171
|
private
|
|
71
172
|
|
|
72
173
|
def assert_jailed(expected, code)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: safemode
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sven Fuchs
|
|
@@ -10,7 +10,6 @@ authors:
|
|
|
10
10
|
- Kingsley Hendrickse
|
|
11
11
|
- Ohad Levy
|
|
12
12
|
- Dmitri Dolguikh
|
|
13
|
-
autorequire:
|
|
14
13
|
bindir: bin
|
|
15
14
|
cert_chain: []
|
|
16
15
|
date: 2026-05-21 00:00:00.000000000 Z
|
|
@@ -30,19 +29,19 @@ dependencies:
|
|
|
30
29
|
- !ruby/object:Gem::Version
|
|
31
30
|
version: '2.4'
|
|
32
31
|
- !ruby/object:Gem::Dependency
|
|
33
|
-
name:
|
|
32
|
+
name: prism
|
|
34
33
|
requirement: !ruby/object:Gem::Requirement
|
|
35
34
|
requirements:
|
|
36
35
|
- - "~>"
|
|
37
36
|
- !ruby/object:Gem::Version
|
|
38
|
-
version: '
|
|
37
|
+
version: '1.0'
|
|
39
38
|
type: :runtime
|
|
40
39
|
prerelease: false
|
|
41
40
|
version_requirements: !ruby/object:Gem::Requirement
|
|
42
41
|
requirements:
|
|
43
42
|
- - "~>"
|
|
44
43
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '
|
|
44
|
+
version: '1.0'
|
|
46
45
|
- !ruby/object:Gem::Dependency
|
|
47
46
|
name: sexp_processor
|
|
48
47
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -115,7 +114,6 @@ dependencies:
|
|
|
115
114
|
version: '3.7'
|
|
116
115
|
description: A library for safe evaluation of Ruby code based on RubyParser and Ruby2Ruby.
|
|
117
116
|
Provides Rails ActionView template handlers for ERB and Haml.
|
|
118
|
-
email:
|
|
119
117
|
executables: []
|
|
120
118
|
extensions: []
|
|
121
119
|
extra_rdoc_files:
|
|
@@ -150,7 +148,6 @@ homepage: https://github.com/svenfuchs/safemode
|
|
|
150
148
|
licenses:
|
|
151
149
|
- MIT
|
|
152
150
|
metadata: {}
|
|
153
|
-
post_install_message:
|
|
154
151
|
rdoc_options: []
|
|
155
152
|
require_paths:
|
|
156
153
|
- lib
|
|
@@ -158,18 +155,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
158
155
|
requirements:
|
|
159
156
|
- - ">="
|
|
160
157
|
- !ruby/object:Gem::Version
|
|
161
|
-
version: '
|
|
158
|
+
version: '3.0'
|
|
162
159
|
- - "<"
|
|
163
160
|
- !ruby/object:Gem::Version
|
|
164
|
-
version: '3.
|
|
161
|
+
version: '3.4'
|
|
165
162
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
163
|
requirements:
|
|
167
164
|
- - ">="
|
|
168
165
|
- !ruby/object:Gem::Version
|
|
169
166
|
version: '0'
|
|
170
167
|
requirements: []
|
|
171
|
-
rubygems_version:
|
|
172
|
-
signing_key:
|
|
168
|
+
rubygems_version: 4.0.10
|
|
173
169
|
specification_version: 4
|
|
174
|
-
summary: A library for safe evaluation of Ruby code based on
|
|
170
|
+
summary: A library for safe evaluation of Ruby code based on Prism and Ruby2Ruby
|
|
175
171
|
test_files: []
|