ruby-mana 0.5.8 → 0.5.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.
@@ -1,195 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
-
5
- module Mana
6
- # Configurable security policy for LLM tool calls.
7
- #
8
- # Five levels (higher = more permissions, each includes all below):
9
- # 0 :sandbox — variables and user functions only
10
- # 1 :strict — + safe stdlib (Time, Date, Math)
11
- # 2 :standard — + read filesystem (File.read, Dir.glob) [default]
12
- # 3 :permissive — + write files, network, require
13
- # 4 :danger — no restrictions
14
- #
15
- # Usage:
16
- # Mana.configure { |c| c.security = :standard }
17
- # Mana.configure { |c| c.security = 2 }
18
- # Mana.configure do |c|
19
- # c.security = :strict
20
- # c.security.allow_receiver "File", only: %w[read exist?]
21
- # end
22
- class SecurityPolicy
23
- LEVELS = { sandbox: 0, strict: 1, standard: 2, permissive: 3, danger: 4 }.freeze
24
-
25
- # Methods blocked at each level. Higher levels remove restrictions.
26
- PRESETS = {
27
- sandbox: {
28
- blocked_methods: %w[
29
- methods singleton_methods private_methods protected_methods public_methods
30
- instance_variables instance_variable_get instance_variable_set remove_instance_variable
31
- local_variables global_variables
32
- send __send__ public_send eval instance_eval instance_exec class_eval module_eval
33
- system exec fork spawn ` require require_relative load
34
- exit exit! abort at_exit
35
- ],
36
- blocked_receivers: :all
37
- },
38
- strict: {
39
- blocked_methods: %w[
40
- methods singleton_methods private_methods protected_methods public_methods
41
- instance_variables instance_variable_get instance_variable_set remove_instance_variable
42
- local_variables global_variables
43
- send __send__ public_send eval instance_eval instance_exec class_eval module_eval
44
- system exec fork spawn ` require require_relative load
45
- exit exit! abort at_exit
46
- ],
47
- blocked_receivers: {
48
- "File" => :all, "Dir" => :all, "IO" => :all,
49
- "Kernel" => :all, "Process" => :all, "ObjectSpace" => :all, "ENV" => :all
50
- }
51
- },
52
- standard: {
53
- blocked_methods: %w[
54
- methods singleton_methods private_methods protected_methods public_methods
55
- instance_variables instance_variable_get instance_variable_set remove_instance_variable
56
- local_variables global_variables
57
- send __send__ public_send eval instance_eval instance_exec class_eval module_eval
58
- system exec fork spawn `
59
- exit exit! abort at_exit
60
- require require_relative load
61
- ],
62
- blocked_receivers: {
63
- "File" => Set.new(%w[delete write open chmod chown rename unlink]),
64
- "Dir" => Set.new(%w[delete rmdir mkdir chdir]),
65
- "IO" => :all, "Kernel" => :all, "Process" => :all,
66
- "ObjectSpace" => :all, "ENV" => :all
67
- }
68
- },
69
- permissive: {
70
- blocked_methods: %w[
71
- eval instance_eval instance_exec class_eval module_eval
72
- system exec fork spawn `
73
- exit exit! abort at_exit
74
- ],
75
- blocked_receivers: {
76
- "ObjectSpace" => :all
77
- }
78
- },
79
- danger: {
80
- blocked_methods: [],
81
- blocked_receivers: {}
82
- }
83
- }.freeze
84
-
85
- attr_reader :preset
86
-
87
- # Initialize security policy from a preset name (Symbol) or numeric level (Integer)
88
- def initialize(preset = :strict)
89
- # Convert numeric level to its corresponding symbol name
90
- preset = LEVELS.key(preset) if preset.is_a?(Integer)
91
- raise ArgumentError, "unknown security level: #{preset.inspect}. Use: #{LEVELS.keys.join(', ')}" unless PRESETS.key?(preset)
92
-
93
- @preset = preset
94
- data = PRESETS[preset]
95
- @blocked_methods = Set.new(data[:blocked_methods])
96
-
97
- if data[:blocked_receivers] == :all
98
- # Sandbox mode: block all receiver calls by default
99
- @block_all_receivers = true
100
- @blocked_receivers = {}
101
- else
102
- # Other modes: block only specific methods on specific receivers
103
- @block_all_receivers = false
104
- @blocked_receivers = data[:blocked_receivers].transform_values { |v|
105
- v == :all ? :all : Set.new(v)
106
- }
107
- end
108
-
109
- # Allowlist overrides added via allow_receiver(name, only: [...])
110
- @allowed_overrides = {}
111
- yield self if block_given?
112
- end
113
-
114
- # --- Mutators ---
115
-
116
- def allow_method(name)
117
- @blocked_methods.delete(name.to_s)
118
- end
119
-
120
- def block_method(name)
121
- @blocked_methods.add(name.to_s)
122
- end
123
-
124
- # Allow a receiver's calls. With `only:`, allowlist specific methods;
125
- # without it, fully unblock the receiver.
126
- def allow_receiver(name, only: nil)
127
- name = name.to_s
128
- if only
129
- # Allowlist only the specified methods (override takes priority over block rules)
130
- @allowed_overrides[name] = Set.new(only.map(&:to_s))
131
- else
132
- # Fully unblock the receiver and remove any override
133
- @blocked_receivers.delete(name)
134
- @allowed_overrides.delete(name)
135
- end
136
- end
137
-
138
- # Block a receiver's calls. With `only:`, block specific methods;
139
- # without it, block all methods on the receiver.
140
- def block_receiver(name, only: nil)
141
- name = name.to_s
142
- if only
143
- existing = @blocked_receivers[name]
144
- if existing == :all
145
- # Already fully blocked — nothing to add
146
- elsif existing.is_a?(Set)
147
- # Merge new blocked methods into the existing set
148
- existing.merge(only.map(&:to_s))
149
- else
150
- # First partial block rule for this receiver
151
- @blocked_receivers[name] = Set.new(only.map(&:to_s))
152
- end
153
- else
154
- # Block all methods on this receiver
155
- @blocked_receivers[name] = :all
156
- @allowed_overrides.delete(name)
157
- end
158
- end
159
-
160
- # --- Queries ---
161
-
162
- def method_blocked?(name)
163
- @blocked_methods.include?(name.to_s)
164
- end
165
-
166
- # Check whether calling `method` on `receiver` is blocked by the policy
167
- def receiver_call_blocked?(receiver, method)
168
- # Danger mode has no restrictions at all
169
- return false if @preset == :danger
170
-
171
- r, m = receiver.to_s, method.to_s
172
-
173
- # Sandbox mode: block everything unless explicitly allowlisted
174
- if @block_all_receivers
175
- return !(@allowed_overrides.key?(r) && @allowed_overrides[r].include?(m))
176
- end
177
-
178
- # Receiver not in the block list — allow
179
- rule = @blocked_receivers[r]
180
- return false if rule.nil?
181
-
182
- # Allowlist override takes priority: if user explicitly allowed this method, pass
183
- if @allowed_overrides.key?(r) && @allowed_overrides[r].include?(m)
184
- return false
185
- end
186
-
187
- # Blocked if receiver is fully blocked (:all) or method is in the blocked set
188
- rule == :all || rule.include?(m)
189
- end
190
-
191
- def level
192
- LEVELS[@preset]
193
- end
194
- end
195
- end