{"id":13538252,"url":"https://github.com/brunofacca/zen-rails-security-checklist","last_synced_at":"2025-04-08T10:21:34.910Z","repository":{"id":55099380,"uuid":"87855245","full_name":"brunofacca/zen-rails-security-checklist","owner":"brunofacca","description":"Checklist of security precautions for Ruby on Rails applications.","archived":false,"fork":false,"pushed_at":"2020-03-09T11:42:57.000Z","size":82,"stargazers_count":1818,"open_issues_count":1,"forks_count":149,"subscribers_count":76,"default_branch":"master","last_synced_at":"2025-04-01T08:44:07.466Z","etag":null,"topics":["checklist","rails","ruby","ruby-on-rails","security","security-vulnerability"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brunofacca.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-10T20:36:14.000Z","updated_at":"2025-03-31T18:23:55.000Z","dependencies_parsed_at":"2022-08-14T12:00:15.979Z","dependency_job_id":null,"html_url":"https://github.com/brunofacca/zen-rails-security-checklist","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brunofacca%2Fzen-rails-security-checklist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brunofacca%2Fzen-rails-security-checklist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brunofacca%2Fzen-rails-security-checklist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brunofacca%2Fzen-rails-security-checklist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brunofacca","download_url":"https://codeload.github.com/brunofacca/zen-rails-security-checklist/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247819943,"owners_count":21001394,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["checklist","rails","ruby","ruby-on-rails","security","security-vulnerability"],"created_at":"2024-08-01T09:01:08.744Z","updated_at":"2025-04-08T10:21:34.879Z","avatar_url":"https://github.com/brunofacca.png","language":"Ruby","funding_links":[],"categories":["Introduction","\u003ca id=\"a4ee2f4d4a944b54b2246c72c037cd2e\"\u003e\u003c/a\u003e收集\u0026\u0026集合","\u003ca id=\"8c5a692b5d26527ef346687e047c5c21\"\u003e\u003c/a\u003e收集","Ruby","Ruby (88)","\u003ca id=\"e97d183e67fa3f530e7d0e7e8c33ee62\"\u003e\u003c/a\u003e未分类","Articles \u0026 Guides","\u003ca id=\"Programming-Languages\"\u003e\u003c/a\u003eProgramming Languages"],"sub_categories":["Rails","\u003ca id=\"e97d183e67fa3f530e7d0e7e8c33ee62\"\u003e\u003c/a\u003e未分类","\u003ca id=\"f110da0bf67359d3abc62b27d717e55e\"\u003e\u003c/a\u003e新添加的"],"readme":"# Zen Rails Security Checklist\n\n## Summary\nThis document provides a not necessarily comprehensive list of security measures\nto be implemented when developing a Ruby on Rails application. It is designed to\nserve as a quick reference and minimize vulnerabilities caused by developer\nforgetfulness. It does not replace developer training on secure coding\nprinciples and how they can be applied.\n\nDescribing how each security vulnerability works is outside the scope of this\ndocument. Links to external resources containing further information are\nprovided in the corresponding sections of the checklist. Please apply only the\nsuggestions you thoroughly understand.\n\nPlease keep in mind that security is a moving target. New vulnerabilities and\nattack vectors are discovered every day. We suggest you try to keep up to date,\nfor instance, by subscribing to security mailing lists related to the software\nand libraries you are using.\n\nThis checklist is meant to be a community-driven resource. Your \n[contributions](#contributing) are welcome! \n\n**Disclaimer**: This document does not cover all possible security \nvulnerabilities. The authors do not take any legal responsibility for the \naccuracy or completeness of the information herein.\n\n## Supported Rails Versions\nThis document focuses on Rails 4 and 5. Vulnerabilities that were present in \nearlier versions and fixed in Rails 4 are not included.\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n## Table of Contents\n\n- [The Checklist](#the-checklist)\n    - [Injection](#injection)\n    - [Authentication](#authentication)\n    - [Sessions \u0026 Cookies](#sessions--cookies)\n    - [Cross-Site Scripting (XSS)](#cross-site-scripting-xss)\n        - [Handling User Input](#handling-user-input)\n        - [Output Escaping \u0026 Sanitization](#output-escaping--sanitization)\n        - [XSS protection in HAML templates](#xss-protection-in-haml-templates)\n        - [Content Security Policy (CSP)](#content-security-policy-csp)\n    - [Insecure Direct Object Reference](#insecure-direct-object-reference)\n    - [HTTP \u0026 TLS](#http--tls)\n        - [Security-related headers](#security-related-headers)\n    - [Memcached Security](#memcached-security)\n    - [Authorization (Pundit)](#authorization-pundit)\n    - [Files](#files)\n        - [File Uploads](#file-uploads)\n        - [File Downloads](#file-downloads)\n    - [Cross-Site Request Forgery (CSRF)](#cross-site-request-forgery-csrf)\n    - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors)\n    - [Sensitive Data Exposure](#sensitive-data-exposure)\n        - [Credentials](#credentials)\n    - [Routing, Template Selection, and Redirection](#routing-template-selection-and-redirection)\n    - [Third-party Software](#third-party-software)\n    - [Security Tools](#security-tools)\n    - [Testing](#testing)\n    - [Others](#others)\n- [Details and Code Samples](#details-and-code-samples)\n    - [Command Injection example](#command-injection-example)\n    - [Password validation regex](#password-validation-regex)\n    - [Pundit: ensure all actions are authorized](#pundit-ensure-all-actions-are-authorized)\n    - [Pundit: only display appropriate records in select boxes](#pundit-only-display-appropriate-records-in-select-boxes)\n    - [rack-cors configuration](#rack-cors-configuration)\n    - [Convert filter_parameters into a whitelist](#convert-filter_parameters-into-a-whitelist)\n    - [Throttling Requests](#throttling-requests)\n    - [HAML: XSS protection](#haml-xss-protection)\n- [Authors](#authors)\n- [Contributing](#contributing)\n- [TODO](#todo)\n- [References and Further Reading](#references-and-further-reading)\n- [License](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\nTable of contents generated by [DocToc](https://github.com/thlorenz/doctoc).\n\n## The Checklist\n\n#### Injection \nInjection attacks are #1 at the [OWASP Top10](https://www.owasp.org/index.php/Top_10_2013-Top_10). \n- [ ] Don’t use standard Ruby interpolation (`#{foo}`) to insert user inputted\nstrings into ActiveRecord or raw SQL queries. Use the `?` character, named bind\nvariables or the [ActiveRecord::Sanitization\nmethods](http://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_conditions)\nto sanitize user input used in DB queries. *Mitigates SQL injection attacks.*\n- [ ] Don't pass user inputted strings to methods capable of evaluating \ncode or running O.S. commands such as `eval`, `system`, `syscall`, `%x()`,\n`open`, `popen\u003cn\u003e`, `File.read`, `File.write`, and `exec`. Using regular\nexpressions is a good way to sanitize it ([code sample](#command-injection-example)).\n*Mitigates command injection attacks.*\n\nResources:\n- [Ruby on Rails Security Guide - SQL Injection](http://guides.rubyonrails.org/security.html#sql-injection)\n- [Rails SQL Injection Examples](https://rails-sqli.org/)\n- [Step Up the Security of Your Rails App | Part 1](https://www.ombulabs.com/blog/rails/security/rails-security.html)\n\n#### Authentication\nBroken Authentication and Session Management are #2 at the [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10).\n- [ ] Avoid rolling your own authentication unless you know **exactly** what you\nare doing. Consider using a gem such as\n[Devise](https://github.com/plataformatec/devise),\n[Authlogic](https://github.com/binarylogic/authlogic) or\n[Clearance](https://github.com/thoughtbot/clearance). *Mitigates dozens of\npotential vulnerabilities.*\n- [ ] Enforce a minimum password length of 8 characters or more. *Mitigates\nbrute-force attacks.*\n    - Devise: set `config.password_length = 8..128` in\n    `config/initializers/devise.rb`. \n- [ ] Consider validating passwords against:\n    - Dictionary words. Since passwords have a minimum length requirement, the\n    dictionary need only include words meeting that requirement. \n    - A list of commonly used passwords such as\n    [these](https://github.com/danielmiessler/SecLists/tree/master/Passwords).\n    The [StrongPassword](https://github.com/bdmac/strong_password) gem provide\n    such feature.\n    - A leaked password database such as [PasswordPing](https://www.passwordping.com/docs-passwords-api/).\n    - Context-specific words, such as the name of the application, the\n    username, and derivatives thereof.\n- [ ] Consider the pros and cons of enforcing password complexity rules such as\nmixtures of different character types. Most applications use it. However, the\nlatest [NIST Guidelines](https://pages.nist.gov/800-63-3/sp800-63b.html) advise\nagainst it. An alternative is to increase the minimum length requirement and\nencourage the usage of passphrases. *Mitigate brute-force attacks.*\n    - Devise: use a Devise-specific gem such as\n    [devise-security](https://github.com/devise-security/devise-security),\n    [devise_zxcvbn](https://github.com/bitzesty/devise_zxcvbn) or one of the \n    following authentication-agnostic solutions.\n    - The [StrongPassword](https://github.com/bdmac/strong_password) gem or a\n    regex validation ([code sample](#password-validation-regex)) should work\n    with most authentication setups.\n- [ ] Lock the account after multiple failed login attempts. *Mitigates \nbrute-force attacks.*\n    - Devise: activate the [lockable\n    module](https://github.com/plataformatec/devise/wiki/How-To:-Add-:lockable-to-Users).\n- [ ] Require users to confirm their e-mail addresses on sign-up and when the \ne-mail address is changed. *Mitigates the creation of bogus accounts with \nnon-existing or third-party e-mails.*\n    - Devise: use the [confirmable\n    module](https://github.com/plataformatec/devise/wiki/How-To:-Add-:confirmable-to-Users)\n    and set `config.reconfirmable = true` in `config/initializers/devise.rb`. \n- [ ] Require users to input their old password on password change. *Mitigates\nunauthorized password changes on session hijacking, CSRF or when a user forgets \nto log out and leaves the PC or mobile device unattended.*\n    - Devise: does that by default\n- [ ] Expire the session at log out and expire old sessions at every successful\nlogin. *Mitigates CSRF, session hijacking and session fixation attacks by \nreducing their time-frame.*\n    - Devise: does that by default.\n- [ ] Expire sessions after a period of inactivity (e.g., 30 minutes).\n*Mitigates CSRF, session hijacking and session fixation attacks by reducing\ntheir time-frame.* \n    - Devise: use the [timeoutable\n    module](http://www.rubydoc.info/github/plataformatec/devise/Devise/Models/Timeoutable).\n- [ ] Notify user via email on password change. *Does not prevent an attacker\nfrom changing the victim's password, but warns the victim so he can contact the\nsystem administrator to revoke the attacker's access.*\n    - Devise: set `config.send_password_change_notification = true` in\n    `config/initializers/devise.rb`.\n- [ ] Use generic error messages such as \"Invalid email or password\" instead of\nspecifying which part (e-mail or password) is invalid. *Mitigates [user\nenumeration](https://www.owasp.org/index.php/Testing_for_user_enumeration_(OWASP-AT-002))\nand brute-force attacks.*\n    - Devise: setting `config.paranoid = true` in\n    `config/initializers/devise.rb` will protect the `confirmable`,\n    `recoverable` and `unlockable` modules against user enumeration. To protect\n    the `registerable` module, add a captcha to the registration page (see\n    [instructions in the Devise\n    Wiki](https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise)).\n- [ ] Ensure all non-public controllers/actions require authentication. *Avoid\nunauthorized access due to developer forgetfulness.*\n    - Devise: add `before_action :authenticate_user!` to \n    `ApplicationController` and\n`skip_before_action :authenticate_user!` to publicly accessible\ncontrollers/actions. \n- [ ] Consider using two-factor authentication (2FA) as provided by\n[Authy](https://www.authy.com/). *Provides a highly effective extra layer of\nauthentication security.*\n    - Devise: see\n    the [devise-two-factor](https://github.com/tinfoil/devise-two-factor) and\n    [authy-devise](https://github.com/authy/authy-devise) gems. \n- [ ] Consider requiring authentication in `config/routes.rb`. Requiring\nauthentication in both controllers and routes may not be DRY, but such\nredundancy provides additional security (see [Defense in\ndepth](https://en.wikipedia.org/wiki/Defense_in_depth_(computing))). \n    - Devise: Place non-public resources within a `authenticate :user do` block\n    (see the [Devise\n    Wiki](https://github.com/plataformatec/devise/wiki/How-To:-Define-resource-actions-that-require-authentication-using-routes.rb)).\n- [ ] Consider limiting the number of simultaneous sessions per account. *May\n reduce application exposure on account compromise (e.g. leaked passwords).*\n    - Devise: use the\n    [devise-security](https://github.com/devise-security/devise-security)\n    gem.\n- [ ] Avoid implementing \"security questions\" such as \"What is your mother's\nmaiden name?\" as their answers may be reused across multiple sites and easily\nfound by means of [social\nengineering](https://en.wikipedia.org/wiki/Social_engineering_(security)). See\n[this article](https://www.wired.com/2016/09/time-kill-security-questions-answer-lies/).\n- [ ] If using role-based access control (RBAC), do not include the role \nattribute in the strong parameters of the controller(s) used for user \nregistration and profile editing. *Prevent malicious users from assigning admin \nrole to themselves.* \n    - Devise: Do not pass the role parameter key to \n    `devise_parameter_sanitizer.permit`.\n- [ ] Consider restricting administrator access by IP. If the client's IP is\ndynamic, restrict by IP block/ASN or by country via IP geolocation.\n\n#### Sessions \u0026 Cookies\nBroken Authentication and Session Management are #2 at the [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10).\n- [ ] Don't store data such as money/point balances or user privileges in a\ncookie or a CookieStore Session. Store it in the database instead. *Mitigates \nreplay attacks.*\n- [ ] Consider always using encrypted cookies. This is the default behavior in  \nRails 4+ when `secret_key_base` is set. *Strengthens cookie encryption and \nmitigates multiple attacks involving cookie tampering.*\n- [ ] Unless your JavaScript frontend needs to read cookies generated by the\nRails server, set all cookies as `httponly`. Search the project for cookie\naccessors and add `httponly: true`. Example: `cookies[:login] = {value: 'user',\nhttponly: true}`. *Restricts cookie access to the Rails server. Mitigates\nattackers from using the victim's browser JavaScript to steal cookies after a\n successful XSS attack.*\n\nResources:\n- [Ruby on Rails Security Guide - Sessions](http://guides.rubyonrails.org/security.html#sessions)\n\n#### Cross-Site Scripting (XSS)\nXSS is #3 at the [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10).\n\n###### Handling User Input\n- [ ] Always validate user input that may eventually be displayed to other\nusers. Attempting to blacklist characters, strings or sanitize input tends to be\nineffective ([see examples of how to bypass such\nblacklists](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet)). A\nwhitelisting approach is usually safer. *Mitigates multiple XSS attacks.*\n- [ ] Consider using the\n[loofah-activerecord](https://github.com/flavorjones/loofah-activerecord) gem\n to scrub your model attribute values. *Mitigates multiple XSS attacks*.\n- [ ] If you must create links from user inputted URLs, be sure to validate\nthem. In particular, it should be possible to limit URL schemes to http/https\nin nearly all cases. The URL passed to `link_to` (the second argument) will be\nHTML escaped. However, `link_to` allows any scheme for the URL. If using regex,\nensure that the string **begins** with the expected protocol(s), as in\n`\\Ahttps?`. *Mitigates XSS attacks such as entering\n`javascript:dangerous_stuff()//http://www.some-legit-url.com` as a website URL\nor a dangerous `data:` payload that is displayed to other users (e.g., in a\nuser profile page).*\n- [ ] When using regex for input validation, use `\\A` and `\\z` to match string \nbeginning and end. Do **not** use `^` and `$` as anchors. *Mitigates XSS \nattacks that involve slipping JS code after line breaks, such as \n`me@example.com\\n\u003cscript\u003edangerous_stuff();\u003c/script\u003e`.* \n- [ ] Do not trust validations implemented at the client (frontend) as most \nimplementations can be bypassed. Always (re)validate at the server.\n\n###### Output Escaping \u0026 Sanitization\n- [ ] Escape all HTML output. Rails does that by default, but calling\n`html_safe` or `raw` at the view suppresses escaping. Look for calls to these\nmethods in the entire project, check if you are generating HTML from\nuser-inputted strings and if those strings are effectively validated. Note that\nthere are [dozens of ways to evade\nvalidation](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet). If\npossible, avoid calling `html_safe` and `raw` altogether. Most templating\nlibraries also provide a way of skipping escaping. ERB uses the double `==`:\n`\u003c%== params[:query] %\u003e`. For custom scrubbing, see\n[ActionView::Helpers::SanitizeHelper](http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html)\n*Mitigates XSS attacks.*\n- [ ] Always enclose attribute values with double quotes. Even without\n`html_safe`, it is possible to introduce cross-site scripting into templates\nwith unquoted attributes. In the following code\n`\u003cp class=\u003c%= params[:style] %\u003e...\u003c/p\u003e`, an attacker can insert a space into\nthe style parameter and suddenly the payload is outside the attribute value and\nthey can insert their own payload. And when a victim mouses over the paragraph,\nthe XSS payload will fire. *Mitigates XSS attacks.*\n- [ ] Rendering JSON inside of HTML templates is tricky. You can't just HTML\nescape JSON, especially when inserting it into a script context, because\ndouble-quotes will be escaped and break the code. But it isn't safe to not\nescape it, because browsers will treat a `\u003c/script\u003e` tag as HTML no matter\nwhere it is. The Rails documentation recommends always using `json_escape`\njust in case `to_json` is overridden or the value is not valid JSON.\n*Mitigates XSS attacks.*\n- [ ] Be careful when using `render inline: ...`. The value passed in will be\ntreated like an ERB template by default. Take a look at this code:\n`render inline: \"Thanks #{@user.name}!\"`. Assuming users can set their own\nname, an attacker might set their name to `\u003c%= rm -rf / %\u003e` which will execute\n`rm -rf /` on the server! This is called Server Side Template Injection and it\nallows arbitrary code execution (RCE) on the server. If you must use an inline\ntemplate treat all input the same as you would in a regular ERB template:\n`render inline: \"Thanks \u003c%= @user.name %\u003e\"`. *Mitigates XSS attacks.*\n- [ ] Avoid sending user inputted strings in e-mails to other users. Attackers\nmay enter a malicious URL in a free text field that is not intended to contain\nURLs and does not provide URL validation. Most e-mail clients display URLs as\nlinks. *Mitigates XSS, phishing, malware infection and other attacks.*\n- [ ] If an I18n key ends up with `_html`, it will automatically be marked as html safe while the key interpolations will be escaped! See [(example code)](#when-i18n-key-ends-up-with-_html).\n\n###### XSS protection in HAML templates\n\n- [ ] Be careful when using `!=` in Haml and it should be made sure that no\nuser data is rendered unescaped. The `!=` notation in Haml works the way\n`\u003c%= raw(…) %\u003e` works in ERB. See [(example code)](#haml-xss-protection).\n\nResources:\n- [Ruby on Rails Security Guide - XSS](http://guides.rubyonrails.org/security.html#cross-site-scripting-xss)\n- [OWASP XSS Filter Evasion Cheat Sheet](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet)\n- [OWASP Ruby on Rails Cheatsheet - Cross-site Scripting (XSS)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Cross-site_Scripting_.28XSS.29)\n- [Plataformatec Blog - The new HTML sanitizer in Rails 4.2](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2)\n- [Brakeman Pro - Cross-Site Scripting in Rails](https://brakemanpro.com/2017/09/08/cross-site-scripting-in-rails)\n- [Preventing security issues in Rails](https://www.railscarma.com/blog/technical-articles/preventing-security-issues-rails/)\n- [Security tips for rails apps](https://drivy.engineering/security-tips-for-rails-apps/)\n\n###### Content Security Policy (CSP)\n- [ ] Content Security Policy (CSP) is an added layer of security that helps to\ndetect and mitigate various types of attacks on our web applications, including\nCross Site Scripting (XSS) and data injection attacks.\n\nResources:\n- [Rails 5.2 DSL for configuring Content Security Policy](https://blog.bigbinary.com/2018/10/23/rails-5-2-adds-dsl-for-configuring-content-security-policy-header.html)\n\n#### Insecure Direct Object Reference\n- [ ] An IDOR issue arises when the user is supposed to have access to url\n`\"/get/post/6\"`, for example, but not `\"/get/post/9\"` but the system does not\nproperly check those permissions. And if we change \"6\" in the URL, what happens?\nWe can see the data of all users. This may be due to the fact that the data was\ngenerated as follows: `@user = User.find_by(id: params[:user_id])` – which is\nbasically getting the ID from the GET parameter in the URL. Instead a more\nsecure way of doing this is setting the `@user` parameter based on the\n`\"current_user\"` session variable like this: `@user = current_user`.\n\nResources:\n- [Rails Vulnerabilities and Where To Find Them – Part 1](https://www.vdalabs.com/2018/01/12/rails-vulnerabilities-find-part-1/)\n\n#### HTTP \u0026 TLS\n- [ ] Force HTTPS over TLS (formerly known as SSL). Set \n`config.force_ssl = true` in `config/environments/production.rb`. May also be\n done in a TLS termination point such as a load balancer, Nginx or Passenger \n Standalone. *Mitigates man-in-the-middle and other attacks.* \n- [ ] Use the [SSL Server Test tool from Qualys SSL\nLab](https://www.ssllabs.com/ssltest/) to check the grade of your TLS\ncertificate. Be sure to use the strongest (yet widely compatible) protocols\nand cipher suites, preferably with Ephemeral Diffie-Hellman support. The  \n[Mozilla SSL Configuration Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/) \ncan give you some suggestions. *Mitigates multiple SSL/TLS-related attacks \nsuch as BEAST and POODLE.*\n- [ ] Consider rate-limiting incoming HTTP requests, as implemented by the\n[rack-attack](https://github.com/kickstarter/rack-attack) and\n[rack-throttle](https://github.com/dryruby/rack-throttle) gems. See [sample\ncode](#throttling-requests). *Mitigates web scraping, HTTP floods, and other\nattacks.*\n\n###### Security-related headers\n- [ ] Consider using the [Secure Headers\ngem](https://github.com/twitter/secureheaders). *Mitigates several attacks.*\n- [ ] Consider obfuscating the web server banner string. In other words, hide\n your web server name and version. *Mitigates HTTP fingerprinting, making it \n harder for attackers to determine which exploits may work on your web server.* \n\n#### Memcached Security\n\n- [ ] Use a firewall. Memcached needs to be accessible from your other servers\nbut there's no reason to expose it to the internet. In short, only your other\nproduction servers have access to your production memcached servers. This alone\nwould prevent your server from being used in an attack. Memcached out of the box\ndoesn't use authentication so anyone who can connect to your server will be able\nto read your data.\n- [ ] Listen on a private interface. If you're running one server for your Rails\napplication and memcached, you should listen on `127.0.0.1`. For availability\nreasons, you shouldn't have 1 server in production anyway. For staging and test\nenvironments, follow this rule. For production setups where you have multiple\nRails servers that need to connect to memcached, use the private IP of the\nserver. This is something like `192.168.0.1`, `172.16.0.1`, or `10.0.0.1`. When\nyou start memcached, use `--listen 127.0.0.1` or `--listen 192.168.0.1`.\n- [ ] Disable UDP. It is enabled by default. To disable UDP, use `-U 0` when\nstarting memcached.\n\nResources:\n- [Memcached Security aka Don't Attack GitHub](https://www.engineyard.com/blog/memcached-security-aka-dont-attack-github-)\n\n#### Authorization (Pundit)\n- [ ] Implement authorization at the back end. Hiding links/controls in the UI\nis not enough to protect resources against unauthorized access. *Mitigates\nforced browsing attacks.*\n- [ ] Ensure all controllers/actions which require authorization call the \n`authorize` or `policy_scope` method ([sample code](#pundit-ensure-all-actions-are-authorized)).\n*Mitigates forced browsing attacks due to developers forgetting to require \nauthorization in some controller actions.*\n- [ ] When using DB records associated to users to populate select\nboxes, radio buttons or checkboxes, instead of querying by association\n(`user.posts`), consider using `policy_scope`. See [additional details and sample\ncode](#pundit-ensure-all-actions-are-authorized). *Improves \nreadability and maintainability of authorization policies.*\n\nResources:\n- [Pundit: Ensuring policies and scopes are used](https://github.com/elabs/pundit#ensuring-policies-and-scopes-are-used)\n- [Pundit: Scopes](https://github.com/elabs/pundit#scopes)\n\n#### Files\n\n###### File Uploads\n- [ ] Avoid using user controlled filenames. If possible, assign \"random\" \nnames to uploaded files when storing them in the OS. If not possible, \nwhitelist acceptable characters. It is safer to deny uploads with invalid \ncharacters in the filenames than to attempt to sanitize them.\n*Mitigates Directory Traversal Attacks such as attempting to overwrite \nsystem files by uploading files with names like `../../passwd`.*\n- [ ] Avoid using libraries such as ImageMagick to process images and videos \non your server. If possible, use an image/video processing service such as\n[Transloadit](https://transloadit.com/),\n[Cloudinary](http://cloudinary.com/features#manipulation), or\n[imgix](https://www.imgix.com/solutions). *Mitigates multiple image/video \nprocessing related vulnerabilities such as [these](https://imagetragick.com).*\n- [ ] If using [paperclip](https://github.com/thoughtbot/paperclip) gem with\n[imagemagick](https://www.imagemagick.org) for file upload and processing, make\nsure:\n    - Imagemagick [policies](https://www.imagemagick.org/source/policy.xml) are\nsuited for your environment to avoid exploits like [pixel flood attack](\nhttps://hackerone.com/reports/390).\n    - Content spoofing is handled manually since it fails in scenarios like\n[#2426](https://github.com/thoughtbot/paperclip/issues/2426).\n- [ ] Process uploaded files asynchronously. If not possible, implement\nper-client rate limiting. *Mitigates DoS Attacks that involve overloading the\nserver CPU by flooding it with uploads that require processing.*  \n- [ ] Do not trust validations implemented at the client (frontend) as most \nimplementations can be bypassed. Always (re)validate at the server.\n- [ ] Validate files before processing. *Mitigates DoS Attacks such\n as image bombs.*\n- [ ] Whitelist acceptable file extensions and acceptable Media Types (formerly \nknown as MIME types). Validating file extensions without checking their media\n types is not enough as attackers may disguise malicious files by changing \n their extensions. *Mitigates the upload of dangerous file formats such as shell\n  or Ruby scripts.*\n- [ ] Limit file size. *Mitigates against DoS attacks involving the \nupload of very large files.*\n- [ ] Consider uploading directly from the client (browser) to S3 or a similar\ncloud storage service. *Mitigates multiple security issues by keeping uploaded\nfiles on a separate server than your Rails application.*\n- [ ] If allowing uploads of malware-prone files (e.g., exe, msi, zip, rar,\npdf), scan them for viruses/malware. If possible, use a third party service to\nscan them outside your server. *Mitigates server infection (mostly in Windows\nservers) and serving infected files to other users.*\n- [ ] If allowing upload of archives such as zip, rar, and gz, validate\nthe target path, estimated unzip size and media types of compressed files\n**before** unzipping. *Mitigates DoS attacks such as zip bombs, zipping \nmalicious files in an attempt to bypass validations, and overwriting of system \nfiles such as `/etc/passwd`.*  \n\n###### File Downloads\n- [ ] Do not allow downloading of user-submitted filenames and paths. If not\npossible, use a whitelist of permitted filenames and paths. *Mitigates the\nexploitation of directory traversal vulnerabilities to download sensitive\nfiles.*\n\nResources:\n- [Ruby on Rails Security Guide - File Uploads and Downloads](http://guides.rubyonrails.org/security.html#file-uploads)\n\n#### Cross-Site Request Forgery (CSRF)\n- [ ] Enforce CSRF protection by setting `protect_from_forgery with: \n:exception` in all controllers used by web views or in \n`ApplicationController`.\n- [ ] Use HTTP verbs in a RESTful way. Do not use GET requests to alter the\nstate of resources. *Mitigates CSRF attacks.*\n- [ ] Up to Rails 4, there was a single CSRF token for all forms, actions, and\nmethods. Rails 5 implements per-form CSRF tokens, which are only valid for a\nsingle form and action/method. Enable it by setting\n`config.action_controller.per_form_csrf_tokens = true`.\n\nResources:\n- [Ruby on Rails Security Guide - CSRF](http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf)\n- [Big Binary Blog - Each form gets its own CSRF token in Rails 5](http://blog.bigbinary.com/2016/01/11/per-form-csrf-token-in-rails-5.html)\n\n#### Cross Origin Resource Sharing (CORS)\n- [ ] Occasionally the need to share some resources across many domains appears.\nFor example, you want to upload a file using AJAX request and send it to the\nother app. The receiving side should specify a whitelist of domains that are\nallowed to make those requests. There are few HTTP headers that control that.\n\nYou can use `rack-cors` gem and in `config/application.rb` specify your\nconfiguration ([code sample](#rack-cors-configuration)).\n\nResources:\n- [Security issues solutions in RoR](https://syndicode.com/2017/10/23/security-issues-solutions-in-ror/)\n\n#### Sensitive Data Exposure\n- [ ] If possible, avoid storing sensitive data such as credit cards, tax IDs\nand third-party authentication credentials in your application. If not \npossible, ensure that all sensitive data is encrypted at rest (in the DB) and\n in transit (use HTTPS over TLS). *Mitigate theft/leakage of sensitive data.*\n- [ ] Do not log sensitive data such as passwords and credit card numbers. You\nmay include parameters that hold sensitive data in `config.filter_parameters` at\n`initializers/filter_parameter_logging.rb`. For added security, consider\nconverting `filter_parameters` into a whitelist. See [sample\ncode](#convert-filter_parameters-into-a-whitelist). *Prevents plain-text storage\nof sensitive data in log files.*\n- [ ] HTML comments are viewable to clients and should not contain details that\ncan be useful to attackers. Consider using server-side comments such as `\u003c%#\nThis comment syntax with ERB %\u003e` instead of HTML comments. *Avoids exposure of\nimplementation details.*\n- [ ] Avoid exposing numerical/sequential record IDs in URLs, form HTML source\nand APIs. Consider using slugs (A.K.A. friendly IDs, vanity URLs) to identify\nrecords instead of numerical IDs, as implemented by the [friendly_id\ngem](https://github.com/norman/friendly_id). Additional benefits include SEO and\nbetter-looking URLs. *Mitigates forced browsing attacks and exposure of metrics\nabout your business, such as the number of registered users, number of \nproducts on stock, or number of receipts/purchases.*\n- [ ] If using slugs instead of numerical IDs for URLs, consider returning a\n`404 Not Found` status code instead of `403 Forbidden` for authorization errors.\nPrevents leakage of attribute values used to generate the slugs. For instance,\nvisiting `www.myapp.com/users/john-doe` and getting a `403`  return status\nindicates the application has a user named John Doe.*\n- [ ] Do not set `config.consider_all_requests_local = true` in the production\nenvironment. If you need to set `config.consider_all_requests_local = true` to\nuse the [better_errors](https://github.com/charliesome/better_errors) gem, do it\non `config/environments/development.rb`. *Prevents leakage of exceptions and\nother information that should only be accessible to developers.*\n- [ ] Don't install development/test-related gems such as \n[better_errors](https://github.com/charliesome/better_errors) and \n[web-console](https://github.com/rails/web-console) in the production \nenvironment. Place them within a `group :development, :test do` block \nin the `Gemfile`. *Prevents leakage of exceptions and even **REPL access** \nif using better_errors + web-console.*\n\n###### Credentials\n- [ ] The encryption key, located on `config/master.key` is created when you run\n`rails new`. It's also added to `.gitignore` so it doesn't get committed to your\nrepository. *Mitigates credential leaks/theft.*\n- [ ] Don't edit the `config/credentials.yml.enc` file directly. To add\ncredentials, run `bin/rails credentials:edit`. Use a flat format which means you\ndon't have to put development or production anymore. *Mitigates credential\nleaks/theft.*\n- [ ] If you want to generate a new secret key base run, `bin/rails secret` and\nadd that to your credentials by running `bin/rails credentials:edit`.\n- [ ] Upload `master.key` securely. You can scp or sftp the file. Upload the key\nto a shared directory. Shared here means shared between releases, not a shared\nfilesystem. On each deploy, you symlink `config/master.key` to\n`/path/to/shared/config/master.key`.\n- [ ] If you need to give a developer a copy of the key, never send it via email\n(unless you're using encrypted emails which most of us don't!) You can use a\npassword manager because they use encryption.\n- [ ] Put the key on the `RAILS_MASTER_KEY` environment variable. In some cases\nwhere you can't upload a file, this is the only option. Even though this is\nconvenient, make sure you know the risks of using environment variables. The\nrisks can be mitigated, but if you can upload master.key then use that option.\n\nResources:\n - [Rails Encrypted Credentials on Rails 5.2](https://www.engineyard.com/blog/rails-encrypted-credentials-on-rails-5.2)\n\n#### Routing, Template Selection, and Redirection\n- [ ] Don't perform URL redirection based on user inputted strings. In other \nwords, don't pass user input to `redirect_to`. If you have no choice, create \na whitelist of acceptable redirect URLs or limit to only redirecting to\npaths within your domain [(example code)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Redirects_and_Forwards).\n*Mitigates redirection to phishing and malware sites. Prevent attackers from \nproviding URLs such as \n`http://www.my-legit-rails-app.com/redirect?to=www.dangeroussite.com` to \nvictims.*\n- [ ] Do not use a user inputted string to determine the name of the template or\nview to be rendered. *Prevents attackers from rendering arbitrary views such as\n admin-only pages.*\n- [ ] Avoid \"catch-all\" routes such as `\nmatch ':controller(/:action(/:id(.:format)))'` and make non-action controller \nmethods private. *Mitigates unintended access to controller methods.*\n\nResources:\n- [OWASP Ruby on Rails Cheatsheet - Redirects and Forwards (URL validation)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Redirects_and_Forwards)\n\n#### Third-party Software\n- [ ] Apply the latest security patches in the OS frequently. Pay special \nattention to internet-facing services such as application servers (Passenger, \nPuma, Unicorn), web servers (Nginx, Apache, Passenger Standalone) and SSH \nservers. \n- [ ] Update Ruby frequently.\n- [ ] Watch out for security vulnerabilities in your gems. Run \n[bundler-audit](https://github.com/rubysec/bundler-audit) frequently or use \na service like [Snyk](https://snyk.io), [GuardRails](https://www.guardrails.io/) \n(both free for open-source development).\n\n#### Security Tools\n- [ ] Run [Brakeman](http://brakemanscanner.org/) before each deploy. \nIf using an automated code review tool like \n[Code Climate](https://codeclimate.com/), enable the [Brakeman \nengine](https://docs.codeclimate.com/v1.0/docs/brakeman). \n- [ ] Adding a gem trust policy with `MediumSecurity` is a good way to stop\nmalicious gems getting installed on the server. For example,\n`bundle --trust-policy MediumSecurity`.\n- [ ] You can use `rubocop` gem and enables security-related rules in the\n`.rubocop.yml` configuration file.\n- [ ] Consider using a continuous security service such as\n [Detectify](https://detectify.com/).\n- [ ] Consider using a Web Application Firewall (WAF) such as \n[NAXSI](https://github.com/nbs-system/naxsi) for Nginx, \n[ModSecurity](https://github.com/SpiderLabs/ModSecurity) for Apache and Nginx. \n*Mitigates XSS, SQL Injection, DoS, and many other attacks.*\n\nResources:\n- [Keeping Rails Application Secured](https://fq.nz/blog/2019/04/14/keeping-rails-application-secured.html)\n- [How RuboCop can secure your Ruby and Rails Applications](https://www.guardrails.io/blog/2018/11/25/how-rubocop-can-secure-your-ruby-and-rails-applications)\n\n#### Testing\n- [ ] Include security tests in your test suite. Look at OWASP's\n[RailsGoat](https://github.com/OWASP/railsgoat) application for examples of\n[security-related Capybara\nspecs](https://github.com/OWASP/railsgoat/tree/master/spec/vulnerabilities).\n*Raises additional security awareness and mitigates security-related \nregressions.*\n- [ ] Create security tests in pairs: one for the access denied scenario and \nanother for the access granted scenario.\n- [ ] When using TDD, consider implementing authentication in the early stages\n of development, as it tends to break multiple preexisting tests.\n\n#### Others\n- [ ] Use strong parameters in the controllers. This is the default behavior as\nof Rails 4+. *Mitigates mass assignment attacks such as overwriting the `role`\nattribute of the `User` model for privilege escalation purposes.*\n- [ ] Implement Captcha or Negative Captcha on publicly exposed forms.\n[reCAPTCHA](https://developers.google.com/recaptcha/) is a great option, and\nthere is [a gem](https://github.com/ambethia/recaptcha) that facilitates Rails\nintegration. Other options are the\n[rucaptcha](https://github.com/huacnlee/rucaptcha) and\n[negative-captcha](https://github.com/subwindow/negative-captcha) gems. \n*Mitigates automated SPAM (spambots).*\n\n## Details and Code Samples\n\n#### Command Injection example\n```\n# User input\nparams[:shop][:items_ids] # Maybe you expect this to be an array inside a string.\n                          # But it can contain something very dangerous like:\n                          # \"Kernel.exec('Whatever OS command you want')\"\n\n# Vulnerable code\nevil_string = params[:shop][:items_ids]\neval(evil_string)\n```\n\nIf you see a call to eval you must be very sure that you are properly sanitizing\nit. Using regular expressions is a good way to accomplish that.\n```\n# Secure code\nevil_string = params[:shop][:items_ids]\nsecure_string = /\\[\\d*,?\\d*,?\\d*\\]/.match(evil_string).to_s\n\neval(secure_string)\n```\n\n#### Password validation regex\nWe may implement password strength validation in Devise by adding the \nfollowing code to the `User` model.\n```\nvalidate :password_strength\n\nprivate\n\ndef password_strength\n  minimum_length = 8\n  # Regex matches at least one lower case letter, one uppercase, and one digit\n  complexity_regex = /\\A(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/\n  # When a user is updated but not its password, the password param is nil\n  if password.present? \u0026\u0026\n    (password.length \u003c minimum_length || !password.match(complexity_regex))\n    errors.add :password, 'must be 8 or more characters long, including \n                           at least one lowercase letter, one uppercase\n                           letter, and one digit.'\n  end\nend\n```\n\n#### Pundit: ensure all actions are authorized\n\nAdd the following to `app/controllers/application_controller.rb`\n```\nafter_action :verify_authorized, except: :index, unless: :devise_controller?\nafter_action :verify_policy_scoped, only: :index, unless: :devise_controller?\n```\n\nAdd the following to controllers that do not require authorization. You may \ncreate a concern for DRY purposes.\n```\nafter_action_skip :verify_authorized\nafter_action_skip :verify_policy_scoped\n```\n\n#### Pundit: only display appropriate records in select boxes\nThink of a blog-like news site where users with `editor` role have access to\nspecific news categories, and `admin` users have access to all categories. The\n`User` and the `Category` models have an HMT relationship. When creating a blog\npost, there is a select box for choosing a category. We want editors only to see\ntheir associated categories in the select box, but admins must see all\ncategories. We could populate that select box with `user.categories`. However,\nwe would have to associate all admin users with all categories (and update these\nassociations every time a new category is created). A better approach is to use\n[Pundit Scopes](https://github.com/elabs/pundit#scopes) to determine which\ncategories are visible to each user role and use the `policy_scope` method when\npopulating the select box.\n\n```\n# app/views/posts/_form.html.erb\nf.collection_select :category_id, policy_scope(Category), :id, :name\n```\n\n#### Convert filter_parameters into a whitelist\nDevelopers may forget to add one or more parameters that contain sensitive data\nto `filter_parameters`. Whitelists are usually safer than blacklists as they do\nnot generate security vulnerabilities in case of developer forgetfulness. \nThe following code converts `filter_parameters` into a whitelist.\n\n```\n# config/initializers/filter_parameter_logging.rb\nif Rails.env.production?\n  # Parameters whose values are allowed to appear in the production logs:\n  WHITELISTED_KEYS = %w(foo bar baz)\n  \n  # (^|_)ids? matches the following parameter names: id, *_id, *_ids\n  WHITELISTED_KEYS_MATCHER = /((^|_)ids?|#{WHITELISTED_KEYS.join('|')})/.freeze\n  SANITIZED_VALUE = '[FILTERED]'.freeze\n  \n  Rails.application.config.filter_parameters \u003c\u003c lambda do |key, value|\n    unless key.match(WHITELISTED_KEYS_MATCHER)\n      value.replace(SANITIZED_VALUE)\n    end\n  end\nelse\n  # Keep the default blacklist approach in the development environment\n  Rails.application.config.filter_parameters += [:password]\nend\n```\n\n#### rack-cors configuration\n\n```\nmodule Sample\n  class Application \u003c Rails::Application\n    config.middleware.use Rack::Cors do\n      allow do\n        origins 'someserver.example.com'\n        resource %r{/users/\\d+.json},\n          headers: ['Origin', 'Accept', 'Content-Type'],\n          methods: [:post, :get]\n      end\n    end\n  end\nend\n```\n\n#### Throttling Requests\n\nOn some pages like the login page, you'll want to throttle your users to a few\nrequests per minute. This prevents bots from trying thousands of passwords\nquickly.\n\nRack Attack is a Rack middleware that provides throttling among other features.\n\n```\nRack::Attack.throttle('logins/email', :limit =\u003e 6, :period =\u003e 60.seconds) do |req|\n  req.params['email'] if req.path == '/login' \u0026\u0026 req.post?\nend\n```\n\n#### When I18n key ends up with \\_html\n\nInstead of the following example:\n```\n# en.yml\nen:\n  hello: \"Welcome \u003cstrong\u003e%{user_name}\u003c/strong\u003e!\"\n```\n```\n\u003c%= t('hello', user_name: current_user.first_name).html_safe %\u003e\n```\nUse the next one:\n```\n# en.yml\nen:\n  hello_html: \"Welcome \u003cstrong\u003e%{user_name}\u003c/strong\u003e!\"\n```\n```\n\u003c%= t('hello_html', user_name: current_user.first_name) %\u003e\n```\n\n#### HAML: XSS protection\nBy default,\n```ruby\n=\"\u003cem\u003eemphasized\u003cem\u003e\"\n!= \"\u003cem\u003eemphasized\u003cem\u003e\"\n```\ncompiles to:\n```ruby\n\u0026lt;em\u0026gt;emphasized\u0026lt;/em\u0026gt;\n\u003cem\u003eemphasized\u003cem\u003e\n```\n\n## Authors\n\n- **Bruno Facca** - [LinkedIn](https://www.linkedin.com/in/brunofacca/) -\nEmail: bruno at facca  dot info\n\n## Contributing\n\nContributions are welcome. If you would like to correct an error or add new \nitems to the checklist, feel free to create an issue followed by a PR. See the\n [TODO](#TODO) section for contribution suggestions.  \n\nIf you are interested in contributing regularly, drop me a line at the above\ne-mail to become a collaborator.\n\n## TODO\n* Add sample tests (RSpec and/or Minitest) to detect the presence of\nvulnerabilities. See OWASP's [RailsGoat security-related Capybara \nspecs](https://github.com/OWASP/railsgoat/tree/master/spec/vulnerabilities) for\ninspiration.\n* Compare upload gems regarding their implementation of the [File\n Uploads](#file-uploads) items of this checklist (build a table).\n* Compare authentication gems regarding their implementation of the\n [Authentication](#authentication) items of this checklist (build a table).\n\n## References and Further Reading\n- [Ruby on Rails Security Guide](http://guides.rubyonrails.org/security.html)\n- [The Rails 4 Way by Obie Fernandez](https://www.amazon.com/Rails-Way-Addison-Wesley-Professional-Ruby/dp/0321944275/), Chapter 15\n- [OWASP Top Ten](https://www.owasp.org/index.php/Top_10_2013-Top_10)\n- [SitePoint: Common Rails Security Pitfalls and Their Solutions](https://www.sitepoint.com/common-rails-security-pitfalls-and-their-solutions/)\n- [Rails Security Audit by Hardhat](https://github.com/hardhatdigital/rails-security-audit)\n- [Rails Security Checklist by Eliot Sykes](https://github.com/eliotsykes/rails-security-checklist)\n- [Ruby on Rails Security 17-Item Checklist](https://www.engineyard.com/blog/ruby-on-rails-security-checklist)\n- [Rails security best practices](https://github.com/ankane/secure_rails)\n- [Awesome Ruby Security resources](https://github.com/pxlpnk/awesome-ruby-security)\n- [Awesome Rails Security](https://github.com/edwardqiu/awesome-rails-security)\n- [Securing Sensitive Data in Rails](https://ankane.org/sensitive-data-rails)\n\n## License\n\nReleased under the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrunofacca%2Fzen-rails-security-checklist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrunofacca%2Fzen-rails-security-checklist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrunofacca%2Fzen-rails-security-checklist/lists"}