I Got Paranoid About Security, So I Vibe-Coded a Rails Engine (Use at Your Own Risk)
{{pixgeist:65e5d26a-930b-437b-b538-a2ff22474eb7}}
TL;DR
Built Beskar - a Rails security engine with WAF, impossible travel detection, and auto-banning. Named after Mandalorian armor because layered protection. Mostly vibe-coded. Currently running in monitor-only mode on Humadroid because I’m not quite paranoid enough to trust my own paranoia gem. You probably shouldn’t use it yet. But here it is anyway.
The SOC 2 Paranoia Spiral
I’m going through SOC 2 compliance for Humadroid right now. Not because it’s optional - when you’re building compliance management SaaS, you kind of need to be compliant yourself. Hard to sell “we’ll help you pass audits” when you can’t pass your own.
The audit process does something interesting to your brain. You start looking at your Rails app differently.
“Is Devise enough? Probably. But what if someone’s trying to brute force accounts right now and I don’t even know? What if there’s a bot scanning for /wp-admin
even though this is a Rails app? What if someone logs in from Poland and then Tokyo five minutes later because their credentials leaked?”
Your app is probably fine.
But what if it’s not?
The Gap Nobody Filled
Here’s what I already had:
- Devise - Handles authentication. Great at that.
- Rack::Attack - Throttling and blocking predefined IPs. Also great at that.
But there’s this gap between “basic security hygiene” and “paranoid enough to actually sleep at night.”
Rack::Attack will throttle requests. But it won’t notice patterns. It won’t ban an IP after they’ve pinged /wp-admin
, /phpmyadmin
, and /.env
in succession. You have to define every blocking rule yourself.
Devise will secure user accounts. But it won’t stop someone from logging into a compromised account from physically impossible locations. There’s no impossible travel detection. No risk scoring. No automatic account locking when things look suspicious.
I wanted something that sits in the middle. Another layer. Something that learns from behavior and responds automatically.
So I built it.
Enter Beskar
The name comes from the Mandalorian armor in Star Wars. Beskar is rare metal used to forge armor in layers - one layer stops blasters, another layer stops lightsabers, the whole thing together is basically indestructible.
That’s the concept here. Not replacing your existing security - layering on top of it.
Plus I was probably procrastinating by watching The Mandalorian when I had the idea. We’ll never know for sure.
What It Actually Does
Beskar is a Rails engine you drop into your app. Installation is deliberately simple:
# Gemfile
gem 'beskar'
# Install
rails g beskar:install
rails db:migrate
# Add to your User model
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
include Beskar::Models::SecurityTrackable
end
That’s it. Now you have:
1. Web Application Firewall (WAF)
The WAF watches for vulnerability scanning patterns. An LLM helped me generate the initial pattern list (because why reinvent the wheel when AI is decent at “show me common exploit paths”):
VULNERABILITY_PATTERNS = {
wordpress: {
patterns: [
%r{/wp-admin}i,
%r{/wp-login\.php}i,
%r{/xmlrpc\.php}i
],
severity: :high,
description: "WordPress vulnerability scan"
},
config_files: {
patterns: [
%r{/\.env},
%r{/\.git},
%r{/database\.yml}
],
severity: :critical,
description: "Configuration file access attempt"
},
# ... 5 more categories
}
Someone hits three of these patterns? They’re flagged. Hit the threshold (configurable, default is 3)? Auto-banned.
2. Impossible Travel Detection
User logs in from Poland. Five minutes later, same account tries to log in from Tokyo.
Unless they have a very impressive private jet, that’s not the same person.
Beskar tracks geolocation (using MaxMind GeoLite2 - you provide your own database due to licensing). If the physics don’t work out, the account gets locked automatically.
# Risk-based locking in action
config.risk_based_locking = {
enabled: true,
risk_threshold: 75, # Lock when risk >= 75
lock_strategy: :devise_lockable,
auto_unlock_time: 1.hour
}
The risk score considers:
- Geographic anomalies (impossible travel, high-risk countries)
- Device fingerprints (suspicious user agents, bot signatures)
- Login patterns (velocity, time of day, recent failures)
- IP reputation
3. Smart Rate Limiting
This goes beyond “10 requests per minute.” Beskar tracks patterns:
- Brute force on single account - One IP hammering one user
- Distributed attack - Multiple IPs targeting one user
- Credential stuffing - One IP trying many accounts
- Mixed patterns - Complex attack signatures
After detecting the pattern, it responds accordingly. Single account getting hit from multiple IPs? Lock the account. One IP trying credential stuffing? Ban the IP.
4. IP Whitelisting
You can whitelist trusted IPs (office networks, VPN gateways, security scanners). They bypass all blocking but still get logged for audit purposes.
config.ip_whitelist = [
"203.0.113.0/24", # Office network
"198.51.100.50", # VPN gateway
"2001:db8::1" # IPv6 address
]
CIDR notation supported. IPv6 supported. Because it’s 2025 and we should probably handle that.
5. Persistent IP Banning
Banned IPs are stored in both cache and database. Survives application restarts. Auto-bans happen for:
- 10+ failed authentication attempts in 1 hour
- 5+ rate limit violations in 1 hour
- 3+ WAF violations (configurable)
Repeat offenders get escalating ban durations: 1h → 6h → 24h → 7d → permanent.
The Vibe-Coding Reality
Here’s the honest part: most of this was vibe-coded.
I had a clear vision of what I wanted - layered security, automatic responses, minimal configuration. But the implementation? A lot of “let’s try this pattern and see if it works.”
The WAF pattern matching? Started with LLM suggestions, then tested against logs.
The risk scoring algorithm? Iterated until the numbers felt right.
The middleware integration? Refactored three times before it felt clean.
Nothing broke dramatically during development. But I haven’t battle-tested it on production traffic yet either. That’s why…
Monitor-Only Mode
Humadroid is running Beskar right now.
In monitor-only mode.
config.waf = {
enabled: true,
monitor_only: true, # Log everything, block nothing
create_security_events: true
}
Why? Because I’m paranoid about security, but I’m also paranoid about locking out legitimate users with buggy security code.
Monitor mode lets me see what would get blocked without actually blocking anyone. I’m watching the logs, tuning thresholds, making sure the patterns make sense.
Eventually I’ll flip monitor_only: false
and trust my own code.
Eventually.
The Performance Reality
Yes, Beskar adds overhead. Middleware analysis, pattern matching, database queries, cache operations - it all takes time.
How much? In practice: barely noticeable.
The middleware checks run in this order:
- Whitelist check (O(n) where n=entries, ~0.5ms for typical lists)
- Banned IP check (O(1) cache lookup, ~0.3ms)
- Rate limit check (O(k) cache-based where k=recent attempts, ~1ms)
- WAF analysis (O(m) pattern matching where m=~40 patterns, ~3-5ms)
Total overhead: typically 5-10ms per request.
For context:
- Most Rails middleware adds 1-5ms
- A typical DB query is 5-20ms
- Your application logic is likely 100-500ms+
The 5-10ms overhead is a worthwhile trade-off for comprehensive security protection.
Who Should Use This
Probably nobody yet.
I’m using it. In monitor mode. On my own app.
But here’s who might want to use it once I’m confident enough to run it in blocking mode:
- Founders who are paranoid - If SOC 2 / ISO 27001 audits are keeping you up at night
- Apps with user accounts - Where account takeover is a real risk
- Anyone getting scanned - Check your logs - you’re probably getting hit by bots right now
- Rails apps needing defense in depth - Not instead of your existing security, on top of it
Who shouldn’t use it:
- Anyone risk-averse - This is a work in progress. Bugs are likely.
- High-traffic apps where 10ms matters - The overhead might be too much
- Teams that can’t tune thresholds - You’ll need to adjust settings for your specific patterns
- Anyone expecting enterprise-grade polish - This was vibe-coded by one developer
What’s Next
Everything is still work in progress. Down the road I want to add better integration with authorization libraries like Pundit or CanCanCan. Catch users trying to do things they’re not authorized to do and block them automatically.
The impossible travel detection hasn’t caught anything real yet. Which is good - it means no one’s compromised a Humadroid account. But it also means I haven’t seen it work in production.
The WAF patterns are fixed for now. I might make them configurable eventually. Or add a way to define custom patterns.
There’s a dashboard coming. Eventually. For visualizing security events in real-time.
But for now, it does what I need: another layer of paranoia between my users and the internet.
The Point
I built this because SOC 2 audits make you paranoid and existing tools didn’t scratch that particular itch.
Is it perfect? No.
Is it battle-tested? Not yet.
Will I use it on Humadroid? Yes, but in monitor mode until I’m confident.
Should you use it? Probably not. But it’s open source, so feel free to clone it, break it, fix it, and send PRs.
The gem is called Beskar. Install at your own risk. Test thoroughly. Start with monitor mode. Don’t blame me if it locks out your entire user base.
But if you’re the kind of developer who lies awake thinking “what if someone’s scanning my Rails app for WordPress vulnerabilities right now,” maybe it’s worth a look.
That’s all I’ve got. Back to tuning thresholds and watching logs.
Want another layer of paranoia? Check out Beskar on GitHub. Or don’t. I’m not evangelizing here. Just sharing what I built while obsessing over SOC 2.
And if you’re wondering why I’m so paranoid about security, I’m building Humadroid - GRC/compliance automation for SMBs. When your entire value proposition is “we keep your data secure,” you tend to care a lot about actually keeping data secure.
(See? Layers of paranoia all the way down.)