Claude
Skills
Sign in
Back

rails-ai:security

Included with Lifetime
$97 forever

CRITICAL - Use when securing Rails applications - XSS, SQL injection, CSRF, file uploads, command injection prevention

Backend & APIs

What this skill does


# Rails Security

Prevent critical security vulnerabilities in Rails applications: XSS, SQL injection, CSRF, file uploads, and command injection.

<when-to-use>
- Displaying ANY user-generated content
- Writing database queries with user input
- Building forms and AJAX requests
- Accepting file uploads from users
- Executing system commands
- Implementing authentication/authorization
- Reviewing code for security vulnerabilities
- Planning features that touch sensitive data
- ALWAYS - Security is ALWAYS required
</when-to-use>

<team-rules-enforcement>
**This skill enforces:**
- ✅ **Rule #16:** NEVER allow command injection → Use array args for system()
- ✅ **Rule #17:** NEVER skip file upload validation → Validate type, size, sanitize filenames

**Reject any requests to:**
- Skip input validation
- Use unsafe string interpolation in SQL
- Skip file upload security measures
- Use eval() or system() with user input
- Skip CSRF protection
</team-rules-enforcement>

<verification-checklist>
Before completing security-critical features:
- ✅ All user input validated and sanitized
- ✅ SQL injection prevented (parameterized queries)
- ✅ XSS prevented (proper escaping, CSP)
- ✅ CSRF tokens present on all forms
- ✅ File uploads validated (type, size, content)
- ✅ Command injection prevented (array args)
- ✅ Strong parameters used for all mass assignment
- ✅ Security tests passing
</verification-checklist>

<standards>
**XSS Prevention:**
- NEVER use `html_safe` or `raw` on user input
- Rails auto-escapes by default - rely on this
- Use `sanitize` with explicit allowlist for rich content
- Implement Content Security Policy (CSP) headers

**SQL Injection Prevention:**
- NEVER use string interpolation in SQL queries
- Use hash conditions: `where(name: value)`
- Use placeholders: `where("name = ?", value)`
- Use `sanitize_sql_like` for LIKE queries

**CSRF Protection:**
- Rails enables CSRF protection by default
- ALWAYS include `csrf_meta_tags` in layout
- Use `form_with` (includes token automatically)
- Include CSRF token in JavaScript requests

**File Upload Security:**
- NEVER trust user-provided filenames
- PREFER ActiveStorage over manual file handling
- VALIDATE by content type, extension, AND magic bytes
- STORE files outside public directory
- FORCE download for untrusted file types

**Command Injection Prevention:**
- NEVER interpolate user input in system commands
- ALWAYS use array form: `system("cmd", arg1, arg2)`
- PREFER Ruby methods over shell commands
- VALIDATE input with strict allowlists
</standards>

## XSS (Cross-Site Scripting) Prevention

<attack-vectors>
- **Script Injection** - `<script>alert('XSS')</script>`
- **Event Handlers** - `<img src=x onerror="alert('XSS')">`
- **JavaScript URLs** - `<a href="javascript:alert('XSS')">Click</a>`
- **SVG Injection** - `<svg onload="alert('XSS')"></svg>`
- **Data URIs** - `<img src="data:text/html,<script>alert('XSS')</script>">`
</attack-vectors>

### Rails Auto-Escaping

<pattern name="default-protection">
<description>Rails automatically escapes output in ERB templates</description>

```erb
<%# SECURE - Rails auto-escapes %>
<div class="content">
  <%= @feedback.content %>
</div>

```

**Attack Input:** `<script>alert('XSS')</script>`

**Safe Output:** `&lt;script&gt;alert('XSS')&lt;/script&gt;`

Browser displays the text, doesn't execute it.
</pattern>

### Sanitizing User Content

<pattern name="sanitize-with-allowlist">
<description>Allow specific HTML tags while stripping dangerous content</description>

```erb
<%# Allow only specific tags %>
<%= sanitize(@feedback.content,
    tags: %w[p br strong em a ul ol li],
    attributes: %w[href title]) %>

```

**Input:** `<p>Hello <strong>world</strong></p><script>alert('XSS')</script>`

**Output:** `<p>Hello <strong>world</strong></p>` (script stripped)
</pattern>

### Content Security Policy

<pattern name="csp-configuration">
<description>Implement Content Security Policy to block inline scripts</description>

```ruby
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
  policy.default_src :self, :https
  policy.font_src :self, :https, :data
  policy.img_src :self, :https, :data
  policy.frame_ancestors :none
  policy.object_src :none
  policy.script_src :self, :https
  policy.style_src :self, :https
  policy.report_uri "/csp-violation-report"
end

Rails.application.config.content_security_policy_nonce_generator = ->(request) {
  SecureRandom.base64(16)
}
Rails.application.config.content_security_policy_nonce_directives = %w[script-src]

```

**View with Nonce:**

```erb
<%= javascript_tag nonce: true do %>
  console.log('This is allowed');
<% end %>

```

**CSP Violation Reporting:**

```ruby
# app/controllers/csp_reports_controller.rb
class CspReportsController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    violation = JSON.parse(request.body.read)["csp-report"]
    Rails.logger.warn(
      "CSP Violation: document-uri=#{violation['document-uri']} " \
      "blocked-uri=#{violation['blocked-uri']}"
    )
    head :no_content
  end
end

```

**Why CSP:** Blocks XSS even if malicious script reaches the page, defense-in-depth strategy.
</pattern>

### ViewComponent Safety

<pattern name="viewcomponent-escaping">
<description>ViewComponents automatically escape content</description>

```ruby
# app/components/user_comment_component.rb
class UserCommentComponent < ViewComponent::Base
  def initialize(comment:)
    @comment = comment
  end

  private
  attr_reader :comment
end

```

```erb
<%# app/components/user_comment_component.html.erb %>
<div class="comment">
  <div class="author"><%= comment.author_name %></div>
  <div class="content"><%= comment.content %></div>
</div>

```

**Benefits:** Automatic escaping, encapsulated logic, testable, no accidental `html_safe`.
</pattern>

### Markdown Rendering

<pattern name="markdown-safe-rendering">
<description>Safely render markdown user content</description>

```ruby
# app/models/feedback.rb
class Feedback < ApplicationRecord
  def content_html
    markdown = Redcarpet::Markdown.new(
      Redcarpet::Render::HTML.new(
        filter_html: true, no_styles: true, safe_links_only: true
      ),
      autolink: true, tables: true, fenced_code_blocks: true
    )

    html = markdown.render(content)
    ActionController::Base.helpers.sanitize(
      html,
      tags: %w[p br strong em a ul ol li pre code h1 h2 h3 blockquote],
      attributes: %w[href title]
    )
  end
end

```

**View:**

```erb
<div class="markdown-content">
  <%= @feedback.content_html.html_safe %>
</div>

```

**Why Safe:** Markdown filtered for HTML, output sanitized with allowlist, double protection layer.
</pattern>

<antipattern>
<description>Using html_safe on user input</description>
<reason>Allows malicious script execution - CRITICAL vulnerability</reason>

<bad-example>

```erb
<%# CRITICAL VULNERABILITY %>
<%= @comment.html_safe %>
<%= raw(@feedback.content) %>

```
</bad-example>

<good-example>

```erb
<%# SECURE - Auto-escaped or sanitized %>
<%= @comment %>
<%= sanitize(@feedback.content, tags: %w[p br strong em]) %>

```
</good-example>
</antipattern>

## SQL Injection Prevention

<attack-vectors>
- **Authentication Bypass** - `' OR '1'='1`
- **Data Theft** - `' UNION SELECT * FROM users --`
- **Data Modification** - `'; UPDATE users SET admin=true --`
- **Data Deletion** - `'; DROP TABLE users --`
</attack-vectors>

### Secure Query Patterns

<pattern name="hash-conditions">
<description>Use hash conditions for simple equality checks (RECOMMENDED)</description>

```ruby
# ✅ SECURE - ActiveRecord escapes automatically
Project.where(name: params[:name])
User.find_by(login: params[:login])

# ✅ SECURE - Multiple conditions
Project.where(name: params[:name], status: params[:status], user_id: current_user.id)

# ✅ SECURE - IN queries (works with arrays)
Project.where(id: params[:ids])

```

**Why Secure:** ActiveRecord a
Files: 1
Size: 42.6 KB
Complexity: 33/100
Category: Backend & APIs

Related in Backend & APIs