Skip to content

brandonzylstra/speak_no_evil

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

13 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ™ˆ SpeakNoEvil πŸ™‰ πŸ™Š

"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!

πŸ›‘οΈ What's This Magic?

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.

πŸƒβ€β™€οΈ Quick Start

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"

πŸ“¦ Installation

Add this gem to your collection:

# In your Gemfile
gem 'speak_no_evil'

Or install it the old-fashioned way:

$ gem install speak_no_evil

πŸ”’ Security Refinements - Your Arsenal

Let's meet our security guards, one by one:

1. πŸ“‚ FileSystemRefinements - No Touching My Files!

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 allowed

2. 🌐 NetworkRefinements - No Phoning Home!

using 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 allowed

3. πŸ’» SystemExecutionRefinements - No Shell Games!

using 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 allowed

4. 🧡 ProcessRefinements - No Process Shenanigans!

using 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 allowed

5. πŸ’Ύ AutoloadRefinements - No Sneaky Loading!

using 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 allowed

6. πŸ”„ ConcurrencyRefinements - No Sneaky Threading!

using 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 time

7. πŸ›€οΈ RailsRefinements - Keeping Rails on Track!

using 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 allowed

8. πŸ” CodeEvaluationRefinements - No Eval for You!

using 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 allowed

Process.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

6. 🎭 ObjectRefinements - No Object Trickery!

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 allowed

7. 🧢 ThreadingRefinements - No Thread Abuse!

using 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 allowed

8. πŸ“š ModuleRefinements - No Messing With Classes!

using 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 allowed

9. πŸ“ IORefinements - No Sneaky IO!

using 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 allowed

πŸ“ Configuration Options

SpeakNoEvil 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]
end

πŸ› οΈ Isolated Refinement Usage

For 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
end

🎭 Creating Custom Sandbox Classes

class 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
end

πŸ§ͺ Testing and Validation

To 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 needed

βš™οΈ Configuration - Customize Your Security

Tweak 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'
end

🧩 Creating Custom Refinements

Need 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::MyCustomRefinements

⚠️ Important Security Notes

Remember, this gem is like a club bouncer with a flashlight - helpful, but not invincible:

  1. Ruby refinements only work within their scope - they won't protect outside that scope
  2. Clever attackers might find ways around the bouncer
  3. For critical security, consider OS-level sandboxing too
  4. This is one layer of your security onion, not the whole onion!

πŸ“œ License

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.

About

πŸ™Š Monkey-patching Ruby for Safety πŸ›‘οΈ

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published