312 lines
6.6 KiB
Markdown
312 lines
6.6 KiB
Markdown
---
|
|
name: rails-security-patterns
|
|
description: Automatically validates security best practices and prevents vulnerabilities
|
|
auto_invoke: true
|
|
trigger_on: [file_create, file_modify]
|
|
file_patterns: ["**/controllers/**/*.rb", "**/models/**/*.rb"]
|
|
tags: [rails, security, authentication, authorization, sql-injection]
|
|
priority: 1
|
|
version: 2.0
|
|
---
|
|
|
|
# Rails Security Patterns Skill
|
|
|
|
Auto-validates security best practices and blocks common vulnerabilities.
|
|
|
|
## What This Skill Does
|
|
|
|
**Automatic Security Checks:**
|
|
- Strong parameters in controllers (prevents mass assignment)
|
|
- SQL injection prevention (parameterized queries)
|
|
- CSRF token handling (API mode considerations)
|
|
- Authentication presence
|
|
- Authorization checks
|
|
|
|
**When It Activates:**
|
|
- Controller files created or modified
|
|
- Model files with database queries modified
|
|
- Authentication-related changes
|
|
|
|
## Security Checks
|
|
|
|
### 1. Strong Parameters
|
|
|
|
**Checks:**
|
|
- Every `create` and `update` action uses strong parameters
|
|
- No direct `params` usage in model instantiation
|
|
- `permit` calls include only expected attributes
|
|
|
|
**Example Violation:**
|
|
```ruby
|
|
# BAD
|
|
def create
|
|
@user = User.create(params[:user]) # ❌ Mass assignment
|
|
end
|
|
|
|
# GOOD
|
|
def create
|
|
@user = User.create(user_params) # ✅ Strong params
|
|
end
|
|
|
|
private
|
|
|
|
def user_params
|
|
params.require(:user).permit(:name, :email)
|
|
end
|
|
```
|
|
|
|
**Skill Output:**
|
|
```
|
|
❌ Security: Mass assignment vulnerability
|
|
Location: app/controllers/users_controller.rb:15
|
|
Issue: params[:user] used directly without strong parameters
|
|
|
|
Fix: Define strong parameters method:
|
|
private
|
|
|
|
def user_params
|
|
params.require(:user).permit(:name, :email, :role)
|
|
end
|
|
|
|
Then use: @user = User.create(user_params)
|
|
```
|
|
|
|
### 2. SQL Injection Prevention
|
|
|
|
**Checks:**
|
|
- No string interpolation in `where` clauses
|
|
- Parameterized queries used
|
|
- No raw SQL without placeholders
|
|
|
|
**Example Violation:**
|
|
```ruby
|
|
# BAD
|
|
User.where("email = '#{params[:email]}'") # ❌ SQL injection
|
|
User.where("name LIKE '%#{params[:query]}%'") # ❌ SQL injection
|
|
|
|
# GOOD
|
|
User.where("email = ?", params[:email]) # ✅ Parameterized
|
|
User.where("name LIKE ?", "%#{params[:query]}%") # ✅ Safe
|
|
User.where(email: params[:email]) # ✅ Hash syntax
|
|
```
|
|
|
|
**Skill Output:**
|
|
```
|
|
❌ Security: SQL injection vulnerability
|
|
Location: app/models/user.rb:45
|
|
Issue: String interpolation in SQL query
|
|
|
|
Vulnerable code:
|
|
User.where("email = '#{email}'")
|
|
|
|
Fix: Use parameterized query:
|
|
User.where("email = ?", email)
|
|
|
|
Or use hash syntax:
|
|
User.where(email: email)
|
|
```
|
|
|
|
### 3. Authentication Checks
|
|
|
|
**Checks:**
|
|
- Controllers have authentication filters
|
|
- Sensitive actions require authentication
|
|
- Token-based auth for API endpoints
|
|
|
|
**Example:**
|
|
```ruby
|
|
# app/controllers/posts_controller.rb
|
|
class PostsController < ApplicationController
|
|
before_action :authenticate_user! # ✅ Auth required
|
|
|
|
def index
|
|
# ...
|
|
end
|
|
end
|
|
```
|
|
|
|
**Skill Output (if missing):**
|
|
```
|
|
⚠️ Security: No authentication found
|
|
Location: app/controllers/admin/users_controller.rb
|
|
Issue: Admin controller without authentication
|
|
|
|
Recommendation: Add authentication:
|
|
before_action :authenticate_user!
|
|
before_action :require_admin!
|
|
```
|
|
|
|
### 4. Authorization Checks
|
|
|
|
**Checks:**
|
|
- Update/destroy actions verify ownership
|
|
- Role-based access control present
|
|
- Resource-level authorization
|
|
|
|
**Example:**
|
|
```ruby
|
|
# BAD
|
|
def destroy
|
|
@post = Post.find(params[:id])
|
|
@post.destroy # ❌ No ownership check
|
|
end
|
|
|
|
# GOOD
|
|
def destroy
|
|
@post = current_user.posts.find(params[:id]) # ✅ Scoped to user
|
|
@post.destroy
|
|
end
|
|
|
|
# BETTER
|
|
def destroy
|
|
@post = Post.find(params[:id])
|
|
authorize @post # ✅ Using Pundit/CanCanCan
|
|
@post.destroy
|
|
end
|
|
```
|
|
|
|
**Skill Output:**
|
|
```
|
|
⚠️ Security: Missing authorization check
|
|
Location: app/controllers/posts_controller.rb:42
|
|
Issue: destroy action without ownership verification
|
|
|
|
Recommendation: Add authorization:
|
|
Option 1 (scope to user):
|
|
@post = current_user.posts.find(params[:id])
|
|
|
|
Option 2 (use authorization gem):
|
|
authorize @post # Pundit
|
|
authorize! :destroy, @post # CanCanCan
|
|
```
|
|
|
|
### 5. Sensitive Data Exposure
|
|
|
|
**Checks:**
|
|
- No passwords in logs
|
|
- API keys not hardcoded
|
|
- Secrets use environment variables
|
|
|
|
**Example Violation:**
|
|
```ruby
|
|
# BAD
|
|
API_KEY = "sk_live_abc123..." # ❌ Hardcoded secret
|
|
|
|
# GOOD
|
|
API_KEY = ENV['STRIPE_API_KEY'] # ✅ Environment variable
|
|
```
|
|
|
|
**Skill Output:**
|
|
```
|
|
❌ Security: Hardcoded secret detected
|
|
Location: config/initializers/stripe.rb:3
|
|
Issue: API key hardcoded in source
|
|
|
|
Fix: Use environment variable:
|
|
API_KEY = ENV['STRIPE_API_KEY']
|
|
|
|
Add to .env (don't commit):
|
|
STRIPE_API_KEY=sk_live_your_key_here
|
|
```
|
|
|
|
## Integration with Pre-commit Hook
|
|
|
|
This skill works with the pre-commit hook to block unsafe commits:
|
|
|
|
**Automatic blocks:**
|
|
- SQL injection vulnerabilities
|
|
- Missing strong parameters in create/update actions
|
|
- Hardcoded secrets/API keys
|
|
- Mass assignment vulnerabilities
|
|
|
|
**Warnings (allow commit):**
|
|
- Missing authentication (might be intentional for public endpoints)
|
|
- Missing authorization (might use custom logic)
|
|
- Complex queries (performance concern, not security)
|
|
|
|
## Configuration
|
|
|
|
Create `.rails-security.yml` to customize:
|
|
|
|
```yaml
|
|
# .rails-security.yml
|
|
strong_parameters:
|
|
enforce: true
|
|
block_commit: true
|
|
|
|
sql_injection:
|
|
enforce: true
|
|
block_commit: true
|
|
|
|
authentication:
|
|
require_for_controllers: true
|
|
exceptions:
|
|
- Api::V1::PublicController
|
|
- PagesController
|
|
|
|
authorization:
|
|
warn_on_missing: true
|
|
block_commit: false
|
|
|
|
secrets:
|
|
detect_patterns:
|
|
- "sk_live_"
|
|
- "api_key"
|
|
- "password"
|
|
- "secret"
|
|
block_commit: true
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### API Authentication
|
|
|
|
**Token-based:**
|
|
```ruby
|
|
class Api::BaseController < ActionController::API
|
|
before_action :authenticate_token!
|
|
|
|
private
|
|
|
|
def authenticate_token!
|
|
token = request.headers['Authorization']&.split(' ')&.last
|
|
@current_user = User.find_by(api_token: token)
|
|
render json: { error: 'Unauthorized' }, status: :unauthorized unless @current_user
|
|
end
|
|
end
|
|
```
|
|
|
|
### Scope to User
|
|
|
|
**Pattern:**
|
|
```ruby
|
|
# Always scope to current_user when possible
|
|
@posts = current_user.posts
|
|
@post = current_user.posts.find(params[:id])
|
|
|
|
# Prevents accessing other users' resources
|
|
```
|
|
|
|
### Rate Limiting
|
|
|
|
**Recommendation:**
|
|
```ruby
|
|
# Gemfile
|
|
gem 'rack-attack'
|
|
|
|
# config/initializers/rack_attack.rb
|
|
Rack::Attack.throttle('api/ip', limit: 100, period: 1.minute) do |req|
|
|
req.ip if req.path.start_with?('/api/')
|
|
end
|
|
```
|
|
|
|
## References
|
|
|
|
- **OWASP Top 10**: https://owasp.org/www-project-top-ten/
|
|
- **Rails Security Guide**: https://guides.rubyonrails.org/security.html
|
|
- **Pattern Library**: /patterns/authentication-patterns.md
|
|
|
|
---
|
|
|
|
**This skill runs automatically and blocks security vulnerabilities before they reach production.**
|