Files
gh-nbarthel-claudy-plugins-…/skills/rails-security-patterns/skill.md
2025-11-30 08:42:29 +08:00

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.**