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
- Ruby Integration
- Docker Compose Deployment
- Kubernetes Deployment
- Deny-by-Default Explained
- Hash-Chained Audit Logs
Try SafeClaw
Action-level gating for AI agents. Set it up in your browser in 60 seconds.
$ npx @authensor/safeclaw