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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7362759a3432731cdb5b3f8177623fe699b6f78e52922813d0c6af3e280d1b92
4
- data.tar.gz: 1f409d13eb5ca31211503168e667e38413cf5f473dece30d7463a099ad9ec751
3
+ metadata.gz: b2feca54490235c1a1dc543fbdb04ac9d3f81495b8f1126cc4d7b411c71fd963
4
+ data.tar.gz: 8a2279cf395544183a5947822bf68a1b7cb190caf093544eae667ca79d8423d3
5
5
  SHA512:
6
- metadata.gz: 7adb149b67b9ddd344203a0360ff70e663effb7a0da5ce5506dbac023b12db34a347a59b976cab62c9cf01cdd8cefd28be02c1c3442df787e599eb82bcad0940
7
- data.tar.gz: 33a099e1f86a7e9f9c0a14425126a36f332921503ee35bc47e686a7c1deeff301b4b40083c96050ab4d2afa0f354d71204b4429828390cffd75248631eee5d80
6
+ metadata.gz: b965556669e1de0b4b966cd7d69a8a55397965a6fbad4b559e91736ed400ccaea3e2f4c50f2dcdef739aa8fb4e2891f927c31a42b471b7b3a2b10020eb981b7c
7
+ data.tar.gz: a0ea7e3948e167e017e27f1e56a1d3a3ad6d6701368aebcd04a0599ebd89d6aab1fd6328ba2624983852e55bb5c4e654480a90a8249239968caea8766d1eb568
@@ -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 freeze)
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 = {
@@ -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
- allowed = [ :call, :vcall, :evstr,
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.2"
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
@@ -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.2
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-01 00:00:00.000000000 Z
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