"What could possibly go wrong when running untrusted code? Everything!" - Every security engineer ever
SpeakNoEvil is your friendly security bouncer for those sketchy Ruby code snippets you're weirdly determined to execute anyway. Whether you're letting an LLM write code that you intend to run (brave soul!), or creating a playground where users can run Ruby, this gem has your back!
SpeakNoEvil uses Ruby's refinement feature to create a security sandbox. It's like putting untrusted code in a playpenβit can have fun, but it can't burn your house down.
Let's dive in and create a secure sandbox for untrusted code:
require 'speak_no_evil'
class SafeBox
# The forbidden forest - no dangerous stuff allowed!
using SpeakNoEvil::Refinements::All # Use ALL the refinements at once!
def self.run(sketchy_code)
result = eval(sketchy_code) # Don't try this outside the refinements!
"Code ran safely, result: #{result}"
rescue SpeakNoEvil::SecurityError => e
"Caught you being naughty! #{e.message}"
end
end
# Try something innocent
SafeBox.run('2 + 2') # => "Code ran safely, result: 4"
# Try something sneaky
SafeBox.run('File.delete("/important_file")') # => "Caught you being naughty! File system modification is not allowed"Add this gem to your collection:
# In your Gemfile
gem 'speak_no_evil'Or install it the old-fashioned way:
$ gem install speak_no_evilLet's meet our security guards, one by one:
using SpeakNoEvil::Refinements::FileSystemRefinements
# Try to read a sensitive file
File.read('/etc/passwd') # => SecurityError: Reading from /etc/passwd is not allowed
# Try to write something sneaky
File.write('destroy_evidence.txt', 'muahaha') # => SecurityError: File system modification is not allowedusing SpeakNoEvil::Refinements::NetworkRefinements
# Try to contact the mothership
require 'net/http'
Net::HTTP.get(URI('https://evil-command-center.com')) # => SecurityError: HTTP requests to evil-command-center.com are not allowed
# Try a socket connection
TCPSocket.new('data-exfiltration.com', 80) # => SecurityError: Network connections to data-exfiltration.com are not allowedusing SpeakNoEvil::Refinements::SystemExecutionRefinements
# Try classic system command execution
system('rm -rf /') # => SecurityError: System command execution is not allowed
# Try the sneaky backtick approach
`cat /etc/shadow` # => SecurityError: System command execution is not allowedusing SpeakNoEvil::Refinements::ProcessRefinements
# Try to fork a process
Process.fork { puts "I'm free!" } # => SecurityError: Process manipulation is not allowed
# Try to kill another process
Process.kill('TERM', 1234) # => SecurityError: Process manipulation is not allowedusing SpeakNoEvil::Refinements::AutoloadRefinements
# Try to autoload from an unsafe path
autoload :DangerClass, '/etc/passwd' # => SecurityError: Autoloading from suspicious path is not allowed
# Try to autoload critical Ruby constants
autoload :ERB, 'erb' # => SecurityError: Autoloading of critical constant ERB is not allowedusing SpeakNoEvil::Refinements::ConcurrencyRefinements
# Try to create too many threads
threads = 100.times.map { Thread.new { sleep 1 } } # => SecurityError: Thread limit exceeded
# Try to manipulate thread abort behavior
Thread.abort_on_exception = true # => SecurityError: Modifying Thread.abort_on_exception is not allowed
# Try to sleep for too long
Kernel.sleep(600) # => SecurityError: Sleep duration exceeds maximum allowed timeusing SpeakNoEvil::Refinements::RailsRefinements
# Try dangerous SQL
User.where("name = '#{user_input}'") # => SecurityError: Potentially unsafe SQL detected
# Try to bypass CSRF protection
protect_from_forgery with: :null_session # => SecurityError: Disabling CSRF protection is not allowed
# Try accessing Rails credentials
Rails.application.credentials.secret_key_base # => SecurityError: Access to Rails credentials is not allowedusing SpeakNoEvil::Refinements::CodeEvaluationRefinements
# Try to use eval
eval('puts "I escaped!"') # => SecurityError: Code evaluation via eval is not allowed
# Try defining a dangerous method
def dangerous_method
`rm -rf /`
end # => SecurityError: Method definition is not allowedProcess.kill(9, 1234) # => SecurityError: Process manipulation is not allowed
### 5. π§ CodeEvaluationRefinements - No Sneaky Evals!
```ruby
using SpeakNoEvil::Refinements::CodeEvaluationRefinements
# Try to break out with eval
eval('File.delete("/important_file")') # => SecurityError: Code evaluation via eval is not allowed
# Try metaprogramming escape
String.class_eval { def dangerous; end } # => SecurityError: Module/Class evaluation is not allowed
using SpeakNoEvil::Refinements::ObjectRefinements
# Try to access ObjectSpace
ObjectSpace.each_object(Class) { |c| puts c } # => SecurityError: ObjectSpace manipulation is not allowed
# Try Marshal trickery
Marshall.load(dangerous_data) # => SecurityError: Marshal operations are not allowedusing SpeakNoEvil::Refinements::ThreadingRefinements
# Try to create too many threads
100.times { Thread.new { loop { } } } # => SecurityError: Thread limit exceeded (max: 5)
# Try to kill threads you don't own
some_thread.kill # => SecurityError: Manipulating other threads is not allowedusing SpeakNoEvil::Refinements::ModuleRefinements
# Try to modify core classes
String.include(EvilModule) # => SecurityError: Modifying core classes/modules is not allowed
# Try to remove methods
String.remove_method(:to_s) # => SecurityError: Removing methods/constants from core classes is not allowedusing SpeakNoEvil::Refinements::IORefinements
# Try to reopen a file for writing
some_io = File.open('important.txt')
some_io.reopen('important.txt', 'w') # => SecurityError: Writing to IO is not allowed
# Try to redirect standard streams
$stdout = File.open('captured_output.txt', 'w') # => SecurityError: Redirecting standard streams is not allowedSpeakNoEvil comes with sensible defaults, but you can customize its behavior:
SpeakNoEvil.configure do |config|
# Allow read access to specific directories
config.allowed_read_paths = ['/safe/path', Dir.pwd]
# Allow network connections to specific hosts
config.allowed_network_hosts = ['api.safe-service.com']
# Set maximum thread count
config.max_threads = 5
# Set maximum sleep duration (in seconds)
config.max_sleep_duration = 1
# Set maximum queue size
config.max_queue_size = 100
# Allow specific constants for autoloading
config.allowed_constants = ['SafeClass', 'AllowedModule']
# Allow specific methods for defining
config.allowed_method_names = [:safe_method, :allowed_helper]
endFor more granular control, you can apply refinements selectively:
class SelectiveSandbox
# Only apply specific refinements
using SpeakNoEvil::Refinements::FileSystemRefinements
using SpeakNoEvil::Refinements::SystemExecutionRefinements
def safe_operation
# File system and system execution are protected
# But network and other operations still allowed
result = Net::HTTP.get(URI('https://api.github.com'))
process_data(result)
end
endclass CustomSandbox
# Apply all security refinements
using SpeakNoEvil::Refinements::All
# Pre-configure allowed resources
def initialize
SpeakNoEvil.configure do |config|
config.allowed_read_paths << './data'
config.allowed_network_hosts << 'api.example.com'
end
end
# Safe evaluation wrapper
def evaluate(code)
begin
result = eval(code)
{ success: true, result: result }
rescue SpeakNoEvil::SecurityError => e
{ success: false, error: e.message }
rescue StandardError => e
{ success: false, error: "Runtime error: #{e.message}" }
end
end
endTo ensure SpeakNoEvil is working properly in your environment:
require 'speak_no_evil/test_suite'
# Run the test suite
SpeakNoEvil::TestSuite.run
# Or test a specific refinement
SpeakNoEvil::TestSuite.test_refinement(:file_system)
## β οΈ Limitations
SpeakNoEvil provides a strong layer of protection, but it's not bulletproof:
- It uses Ruby refinements which have lexical scope - make sure you apply them properly
- It can't protect against excessive memory usage or infinite loops
- Some clever exploits might still bypass protections in edge cases
- For maximum security, combine with OS-level sandboxing and resource limits
## π€ Contributing
Contributions are welcome! Here's how you can help:
1. Fork the repository
2. Create a feature branch (`git checkout -b my-new-feature`)
3. Add or improve refinements
4. Add tests (isolated tests are preferred)
5. Commit your changes (`git commit -am 'Add some feature'`)
6. Push to the branch (`git push origin my-new-feature`)
7. Create a new Pull Request
## π License
The MIT License (MIT)
Copyright (c) 2025 Brandon Zylstra
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
You can use all refinements at once:
```ruby
using SpeakNoEvil::Refinements::All # This includes all refinements!
Or pick and choose which security features you need:
using SpeakNoEvil::Refinements::FileSystemRefinements
using SpeakNoEvil::Refinements::NetworkRefinements
# Add more as neededTweak the security settings to your liking:
SpeakNoEvil.configure do |config|
# Where can code read from?
config.allowed_read_paths << Rails.root.join('app/templates')
# Set resource limits
config.max_cpu_time = 2 # seconds - "ain't nobody got time for that!"
config.max_memory = 50 * 1024 * 1024 # 50MB - "on a diet"
# Allow network calls to specific trusted services
config.allowed_network_hosts << 'api.safe-service.com'
# Allow defining specific constants
config.allowed_constants << 'MyApp::Templates'
endNeed to add your own security measures? It's easy to create custom refinements:
module SpeakNoEvil
module Refinements
module MyCustomRefinements
refine DangerousClass do
def risky_method(*args)
raise SecurityError, "Nice try, but no!"
end
end
end
end
end
# Then use it like any other refinement
using SpeakNoEvil::Refinements::MyCustomRefinementsRemember, this gem is like a club bouncer with a flashlight - helpful, but not invincible:
- Ruby refinements only work within their scope - they won't protect outside that scope
- Clever attackers might find ways around the bouncer
- For critical security, consider OS-level sandboxing too
- This is one layer of your security onion, not the whole onion!
This gem is available as open source under the terms of the MIT License. Use it for good, not evil... although the irony isn't lost on us.