safemode 2.0.2 → 2.0.3
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/core_jails.rb +1 -1
- data/lib/safemode/parser.rb +23 -2
- data/safemode.gemspec +4 -2
- data/test/test_for_loop_jail.rb +27 -0
- data/test/test_parser_node_allowlist.rb +13 -0
- data/test/test_safemode_eval.rb +14 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2feca54490235c1a1dc543fbdb04ac9d3f81495b8f1126cc4d7b411c71fd963
|
|
4
|
+
data.tar.gz: 8a2279cf395544183a5947822bf68a1b7cb190caf093544eae667ca79d8423d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b965556669e1de0b4b966cd7d69a8a55397965a6fbad4b559e91736ed400ccaea3e2f4c50f2dcdef739aa8fb4e2891f927c31a42b471b7b3a2b10020eb981b7c
|
|
7
|
+
data.tar.gz: a0ea7e3948e167e017e27f1e56a1d3a3ad6d6701368aebcd04a0599ebd89d6aab1fd6328ba2624983852e55bb5c4e654480a90a8249239968caea8766d1eb568
|
data/lib/safemode/core_jails.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Safemode
|
|
|
37
37
|
# these methods are allowed in all classes if they are present
|
|
38
38
|
@@default_methods = %w( % & * ** + +@ - -@ / < << <= <=> ! != == === > >= >> ^ | ~
|
|
39
39
|
eql? equal? new methods is_a? kind_of? nil?
|
|
40
|
-
[] []= to_a to_jail to_s inspect to_param not
|
|
40
|
+
[] []= to_a to_jail to_s inspect to_param not)
|
|
41
41
|
|
|
42
42
|
# whitelisted methods for core classes ... kind of arbitrary selection
|
|
43
43
|
@@methods_whitelist = {
|
data/lib/safemode/parser.rb
CHANGED
|
@@ -24,6 +24,12 @@ module Safemode
|
|
|
24
24
|
def process_call(exp, safe_call = false)
|
|
25
25
|
_, recv, name, *args = exp
|
|
26
26
|
|
|
27
|
+
# ERB emits literal template text as string literals. Freezing one only
|
|
28
|
+
# affects that template-generated string, not a string passed in by the caller.
|
|
29
|
+
if name == :freeze && args.empty? && recv && recv.sexp_type == :str
|
|
30
|
+
return "(#{process recv}).freeze"
|
|
31
|
+
end
|
|
32
|
+
|
|
27
33
|
receiver = jail(process_call_receiver(recv), safe_call: safe_call)
|
|
28
34
|
arguments = process_call_args(name, args)
|
|
29
35
|
|
|
@@ -59,9 +65,17 @@ module Safemode
|
|
|
59
65
|
end
|
|
60
66
|
end
|
|
61
67
|
|
|
68
|
+
def process_for(exp)
|
|
69
|
+
_, recv, iter, body = exp
|
|
70
|
+
receiver = jail(process(recv), true)
|
|
71
|
+
variable = process(iter)
|
|
72
|
+
body = process(body) || '# do nothing'
|
|
73
|
+
"for #{variable} in #{receiver} do\n#{indent(body)}\nend"
|
|
74
|
+
end
|
|
75
|
+
|
|
62
76
|
# see http://www.namikilab.tuat.ac.jp/~sasada/prog/rubynodes/nodes.html
|
|
63
77
|
|
|
64
|
-
|
|
78
|
+
ALLOWED_NODES = [ :call, :safe_call, :fcall, :vcall, :evstr, :const, :arglist,
|
|
65
79
|
:lvar, :dvar, :ivar, :lasgn, :masgn, :dasgn, :dasgn_curr,
|
|
66
80
|
:lit, :str, :dstr, :dsym, :nil, :true, :false,
|
|
67
81
|
:array, :zarray, :hash, :dot2, :dot3, :flip2, :flip3,
|
|
@@ -83,7 +97,14 @@ module Safemode
|
|
|
83
97
|
# pattern matching (Ruby 3.0+)
|
|
84
98
|
:in, :array_pat, :hash_pat, :find_pat, :kwrest,
|
|
85
99
|
# needed for haml
|
|
86
|
-
:block ]
|
|
100
|
+
:block ].freeze
|
|
101
|
+
|
|
102
|
+
def process(exp)
|
|
103
|
+
if exp && !ALLOWED_NODES.include?(exp.sexp_type)
|
|
104
|
+
raise_security_error(exp.sexp_type, nil)
|
|
105
|
+
end
|
|
106
|
+
super
|
|
107
|
+
end
|
|
87
108
|
|
|
88
109
|
disallowed = [ # :self, # self doesn't seem to be needed for vcalls?
|
|
89
110
|
# see below for :const handling
|
data/safemode.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ require 'date'
|
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |s|
|
|
6
6
|
s.name = "safemode".freeze
|
|
7
|
-
s.version = "2.0.
|
|
7
|
+
s.version = "2.0.3"
|
|
8
8
|
s.date = Date.today
|
|
9
9
|
|
|
10
10
|
s.summary = "A library for safe evaluation of Ruby code based on Prism and Ruby2Ruby"
|
|
@@ -49,7 +49,9 @@ Gem::Specification.new do |s|
|
|
|
49
49
|
"test/test_helper.rb",
|
|
50
50
|
"test/test_jail.rb",
|
|
51
51
|
"test/test_safemode_eval.rb",
|
|
52
|
-
"test/test_safemode_parser.rb"
|
|
52
|
+
"test/test_safemode_parser.rb",
|
|
53
|
+
"test/test_for_loop_jail.rb",
|
|
54
|
+
"test/test_parser_node_allowlist.rb"
|
|
53
55
|
]
|
|
54
56
|
|
|
55
57
|
s.required_ruby_version = ">= 3.0", "< 3.4"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'test_helper'
|
|
3
|
+
|
|
4
|
+
class TestForLoopJail < Test::Unit::TestCase
|
|
5
|
+
class Host
|
|
6
|
+
attr_reader :calls
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@calls = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def each
|
|
13
|
+
@calls << :each
|
|
14
|
+
yield 'secret'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_for_loop_checks_each_permission
|
|
19
|
+
host = Host.new
|
|
20
|
+
assert_raise(Safemode::NoMethodError) do
|
|
21
|
+
Safemode::Box.new.eval('for x in @host; x; end', host: host)
|
|
22
|
+
end
|
|
23
|
+
assert_empty host.calls
|
|
24
|
+
assert_equal 3, Safemode::Box.new.eval('sum = 0; for x in [1, 2]; sum += x; end; sum')
|
|
25
|
+
assert_equal 2, Safemode::Box.new.eval('for x in [1, 2]; end; x')
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'test_helper'
|
|
3
|
+
|
|
4
|
+
class TestParserNodeAllowlist < Test::Unit::TestCase
|
|
5
|
+
def test_new_dependency_node_handlers_are_not_implicitly_trusted
|
|
6
|
+
parser = Class.new(Safemode::Parser) do
|
|
7
|
+
def process_unreviewed_node(exp)
|
|
8
|
+
'42'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
assert_raise(Safemode::SecurityError) { parser.new.process(Sexp.new(:unreviewed_node)) }
|
|
12
|
+
end
|
|
13
|
+
end
|
data/test/test_safemode_eval.rb
CHANGED
|
@@ -201,6 +201,20 @@ class TestSafemodeEval < Test::Unit::TestCase
|
|
|
201
201
|
end
|
|
202
202
|
end
|
|
203
203
|
|
|
204
|
+
def test_freeze_on_jailed_object_is_blocked
|
|
205
|
+
string = String.new('host value')
|
|
206
|
+
assert_raise(Safemode::NoMethodError) { @box.eval('@value.freeze', value: string) }
|
|
207
|
+
assert_false string.frozen?
|
|
208
|
+
assert_raise(Safemode::NoMethodError) do
|
|
209
|
+
@box.eval('@arr.freeze', { arr: [1, 2, 3] })
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_freeze_on_literal_strings_supports_erb
|
|
214
|
+
assert_predicate @box.eval('"literal".freeze'), :frozen?
|
|
215
|
+
assert_equal 'Hello world', @box.eval(ERB.new('Hello <%= @name %>').src, name: 'world')
|
|
216
|
+
end
|
|
217
|
+
|
|
204
218
|
def test_lambda_is_blocked
|
|
205
219
|
assert_raise(Safemode::SecurityError) { @box.eval('-> { 1 }') }
|
|
206
220
|
end
|
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: 2.0.
|
|
4
|
+
version: 2.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sven Fuchs
|
|
@@ -12,7 +12,7 @@ authors:
|
|
|
12
12
|
- Dmitri Dolguikh
|
|
13
13
|
bindir: bin
|
|
14
14
|
cert_chain: []
|
|
15
|
-
date: 2026-10-
|
|
15
|
+
date: 2026-10-26 00:00:00.000000000 Z
|
|
16
16
|
dependencies:
|
|
17
17
|
- !ruby/object:Gem::Dependency
|
|
18
18
|
name: ruby2ruby
|
|
@@ -140,8 +140,10 @@ files:
|
|
|
140
140
|
- lib/safemode/scope.rb
|
|
141
141
|
- safemode.gemspec
|
|
142
142
|
- test/test_erb_eval.rb
|
|
143
|
+
- test/test_for_loop_jail.rb
|
|
143
144
|
- test/test_helper.rb
|
|
144
145
|
- test/test_jail.rb
|
|
146
|
+
- test/test_parser_node_allowlist.rb
|
|
145
147
|
- test/test_safemode_eval.rb
|
|
146
148
|
- test/test_safemode_parser.rb
|
|
147
149
|
homepage: https://github.com/svenfuchs/safemode
|