--- name: rails-ai:security description: CRITICAL - Use when securing Rails applications - XSS, SQL injection, CSRF, file uploads, command injection prevention --- # Rails Security Prevent critical security vulnerabilities in Rails applications: XSS, SQL injection, CSRF, file uploads, and command injection. - 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 **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 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 **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 ## XSS (Cross-Site Scripting) Prevention - **Script Injection** - `` - **Event Handlers** - `` - **JavaScript URLs** - `Click` - **SVG Injection** - `` - **Data URIs** - `` ### Rails Auto-Escaping Rails automatically escapes output in ERB templates ```erb <%# SECURE - Rails auto-escapes %>
<%= @feedback.content %>
``` **Attack Input:** `` **Safe Output:** `<script>alert('XSS')</script>` Browser displays the text, doesn't execute it.
### Sanitizing User Content Allow specific HTML tags while stripping dangerous content ```erb <%# Allow only specific tags %> <%= sanitize(@feedback.content, tags: %w[p br strong em a ul ol li], attributes: %w[href title]) %> ``` **Input:** `

Hello world

` **Output:** `

Hello world

` (script stripped)
### Content Security Policy Implement Content Security Policy to block inline scripts ```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. ### ViewComponent Safety ViewComponents automatically escape content ```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 %>
<%= comment.author_name %>
<%= comment.content %>
``` **Benefits:** Automatic escaping, encapsulated logic, testable, no accidental `html_safe`.
### Markdown Rendering Safely render markdown user content ```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
<%= @feedback.content_html.html_safe %>
``` **Why Safe:** Markdown filtered for HTML, output sanitized with allowlist, double protection layer.
Using html_safe on user input Allows malicious script execution - CRITICAL vulnerability ```erb <%# CRITICAL VULNERABILITY %> <%= @comment.html_safe %> <%= raw(@feedback.content) %> ``` ```erb <%# SECURE - Auto-escaped or sanitized %> <%= @comment %> <%= sanitize(@feedback.content, tags: %w[p br strong em]) %> ``` ## SQL Injection Prevention - **Authentication Bypass** - `' OR '1'='1` - **Data Theft** - `' UNION SELECT * FROM users --` - **Data Modification** - `'; UPDATE users SET admin=true --` - **Data Deletion** - `'; DROP TABLE users --` ### Secure Query Patterns Use hash conditions for simple equality checks (RECOMMENDED) ```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 automatically escapes values and prevents injection. Use ? placeholders for complex queries ```ruby # ✅ SECURE - Single placeholder Project.where("name = ?", params[:name]) Project.where("created_at > ?", 1.week.ago) # ✅ SECURE - Multiple placeholders User.where("login = ? AND status = ? AND created_at > ?", params[:login], "active", 1.month.ago) # ✅ SECURE - Complex conditions Feedback.where("status = ? AND (priority = ? OR created_at < ?)", params[:status], "high", 1.day.ago) ``` **Why Secure:** Rails escapes each parameter value, preventing injection. Safely handle LIKE queries with wildcards ```ruby # ✅ SECURE - Escape special LIKE characters (% -> \%, _ -> \_) search_term = Book.sanitize_sql_like(params[:title]) Book.where("title LIKE ?", "#{search_term}%") # ✅ SECURE - Case-insensitive search search_term = Book.sanitize_sql_like(params[:query]) Book.where("LOWER(title) LIKE LOWER(?)", "%#{search_term}%") ``` **Why Sanitize:** Without `sanitize_sql_like`, users could inject `%` or `_` wildcards. Using string interpolation in queries CRITICAL - Allows arbitrary SQL injection ```ruby # ❌ CRITICAL VULNERABILITY Project.where("name = '#{params[:name]}'") # Attack: params[:name] = "' OR '1'='1" - Returns ALL projects User.find_by("login = '#{params[:login]}' AND password = '#{params[:password]}'") # Attack: params[:login] = "admin'--" - Bypasses password check # ❌ CRITICAL - Data exfiltration Project.where("id = #{params[:id]}") # Attack: params[:id] = "1 UNION SELECT id,email,password,1,1 FROM users" ``` ```ruby # ✅ SECURE - Use placeholders Project.where("name = ?", params[:name]) User.find_by("login = ? AND password = ?", params[:login], params[:password]) # ✅ BETTER - Use hash conditions Project.where(name: params[:name]) User.find_by(login: params[:login], password: params[:password]) # ✅ SECURE - Type conversion prevents injection Project.where(id: params[:id].to_i) ``` ### Dynamic ORDER BY Clauses Safely build ORDER BY from user input with allowlist ```ruby # ✅ SECURE - Allowlist approach ALLOWED_SORT_COLUMNS = %w[name created_at status priority].freeze ALLOWED_DIRECTIONS = %w[ASC DESC].freeze def index column = ALLOWED_SORT_COLUMNS.include?(params[:sort]) ? params[:sort] : "created_at" direction = ALLOWED_DIRECTIONS.include?(params[:direction]&.upcase) ? params[:direction] : "DESC" @projects = Project.order("#{column} #{direction}") end ``` **Why Secure:** User input limited to predefined safe values, SQL injection impossible. Building ORDER BY from user input Allows column enumeration and SQL injection ```ruby # ❌ VULNERABLE Project.order("#{params[:sort]} #{params[:direction]}") # Attack: params[:sort] = "name); DROP TABLE projects; --" ``` ```ruby # ✅ SECURE - Allowlist only allowed = %w[name created_at] column = allowed.include?(params[:sort]) ? params[:sort] : "created_at" Project.order(column) ``` ### ActiveRecord Query Methods Use ActiveRecord methods for automatic protection ```ruby # ✅ SECURE - All ActiveRecord methods are safe Project.find(params[:id]) Project.find_by(name: params[:name]) Project.where(status: params[:status]) Project.order(:created_at) Project.limit(10) Project.offset(params[:page].to_i * 10) Project.joins(:user) Project.includes(:comments) Project.group(:category) Project.having("COUNT(*) > ?", 5) # ✅ SECURE - Scopes class Project < ApplicationRecord scope :active, -> { where(status: "active") } scope :by_user, ->(user_id) { where(user_id: user_id) } scope :search, ->(term) { sanitized = sanitize_sql_like(term) where("name LIKE ?", "%#{sanitized}%") } end Project.active.by_user(params[:user_id]).search(params[:query]) ``` **Why Secure:** ActiveRecord automatically escapes all parameters. ## CSRF (Cross-Site Request Forgery) Protection - **Hidden Form Attack** - Malicious site auto-submits form to your app - **Image Tag Attack** - `` - **AJAX Attack** - JavaScript fetch/XHR to your endpoints - **Auto-Submit Form** - JavaScript automatically submits hidden form ### Rails Default Protection Rails enables CSRF protection by default ```ruby # app/controllers/application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception # Raises ActionController::InvalidAuthenticityToken if token invalid end ``` **Why :exception is Best:** Makes failures visible, prevents silent bypasses, forces proper error handling. ### Form Protection form_with automatically includes CSRF token ```erb <%# ✅ SECURE - Token included automatically %> <%= form_with model: @feedback do |form| %> <%= form.text_field :content %> <%= form.text_field :recipient_email %> <%= form.submit "Submit" %> <% end %> ``` **Generated HTML:** ```html
``` **Why Secure:** Rails validates token matches session.
### JavaScript Protection Include CSRF meta tags for JavaScript access ```erb <%# app/views/layouts/application.html.erb %> My App <%= csrf_meta_tags %> ``` Include CSRF token in fetch requests ```javascript // ✅ SECURE - Extract token from meta tag const csrfToken = document.head.querySelector("meta[name=csrf-token]")?.content; fetch("/feedbacks", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken }, body: JSON.stringify({ feedback: { content: "test", recipient_email: "user@example.com" } }) }); ``` **Why Secure:** Rails checks `X-CSRF-Token` header matches session token. Skipping CSRF for session-based authentication CRITICAL - Allows attackers to perform actions as authenticated users ```ruby # ❌ CRITICAL VULNERABILITY class FeedbacksController < ApplicationController skip_before_action :verify_authenticity_token def create @feedback = current_user.feedbacks.create!(feedback_params) redirect_to @feedback end end ``` ```ruby # ✅ SECURE - Keep CSRF protection enabled class FeedbacksController < ApplicationController # protect_from_forgery inherited from ApplicationController def create @feedback = current_user.feedbacks.create!(feedback_params) redirect_to @feedback end end ``` ### Rails Request.js Library Use @rails/request.js for automatic CSRF handling **Installation:** ```bash npm install @rails/request.js ``` **Usage:** ```javascript // ✅ SECURE - Token automatically included import { post, patch, destroy } from '@rails/request.js' await post('/feedbacks', { body: JSON.stringify({ feedback: { content: "test" } }), contentType: 'application/json', responseKind: 'json' }) await patch('/feedbacks/123', { body: JSON.stringify({ feedback: { status: "reviewed" } }) }) await destroy('/feedbacks/123', { responseKind: 'json' }) ``` **Why Recommended:** Automatic CSRF token handling, consistent API, Rails-aware error handling. ### API Endpoints Skip CSRF for stateless API endpoints with token auth ```ruby # app/controllers/api/v1/base_controller.rb class Api::V1::BaseController < ApplicationController skip_before_action :verify_authenticity_token before_action :authenticate_api_token private def authenticate_api_token token = request.headers["Authorization"]&.split(" ")&.last @current_api_user = User.find_by(api_token: token) head :unauthorized unless @current_api_user end end ``` **Why Skip CSRF for APIs:** API clients use Bearer tokens (not cookies), tokens must be explicitly sent, CSRF only affects cookie-based authentication. ### Error Handling Handle CSRF failures with user-friendly error messages ```ruby # app/controllers/application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception rescue_from ActionController::InvalidAuthenticityToken do |exception| Rails.logger.warn( "CSRF failure: #{exception.message} IP: #{request.remote_ip} Path: #{request.fullpath}" ) sign_out_user if user_signed_in? redirect_to root_path, alert: "Your session has expired. Please log in again." end private def sign_out_user cookies.delete(:user_token) reset_session end end ``` **Why Important:** Users see helpful error, security events logged, stale sessions cleared. ### SameSite Cookies Use SameSite cookie attribute for defense-in-depth ```ruby # config/initializers/session_store.rb Rails.application.config.session_store :cookie_store, key: '_myapp_session', same_site: :lax, # Prevents CSRF from external sites secure: Rails.env.production?, # HTTPS only in production httponly: true, # Not accessible via JavaScript expire_after: 24.hours ``` **SameSite Options:** - `:lax` (RECOMMENDED) - Allows top-level navigation, blocks embedded requests - `:strict` - Most secure, blocks ALL cross-site requests (may break OAuth) - `:none` - Allows all cross-site requests (requires secure: true) **Why Use SameSite:** Defense-in-depth complements CSRF tokens, blocks many attacks without token. ## Secure File Uploads - **Path Traversal** - `../../config/database.yml` overwrites config files - **Malicious File Types** - `.exe`, `.php`, `.html` files containing malware - **Content Type Spoofing** - File claims to be image but is script - **Magic Bytes Bypass** - Extension doesn't match actual file type - **XSS via SVG** - SVG files containing embedded JavaScript - **Denial of Service** - Large files consuming disk/memory ### ActiveStorage (Recommended) Use ActiveStorage for automatic security handling **Model:** ```ruby class Feedback < ApplicationRecord has_one_attached :screenshot has_many_attached :documents validates :screenshot, content_type: ["image/png", "image/jpeg", "image/gif"], size: { less_than: 5.megabytes } validates :documents, content_type: ["application/pdf", "text/plain"], size: { less_than: 10.megabytes } end ``` **Controller:** ```ruby class FeedbacksController < ApplicationController def create @feedback = Feedback.new(feedback_params) if @feedback.save redirect_to @feedback, notice: "Feedback created" else render :new, status: :unprocessable_entity end end private def feedback_params params.expect(feedback: [:content, :recipient_email, :screenshot, documents: []]) end end ``` **View:** ```erb <%= form_with model: @feedback do |f| %> <%= f.file_field :screenshot, accept: "image/*" %> <%= f.file_field :documents, multiple: true, accept: ".pdf,.txt" %> <%= f.submit %> <% end %> ``` **Why Secure:** Automatic filename sanitization, storage outside public/, signed URLs with expiration. ### File Type Validation Validate file types by content type, extension, AND magic bytes ```ruby class Feedback < ApplicationRecord has_one_attached :image validate :acceptable_image private def acceptable_image return unless image.attached? unless image.content_type.in?(%w[image/jpeg image/png image/gif]) errors.add(:image, "must be a JPEG, PNG, or GIF") end unless image.filename.to_s.match?(/\.(jpe?g|png|gif)\z/i) errors.add(:image, "must have a valid extension") end unless valid_image_signature? errors.add(:image, "file signature doesn't match declared type") end if image.byte_size > 5.megabytes errors.add(:image, "must be less than 5MB") end end def valid_image_signature? image.open do |file| magic_bytes = file.read(8) return false unless magic_bytes # JPEG: FF D8 FF, PNG: 89 50 4E 47, GIF: 47 49 46 38 magic_bytes[0..2] == "\xFF\xD8\xFF" || magic_bytes[0..7] == "\x89PNG\r\n\x1A\n" || magic_bytes[0..3] == "GIF8" end rescue => e Rails.logger.error("Image validation error: #{e.message}") false end end ``` **Why Triple Validation:** Content-Type can be spoofed, extension can be faked, magic bytes verify actual format. ### Secure File Serving Serve files via controller with proper security headers ```ruby class DownloadsController < ApplicationController before_action :authenticate_user! def show @feedback = Feedback.find(params[:feedback_id]) head :forbidden and return unless can_download?(@feedback) @document = @feedback.documents.find(params[:id]) send_data @document.download, filename: @document.filename.to_s.gsub(/[^\w.-]/, "_"), type: @document.content_type, disposition: "attachment" # Force download, never inline end private def can_download?(feedback) feedback.user == current_user || current_user.admin? end end ``` **Why Secure:** Authentication + authorization enforced, `Content-Disposition: attachment` prevents XSS. ### Dangerous File Types Force binary download for dangerous file types ```ruby # config/initializers/active_storage.rb Rails.application.config.active_storage.content_types_to_serve_as_binary.tap do |types| types << "image/svg+xml" # SVG with embedded JavaScript types << "text/html" << "application/xhtml+xml" # HTML scripts types << "text/xml" << "application/xml" # XML entities types << "application/javascript" << "text/javascript" end Rails.application.config.active_storage.content_types_allowed_inline = %w[ image/png image/jpeg image/gif image/bmp image/webp application/pdf ] ``` **Why Important:** SVG/HTML files can contain JavaScript that executes when viewed, enabling XSS. ### File Size Limits Implement multiple layers of file size protection **Application-Wide:** ```ruby # config/application.rb config.active_storage.max_file_size = 100.megabytes ``` **Web Server (Nginx):** ```nginx client_max_body_size 100M; ``` **Model-Specific:** ```ruby class Feedback < ApplicationRecord has_one_attached :avatar has_many_attached :photos validates :avatar, size: { less_than: 2.megabytes } validates :photos, size: { less_than: 5.megabytes }, limit: { max: 10 } end ``` **Why Multiple Layers:** Web server rejects huge uploads early, application-wide limit prevents resource exhaustion, model limits enforce business rules. ### Virus Scanning Scan uploaded files for malware in production **Setup:** `gem "clamby"` + ClamAV (`apt-get install clamav clamav-daemon`) ```ruby class Feedback < ApplicationRecord has_one_attached :file validate :file_not_infected, if: -> { file.attached? } private def file_not_infected return unless Rails.env.production? file.open do |temp_file| unless Clamby.safe?(temp_file.path) errors.add(:file, "contains malware or virus") Rails.logger.warn("Malware detected: user_id=#{user_id}, filename=#{file.filename}") end end rescue Clamby::ClambyScanError => e Rails.logger.error("Virus scan failed: #{e.message}") end end ``` **Why Critical:** Prevent viruses, ransomware, and malware from infecting users or servers. ### ActiveStorage Variants Use ActiveStorage variants for secure image processing ```ruby class Feedback < ApplicationRecord has_one_attached :image def thumbnail image.variant(resize_to_limit: [100, 100], format: :png, saver: { quality: 85 }) end def medium image.variant(resize_to_limit: [400, 400], format: :png) end end ``` **View:** ```erb <%= image_tag @feedback.thumbnail, alt: "Feedback screenshot" %> ``` **Why Secure:** Variants re-encode images (stripping metadata/exploits), format conversion prevents attacks. Trusting user-provided filenames CRITICAL - Enables path traversal and file overwrite attacks ```ruby # ❌ CRITICAL VULNERABILITY def upload filename = params[:file].original_filename File.open("uploads/#{filename}", "wb") { |f| f.write(params[:file].read) } end # Attack: filename = "../../config/database.yml" - Overwrites database config! # ❌ CRITICAL - Serving from public directory path = Rails.root.join("public/uploads/#{params[:file].original_filename}") File.open(path, "wb") { |f| f.write(params[:file].read) } # Attacker uploads malicious.html with Hello") assert_not_includes feedback.content_html, "" click_button "Submit" assert_text "" # Escaped, not executed end test "form includes CSRF token" do visit new_feedback_path assert_selector "input[name='authenticity_token'][type='hidden']" end end # test/jobs/pdf_generation_job_test.rb class PdfGenerationJobTest < ActiveJob::TestCase # Command Injection Prevention test "validates output path format" do assert_raises(ArgumentError, /Invalid output path/) do PdfGenerationJob.perform_now(feedbacks(:one).id, "invalid_path.pdf") end end test "prevents directory traversal in path" do assert_raises(ArgumentError, /Invalid output path|outside allowed/i) do PdfGenerationJob.perform_now(feedbacks(:one).id, "../../../etc/passwd") end end test "rejects command injection in path" do assert_raises(ArgumentError) do PdfGenerationJob.perform_now(feedbacks(:one).id, "output.pdf; rm -rf /") end end end # test/controllers/downloads_controller_test.rb class DownloadsControllerTest < ActionDispatch::IntegrationTest test "requires authentication for file downloads" do feedback = feedbacks(:one) get feedback_download_path(feedback, feedback.documents.first) assert_redirected_to login_path end test "serves file with secure headers" do sign_in users(:user) feedback = users(:user).feedbacks.first get feedback_download_path(feedback, feedback.documents.first) assert_response :success assert_equal "attachment", response.headers["Content-Disposition"].split(";").first end test "prevents unauthorized access to other users files" do sign_in users(:user) other_feedback = users(:other_user).feedbacks.first get feedback_download_path(other_feedback, other_feedback.documents.first) assert_response :forbidden end end ``` ## Security Checklist ### Before Deploying **XSS Prevention:** - [ ] Never use `html_safe` or `raw` on user input - [ ] Implement Content Security Policy headers - [ ] Test with `` in all user inputs - [ ] Review all `sanitize` calls have explicit allowlists - [ ] Verify ViewComponents used for complex rendering **SQL Injection Prevention:** - [ ] No string interpolation in SQL queries (`"WHERE name = '#{value}'"`) - [ ] All queries use hash conditions or placeholders - [ ] `sanitize_sql_like` used for LIKE queries - [ ] ORDER BY uses allowlist validation - [ ] Test with `'; DROP TABLE users; --` in search inputs **CSRF Protection:** - [ ] `csrf_meta_tags` in application layout - [ ] All forms use `form_with` (includes token) - [ ] JavaScript requests include `X-CSRF-Token` header - [ ] API endpoints properly skip CSRF (with token auth) - [ ] Test POST/DELETE without CSRF token fails **File Upload Security:** - [ ] ActiveStorage used (or manual filename sanitization) - [ ] Triple validation: content type + extension + magic bytes - [ ] File size limits at application and model levels - [ ] Dangerous file types force download (SVG, HTML) - [ ] Files stored outside public directory - [ ] Virus scanning in production - [ ] Test with renamed .php→.jpg file **Command Injection Prevention:** - [ ] No string interpolation in system commands - [ ] Array form used: `system("cmd", arg1, arg2)` - [ ] Input validation with strict allowlists - [ ] Ruby methods preferred over shell commands - [ ] Path validation prevents directory traversal - [ ] Test with `; rm -rf /` in file paths ### Security Headers ```ruby # config/application.rb config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-Content-Type-Options' => 'nosniff', 'X-XSS-Protection' => '1; mode=block', 'Referrer-Policy' => 'strict-origin-when-cross-origin' } ``` ### Production Monitoring **Log Security Events:** - CSRF failures - CSP violations - Malware detection in uploads - Failed authentication attempts - SQL injection attempts (unusual queries) - Command injection attempts **Alert On:** - Multiple CSRF failures from same IP - Malware detected in uploads - CSP violation patterns - Repeated authentication failures - SQL error patterns in logs - rails-ai:controllers - Strong parameters for mass assignment protection - rails-ai:models - Input validation patterns - rails-ai:views - XSS prevention in templates - rails-ai:testing - Security testing strategies **Official Documentation:** - [Rails Guides - Securing Rails Applications](https://guides.rubyonrails.org/security.html) **Security Standards:** - [OWASP Top 10](https://owasp.org/www-project-top-ten/) - Most critical web app security risks - [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) - [OWASP SQL Injection Prevention](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html) - [OWASP CSRF Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) - [OWASP File Upload Security](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html) - [OWASP Command Injection](https://owasp.org/www-community/attacks/Command_Injection)