Class: RailsMcpServer::ExecuteRuby
- Defined in:
- lib/rails-mcp-server/tools/execute_ruby.rb
Constant Summary collapse
- FORBIDDEN_PATTERNS =
Patterns that indicate dangerous operations
[ # File/IO writing /File\.(write|open|new)\s*\([^)]*['"][wa+]/i, /File\.(delete|unlink|rename|chmod|chown|truncate)/i, /FileUtils\./i, /IO\.(write|syswrite|popen|pipe)/i, /\.(write|puts|print|syswrite)\s*[(\s]/, # Directory modification /Dir\.(mkdir|rmdir|delete|chdir)/i, # System/shell execution /system\s*[(\s]/, /exec\s*[(\s]/, /`[^`]+`/, /%x[{(\[]/, /Kernel\.(system|exec|spawn|`)/, /Open3\./i, /IO\.popen/i, /Process\.(spawn|exec|fork)/i, /Shellwords/i, # Network access /Net::(HTTP|FTP|SMTP)/i, /URI\.(open|parse)/i, /HTTParty/i, /Faraday/i, /RestClient/i, /open-uri/i, /Socket/i, /TCPSocket/i, /UDPSocket/i, # Dangerous Ruby features /eval\s*[(\s]/, /instance_eval/i, /class_eval/i, /module_eval/i, /define_method/i, /send\s*[(\s]+[:'"]*(system|exec|`)/i, /__send__/, /ObjectSpace/i, /Binding/i, /set_trace_func/i, # Environment/credentials access # Match any ENV usage (ENV[, ENV.fetch, ENV.to_h, ENV.values_at, ENV.each, # ...). Case-sensitive so it doesn't flag `Rails.env` or a local `env`. /\bENV\b/, /Rails\.application\.credentials/i, /Rails\.application\.secrets/i, # Load/require that could execute arbitrary code /load\s*[(\s]+[^)]*\$/i, /require\s+[^'"]/i ].freeze
- CONFIRMATION_REQUIRED_PATTERNS =
Dual-use constructs that are NOT hard-blocked (they have legitimate read-only uses) but can defeat the static safety scan, so running them requires explicit user confirmation via confirm_risky: true. Each entry: [pattern, label, why-it-is-risky].
[ [/(?<![.\w])open\s*\(/, "Kernel#open", "`open(arg)` runs a shell command when arg begins with '|', and can open network/URI targets — both escape the sandbox."], [/\bpublic_send\b/, "public_send", "dynamic dispatch can invoke methods the static scan cannot see, e.g. reaching blocked system/file APIs indirectly."], [/\bsend\s*[(\s]/, "send", "dynamic dispatch can invoke methods the static scan cannot see, e.g. reaching blocked system/file APIs indirectly."], [/\bconst_get\b/, "const_get", "resolves constants by name at runtime, which can reach classes the static scan would otherwise block."] ].freeze
- SENSITIVE_PATTERNS =
Sensitive file patterns (in addition to .gitignore)
[ /\.env(\..*)?$/i, /\.key$/i, /\.pem$/i, /\.crt$/i, /\.p12$/i, /credentials\.yml/i, /secrets\.yml/i, /master\.key/i, /config\/credentials/i, /config\/secrets/i, /\.secret$/i, /password/i, /\.ssh\//i, /id_rsa/i, /id_ed25519/i ].freeze
- ALLOWED_READ_PATHS =
Read-only system data directories the sandbox may read. TZInfo lazily loads IANA timezone data on first Time.zone use; these are its default search paths plus /var/db/timezone, the real location behind macOS's /usr/share/zoneinfo symlink. Writes remain blocked by the File/Dir/ FileUtils overrides.
%w[ /usr/share/zoneinfo /usr/share/lib/zoneinfo /etc/zoneinfo /var/db/timezone ].freeze
- NO_OUTPUT_MESSAGE =
<<~MSG Code executed successfully (no output). Hint: Use `puts` to see results, e.g.: puts read_file('config/routes.rb') puts User.count puts Dir.glob('app/models/*.rb') MSG
Instance Method Summary collapse
Instance Method Details
#call(code:, timeout: 30, confirm_risky: false) ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/rails-mcp-server/tools/execute_ruby.rb', line 154 def call(code:, timeout: 30, confirm_risky: false) unless current_project return "No active project. Please switch to a project first." end timeout = [timeout.to_i, 60].min # Cap at 60 seconds timeout = 10 if timeout < 1 # Step 1: Static analysis - reject outright-dangerous code validation_error = validate_code_safety(code) return validation_error if validation_error # Step 2: Dual-use constructs require explicit user confirmation unless confirm_risky confirmation = confirmation_required(code) return confirmation if confirmation end # Step 3: Build the sandboxed execution environment sandbox_code = build_sandbox(code) # Step 4: Execute with timeout execute_sandboxed(sandbox_code, timeout) end |