Project

otto

0.01
There's a lot of open issues
No release in over a year
Otto: Auto-define your rack-apps in plaintext.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Runtime

~> 3.1, < 4.0
~> 3.3, >= 3.3.6
~> 0.6.3
~> 3.1
~> 2.20
 Project Readme

Otto - A Ruby Gem

Define your rack-apps in plain-text with built-in security.

v2.0.0-pre6 Available: This pre-release includes major improvements to middleware management, logging, and request callback handling. See changelog for details and upgrade notes.

Otto mascot

Otto apps have three files: a rackup file, a Ruby class, and a routes file. The routes file is just plain text that maps URLs to Ruby methods.

$ cd myapp && ls
config.ru app.rb routes

Why Otto?

  • Security by Default: Automatic IP masking for public addresses, user agent anonymization, CSRF protection, and input validation
  • Privacy First: Masks public IPs, strips user agent versions, provides country-level geo-location only—no external APIs needed
  • Simple Routing: Define routes in plain-text files with zero configuration overhead
  • Built-in Authentication: Multiple strategies including API keys, tokens, role-based access, and custom implementations
  • Developer Friendly: Works with any Rack server, minimal dependencies, easy testing and debugging

Routes File

# routes

GET   /                         App#index
POST  /feedback                 App#receive_feedback
GET   /product/:id              App#show_product
GET   /robots.txt               App#robots_text
GET   /404                      App#not_found

Ruby Class

# app.rb

class App
  def initialize(req, res)
    @req, @res = req, res
  end

  def index
    res.body = '<h1>Hello Otto</h1>'
  end

  def show_product
    product_id = req.params[:id]
    res.body = "Product: #{product_id}"
  end

  def robots_text
    res.header['Content-Type'] = "text/plain"
    rules = 'User-agent: *', 'Disallow: /private/keep/out'
    res.body = rules.join($/)
  end
end

Rackup File

# config.ru

require 'otto'
require 'app'

run Otto.new("./routes")

Security Features

Otto includes optional security features for production apps:

# Enable security features
app = Otto.new("./routes", {
  csrf_protection: true,      # CSRF tokens and validation
  request_validation: true,   # Input sanitization and limits
  trusted_proxies: ['10.0.0.0/8']
})

Security features include CSRF protection, input validation, security headers, and trusted proxy configuration.

Privacy by Default

Otto automatically masks public IP addresses and anonymizes user agents to comply with GDPR, CCPA, and other privacy regulations:

# Public IPs are automatically masked (203.0.113.9 → 203.0.113.0)
# Private IPs are NOT masked by default (127.0.0.1, 192.168.x.x, 10.x.x.x)
app = Otto.new("./routes")

# User agents: versions stripped for privacy
# Geo-location: country-level only, no external APIs or databases
# IP hashing: daily-rotating hashes enable analytics without tracking

Private and localhost IPs are exempted by default for development convenience, but this behavior can be customized via configure_ip_privacy() method. Geolocation uses CDN headers (Cloudflare, AWS, etc.) with fallback to IP ranges—no external services required. See CLAUDE.md for detailed configuration options.

Internationalization Support

Otto provides built-in locale detection and management:

# Global configuration (affects all Otto instances)
Otto.configure do |opts|
  opts.available_locales = { 'en' => 'English', 'es' => 'Spanish', 'fr' => 'French' }
  opts.default_locale = 'en'
end

# Or configure during initialization
app = Otto.new("./routes", {
  available_locales: { 'en' => 'English', 'es' => 'Spanish', 'fr' => 'French' },
  default_locale: 'en'
})

# Or configure at runtime
app.configure(
  available_locales: { 'en' => 'English', 'es' => 'Spanish' },
  default_locale: 'en'
)

# Legacy support (still works)
app = Otto.new("./routes", {
  locale_config: {
    available_locales: { 'en' => 'English', 'es' => 'Spanish', 'fr' => 'French' },
    default_locale: 'en'
  }
})

In your application, use the locale helper:

class App
  def initialize(req, res)
    @req, @res = req, res
  end

  def show_product
    # Automatically detects locale from:
    # 1. URL parameter: ?locale=es
    # 2. User preference (if provided)
    # 3. Accept-Language header
    # 4. Default locale
    locale = req.check_locale!

    # Use locale for localized content
    res.body = localized_content(locale)
  end
end

The locale helper checks multiple sources in order of precedence and validates against your configured locales.

Examples

Otto includes comprehensive examples demonstrating different features:

Standalone Tutorials

See the examples/ directory for more.

Requirements

  • Ruby 3.2+
  • Rack 3.1+

Installation

gem install otto

Documentation

  • CLAUDE.md - Comprehensive developer guidance covering authentication architecture, configuration freezing, IP privacy, structured logging, and multi-app patterns
  • docs/ - Technical guides and migration guides
  • CHANGELOG.rst - Version history, breaking changes, and upgrade notes

AI Development Assistance

Version 1.2.0's security features were developed with AI assistance:

  • Zed Agent (Claude Sonnet 4) - Security implementation and testing
  • Claude Desktop - Rack 3+ compatibility and debugging
  • GitHub Copilot - Code completion

The maintainer remains responsible for all security decisions and implementation. We believe in transparency about development tools, especially for security-focused software.

License

See LICENSE.txt