2025-12-15 · Authensor

AI Agent Safety for Ruby on Rails Applications

SafeClaw by Authensor integrates into Ruby on Rails applications as a service object pattern, providing deny-by-default action gating for AI agent tool calls. Every system() call, File.read(), and Net::HTTP.get() your agent attempts is gated against your YAML policy before execution. Install with npx @authensor/safeclaw and wire it into your Rails controllers and service objects.

Why Rails AI Agents Need Gating

Rails applications are adding AI agent capabilities for content management, data processing, and customer support. These agents run in the same process as your Rails app, with access to ActiveRecord, the filesystem, shell execution, and outbound HTTP. A compromised agent can read config/database.yml, execute rails db:drop, or exfiltrate data.

SafeClaw's 446 tests validate every gate decision. The hash-chained audit trail provides tamper-proof logging.

Installation

npx @authensor/safeclaw

Policy

version: 1
defaultAction: deny

rules:
- action: file.read
path:
glob: "/app/storage/**"
decision: allow

- action: file.write
path:
glob: "/app/tmp/output/**"
decision: allow

- action: process.exec
command:
startsWith: "rails runner"
decision: prompt

- action: network.request
host:
in: ["api.openai.com", "api.anthropic.com"]
decision: allow

- action: file.read
path:
glob: "*/config/credentials"
decision: deny

- action: file.read
path:
glob: "**/config/database.yml"
decision: deny

- action: process.exec
command:
contains: "db:drop"
decision: deny

Rails Service Object

# app/services/safe_claw_gate.rb
class SafeClawGate
  include Singleton

ENDPOINT = ENV.fetch('SAFECLAW_ENDPOINT', 'http://localhost:9800')

def check(action_request)
uri = URI("#{ENDPOINT}/check")
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req.body = action_request.to_json

response = Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
JSON.parse(response.body, symbolize_names: true)
rescue StandardError => e
Rails.logger.error("SafeClaw unreachable: #{e.message}")
{ allowed: false, reason: 'SafeClaw unreachable' }
end

def require!(action_request)
decision = check(action_request)
unless decision[:allowed]
raise SecurityError, "SafeClaw denied: #{decision[:reason]}"
end
decision
end
end

Controller Integration

# app/controllers/agent_controller.rb
class AgentController < ApplicationController
  skip_before_action :verify_authenticity_token

def read_file
gate = SafeClawGate.instance
gate.require!(action: 'file.read', path: params[:path])

content = File.read(params[:path])
render json: { content: content }
rescue SecurityError => e
render json: { error: e.message }, status: :forbidden
end

def exec_command
gate = SafeClawGate.instance
gate.require!(action: 'process.exec', command: params[:command])

output = #{params[:command]}
render json: { output: output }
rescue SecurityError => e
render json: { error: e.message }, status: :forbidden
end

def fetch_url
gate = SafeClawGate.instance
host = URI(params[:url]).host
gate.require!(
action: 'network.request',
host: host,
url: params[:url],
method: 'GET'
)

response = Net::HTTP.get(URI(params[:url]))
render json: { body: response }
rescue SecurityError => e
render json: { error: e.message }, status: :forbidden
end
end

Routes

# config/routes.rb
Rails.application.routes.draw do
  namespace :agent do
    post 'read_file', to: 'agent#read_file'
    post 'exec', to: 'agent#exec_command'
    post 'fetch', to: 'agent#fetch_url'
  end
end

Rake Task for Audit

# lib/tasks/safeclaw.rake
namespace :safeclaw do
  desc 'Export SafeClaw audit trail'
  task audit: :environment do
    response = Net::HTTP.get(URI('http://localhost:9800/audit'))
    entries = JSON.parse(response)
    entries.each do |entry|
      puts "#{entry['timestamp']} | #{entry['action']} | #{entry['decision']} | #{entry['hash'][0..11]}"
    end
  end
end

MIT licensed, provider-agnostic (Claude and OpenAI), hash-chained audit.

Cross-References

Try SafeClaw

Action-level gating for AI agents. Set it up in your browser in 60 seconds.

$ npx @authensor/safeclaw