zen rails security checklist

Checklist of security precautions for Ruby on Rails applications.

1768
147
Ruby

Zen Rails Security Checklist

Summary

This document provides a not necessarily comprehensive list of security measures
to be implemented when developing a Ruby on Rails application. It is designed to
serve as a quick reference and minimize vulnerabilities caused by developer
forgetfulness. It does not replace developer training on secure coding
principles and how they can be applied.

Describing how each security vulnerability works is outside the scope of this
document. Links to external resources containing further information are
provided in the corresponding sections of the checklist. Please apply only the
suggestions you thoroughly understand.

Please keep in mind that security is a moving target. New vulnerabilities and
attack vectors are discovered every day. We suggest you try to keep up to date,
for instance, by subscribing to security mailing lists related to the software
and libraries you are using.

This checklist is meant to be a community-driven resource. Your
contributions are welcome!

Disclaimer: This document does not cover all possible security
vulnerabilities. The authors do not take any legal responsibility for the
accuracy or completeness of the information herein.

Supported Rails Versions

This document focuses on Rails 4 and 5. Vulnerabilities that were present in
earlier versions and fixed in Rails 4 are not included.

Table of Contents

Table of contents generated by DocToc.

The Checklist

Injection

Injection attacks are #1 at the OWASP Top10.

  • [ ] Don’t use standard Ruby interpolation (#{foo}) to insert user inputted
    strings into ActiveRecord or raw SQL queries. Use the ? character, named bind
    variables or the ActiveRecord::Sanitization
    methods

    to sanitize user input used in DB queries. Mitigates SQL injection attacks.
  • [ ] Don’t pass user inputted strings to methods capable of evaluating
    code or running O.S. commands such as eval, system, syscall, %x(),
    open, popen<n>, File.read, File.write, and exec. Using regular
    expressions is a good way to sanitize it (code sample).
    Mitigates command injection attacks.

Resources:

Authentication

Broken Authentication and Session Management are #2 at the OWASP Top 10.

  • [ ] Avoid rolling your own authentication unless you know exactly what you
    are doing. Consider using a gem such as
    Devise,
    Authlogic or
    Clearance. Mitigates dozens of
    potential vulnerabilities.
  • [ ] Enforce a minimum password length of 8 characters or more. Mitigates
    brute-force attacks.
    • Devise: set config.password_length = 8..128 in
      config/initializers/devise.rb.
  • [ ] Consider validating passwords against:
    • Dictionary words. Since passwords have a minimum length requirement, the
      dictionary need only include words meeting that requirement.
    • A list of commonly used passwords such as
      these.
      The StrongPassword gem provide
      such feature.
    • A leaked password database such as PasswordPing.
    • Context-specific words, such as the name of the application, the
      username, and derivatives thereof.
  • [ ] Consider the pros and cons of enforcing password complexity rules such as
    mixtures of different character types. Most applications use it. However, the
    latest NIST Guidelines advise
    against it. An alternative is to increase the minimum length requirement and
    encourage the usage of passphrases. Mitigate brute-force attacks.
  • [ ] Lock the account after multiple failed login attempts. Mitigates
    brute-force attacks.
  • [ ] Require users to confirm their e-mail addresses on sign-up and when the
    e-mail address is changed. Mitigates the creation of bogus accounts with
    non-existing or third-party e-mails.
    • Devise: use the confirmable
      module

      and set config.reconfirmable = true in config/initializers/devise.rb.
  • [ ] Require users to input their old password on password change. Mitigates
    unauthorized password changes on session hijacking, CSRF or when a user forgets
    to log out and leaves the PC or mobile device unattended.
    • Devise: does that by default
  • [ ] Expire the session at log out and expire old sessions at every successful
    login. Mitigates CSRF, session hijacking and session fixation attacks by
    reducing their time-frame.
    • Devise: does that by default.
  • [ ] Expire sessions after a period of inactivity (e.g., 30 minutes).
    Mitigates CSRF, session hijacking and session fixation attacks by reducing
    their time-frame.
  • [ ] Notify user via email on password change. Does not prevent an attacker
    from changing the victim’s password, but warns the victim so he can contact the
    system administrator to revoke the attacker’s access.
    • Devise: set config.send_password_change_notification = true in
      config/initializers/devise.rb.
  • [ ] Use generic error messages such as “Invalid email or password” instead of
    specifying which part (e-mail or password) is invalid. Mitigates user
    enumeration

    and brute-force attacks.
    • Devise: setting config.paranoid = true in
      config/initializers/devise.rb will protect the confirmable,
      recoverable and unlockable modules against user enumeration. To protect
      the registerable module, add a captcha to the registration page (see
      instructions in the Devise
      Wiki
      ).
  • [ ] Ensure all non-public controllers/actions require authentication. Avoid
    unauthorized access due to developer forgetfulness.
    • Devise: add before_action :authenticate_user! to
      ApplicationController and
      skip_before_action :authenticate_user! to publicly accessible
      controllers/actions.
  • [ ] Consider using two-factor authentication (2FA) as provided by
    Authy. Provides a highly effective extra layer of
    authentication security.
  • [ ] Consider requiring authentication in config/routes.rb. Requiring
    authentication in both controllers and routes may not be DRY, but such
    redundancy provides additional security (see Defense in
    depth
    ).
    • Devise: Place non-public resources within a authenticate :user do block
      (see the Devise
      Wiki
      ).
  • [ ] Consider limiting the number of simultaneous sessions per account. May
    reduce application exposure on account compromise (e.g. leaked passwords).
  • [ ] Avoid implementing “security questions” such as “What is your mother’s
    maiden name?” as their answers may be reused across multiple sites and easily
    found by means of social
    engineering
    . See
    this article.
  • [ ] If using role-based access control (RBAC), do not include the role
    attribute in the strong parameters of the controller(s) used for user
    registration and profile editing. Prevent malicious users from assigning admin
    role to themselves.
    • Devise: Do not pass the role parameter key to
      devise_parameter_sanitizer.permit.
  • [ ] Consider restricting administrator access by IP. If the client’s IP is
    dynamic, restrict by IP block/ASN or by country via IP geolocation.

Sessions & Cookies

Broken Authentication and Session Management are #2 at the OWASP Top 10.

  • [ ] Don’t store data such as money/point balances or user privileges in a
    cookie or a CookieStore Session. Store it in the database instead. Mitigates
    replay attacks.
  • [ ] Consider always using encrypted cookies. This is the default behavior in
    Rails 4+ when secret_key_base is set. Strengthens cookie encryption and
    mitigates multiple attacks involving cookie tampering.
  • [ ] Unless your JavaScript frontend needs to read cookies generated by the
    Rails server, set all cookies as httponly. Search the project for cookie
    accessors and add httponly: true. Example: cookies[:login] = {value: 'user', httponly: true}. Restricts cookie access to the Rails server. Mitigates
    attackers from using the victim’s browser JavaScript to steal cookies after a
    successful XSS attack.

Resources:

Cross-Site Scripting (XSS)

XSS is #3 at the OWASP Top 10.

Handling User Input
  • [ ] Always validate user input that may eventually be displayed to other
    users. Attempting to blacklist characters, strings or sanitize input tends to be
    ineffective (see examples of how to bypass such
    blacklists
    ). A
    whitelisting approach is usually safer. Mitigates multiple XSS attacks.
  • [ ] Consider using the
    loofah-activerecord gem
    to scrub your model attribute values. Mitigates multiple XSS attacks.
  • [ ] If you must create links from user inputted URLs, be sure to validate
    them. In particular, it should be possible to limit URL schemes to http/https
    in nearly all cases. The URL passed to link_to (the second argument) will be
    HTML escaped. However, link_to allows any scheme for the URL. If using regex,
    ensure that the string begins with the expected protocol(s), as in
    \Ahttps?. Mitigates XSS attacks such as entering
    javascript:dangerous_stuff()//http://www.some-legit-url.com as a website URL
    or a dangerous data: payload that is displayed to other users (e.g., in a
    user profile page).
  • [ ] When using regex for input validation, use \A and \z to match string
    beginning and end. Do not use ^ and $ as anchors. Mitigates XSS
    attacks that involve slipping JS code after line breaks, such as
    [email protected]\n<script>dangerous_stuff();</script>.
  • [ ] Do not trust validations implemented at the client (frontend) as most
    implementations can be bypassed. Always (re)validate at the server.
Output Escaping & Sanitization
  • [ ] Escape all HTML output. Rails does that by default, but calling
    html_safe or raw at the view suppresses escaping. Look for calls to these
    methods in the entire project, check if you are generating HTML from
    user-inputted strings and if those strings are effectively validated. Note that
    there are dozens of ways to evade
    validation
    . If
    possible, avoid calling html_safe and raw altogether. Most templating
    libraries also provide a way of skipping escaping. ERB uses the double ==:
    <%== params[:query] %>. For custom scrubbing, see
    ActionView::Helpers::SanitizeHelper
    Mitigates XSS attacks.
  • [ ] Always enclose attribute values with double quotes. Even without
    html_safe, it is possible to introduce cross-site scripting into templates
    with unquoted attributes. In the following code
    <p class=<%= params[:style] %>...</p>, an attacker can insert a space into
    the style parameter and suddenly the payload is outside the attribute value and
    they can insert their own payload. And when a victim mouses over the paragraph,
    the XSS payload will fire. Mitigates XSS attacks.
  • [ ] Rendering JSON inside of HTML templates is tricky. You can’t just HTML
    escape JSON, especially when inserting it into a script context, because
    double-quotes will be escaped and break the code. But it isn’t safe to not
    escape it, because browsers will treat a </script> tag as HTML no matter
    where it is. The Rails documentation recommends always using json_escape
    just in case to_json is overridden or the value is not valid JSON.
    Mitigates XSS attacks.
  • [ ] Be careful when using render inline: .... The value passed in will be
    treated like an ERB template by default. Take a look at this code:
    render inline: "Thanks #{@user.name}!". Assuming users can set their own
    name, an attacker might set their name to <%= rm -rf / %> which will execute
    rm -rf / on the server! This is called Server Side Template Injection and it
    allows arbitrary code execution (RCE) on the server. If you must use an inline
    template treat all input the same as you would in a regular ERB template:
    render inline: "Thanks <%= @user.name %>". Mitigates XSS attacks.
  • [ ] Avoid sending user inputted strings in e-mails to other users. Attackers
    may enter a malicious URL in a free text field that is not intended to contain
    URLs and does not provide URL validation. Most e-mail clients display URLs as
    links. Mitigates XSS, phishing, malware infection and other attacks.
  • [ ] 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).
XSS protection in HAML templates
  • [ ] Be careful when using != in Haml and it should be made sure that no
    user data is rendered unescaped. The != notation in Haml works the way
    <%= raw(…) %> works in ERB. See (example code).

Resources:

Content Security Policy (CSP)
  • [ ] Content Security Policy (CSP) is an added layer of security that helps to
    detect and mitigate various types of attacks on our web applications, including
    Cross Site Scripting (XSS) and data injection attacks.

Resources:

Insecure Direct Object Reference

  • [ ] An IDOR issue arises when the user is supposed to have access to url
    "/get/post/6", for example, but not "/get/post/9" but the system does not
    properly check those permissions. And if we change “6” in the URL, what happens?
    We can see the data of all users. This may be due to the fact that the data was
    generated as follows: @user = User.find_by(id: params[:user_id]) – which is
    basically getting the ID from the GET parameter in the URL. Instead a more
    secure way of doing this is setting the @user parameter based on the
    "current_user" session variable like this: @user = current_user.

Resources:

HTTP & TLS

  • [ ] Force HTTPS over TLS (formerly known as SSL). Set
    config.force_ssl = true in config/environments/production.rb. May also be
    done in a TLS termination point such as a load balancer, Nginx or Passenger
    Standalone. Mitigates man-in-the-middle and other attacks.
  • [ ] Use the SSL Server Test tool from Qualys SSL
    Lab
    to check the grade of your TLS
    certificate. Be sure to use the strongest (yet widely compatible) protocols
    and cipher suites, preferably with Ephemeral Diffie-Hellman support. The
    Mozilla SSL Configuration Generator
    can give you some suggestions. Mitigates multiple SSL/TLS-related attacks
    such as BEAST and POODLE.
  • [ ] Consider rate-limiting incoming HTTP requests, as implemented by the
    rack-attack and
    rack-throttle gems. See sample
    code
    . Mitigates web scraping, HTTP floods, and other
    attacks.
Security-related headers
  • [ ] Consider using the Secure Headers
    gem
    . Mitigates several attacks.
  • [ ] Consider obfuscating the web server banner string. In other words, hide
    your web server name and version. Mitigates HTTP fingerprinting, making it
    harder for attackers to determine which exploits may work on your web server.

Memcached Security

  • [ ] Use a firewall. Memcached needs to be accessible from your other servers
    but there’s no reason to expose it to the internet. In short, only your other
    production servers have access to your production memcached servers. This alone
    would prevent your server from being used in an attack. Memcached out of the box
    doesn’t use authentication so anyone who can connect to your server will be able
    to read your data.
  • [ ] Listen on a private interface. If you’re running one server for your Rails
    application and memcached, you should listen on 127.0.0.1. For availability
    reasons, you shouldn’t have 1 server in production anyway. For staging and test
    environments, follow this rule. For production setups where you have multiple
    Rails servers that need to connect to memcached, use the private IP of the
    server. This is something like 192.168.0.1, 172.16.0.1, or 10.0.0.1. When
    you start memcached, use --listen 127.0.0.1 or --listen 192.168.0.1.
  • [ ] Disable UDP. It is enabled by default. To disable UDP, use -U 0 when
    starting memcached.

Resources:

Authorization (Pundit)

  • [ ] Implement authorization at the back end. Hiding links/controls in the UI
    is not enough to protect resources against unauthorized access. Mitigates
    forced browsing attacks.
  • [ ] Ensure all controllers/actions which require authorization call the
    authorize or policy_scope method (sample code).
    Mitigates forced browsing attacks due to developers forgetting to require
    authorization in some controller actions.
  • [ ] When using DB records associated to users to populate select
    boxes, radio buttons or checkboxes, instead of querying by association
    (user.posts), consider using policy_scope. See additional details and sample
    code
    . Improves
    readability and maintainability of authorization policies.

Resources:

Files

File Uploads
  • [ ] Avoid using user controlled filenames. If possible, assign “random”
    names to uploaded files when storing them in the OS. If not possible,
    whitelist acceptable characters. It is safer to deny uploads with invalid
    characters in the filenames than to attempt to sanitize them.
    Mitigates Directory Traversal Attacks such as attempting to overwrite
    system files by uploading files with names like ../../passwd.
  • [ ] Avoid using libraries such as ImageMagick to process images and videos
    on your server. If possible, use an image/video processing service such as
    Transloadit,
    Cloudinary, or
    imgix. Mitigates multiple image/video
    processing related vulnerabilities such as these.
  • [ ] If using paperclip gem with
    imagemagick for file upload and processing, make
    sure:
    • Imagemagick policies are
      suited for your environment to avoid exploits like pixel flood attack.
    • Content spoofing is handled manually since it fails in scenarios like
      #2426.
  • [ ] Process uploaded files asynchronously. If not possible, implement
    per-client rate limiting. Mitigates DoS Attacks that involve overloading the
    server CPU by flooding it with uploads that require processing.
  • [ ] Do not trust validations implemented at the client (frontend) as most
    implementations can be bypassed. Always (re)validate at the server.
  • [ ] Validate files before processing. Mitigates DoS Attacks such
    as image bombs.
  • [ ] Whitelist acceptable file extensions and acceptable Media Types (formerly
    known as MIME types). Validating file extensions without checking their media
    types is not enough as attackers may disguise malicious files by changing
    their extensions. Mitigates the upload of dangerous file formats such as shell
    or Ruby scripts.
  • [ ] Limit file size. Mitigates against DoS attacks involving the
    upload of very large files.
  • [ ] Consider uploading directly from the client (browser) to S3 or a similar
    cloud storage service. Mitigates multiple security issues by keeping uploaded
    files on a separate server than your Rails application.
  • [ ] If allowing uploads of malware-prone files (e.g., exe, msi, zip, rar,
    pdf), scan them for viruses/malware. If possible, use a third party service to
    scan them outside your server. Mitigates server infection (mostly in Windows
    servers) and serving infected files to other users.
  • [ ] If allowing upload of archives such as zip, rar, and gz, validate
    the target path, estimated unzip size and media types of compressed files
    before unzipping. Mitigates DoS attacks such as zip bombs, zipping
    malicious files in an attempt to bypass validations, and overwriting of system
    files such as /etc/passwd.
File Downloads
  • [ ] Do not allow downloading of user-submitted filenames and paths. If not
    possible, use a whitelist of permitted filenames and paths. Mitigates the
    exploitation of directory traversal vulnerabilities to download sensitive
    files.

Resources:

Cross-Site Request Forgery (CSRF)

  • [ ] Enforce CSRF protection by setting protect_from_forgery with: :exception in all controllers used by web views or in
    ApplicationController.
  • [ ] Use HTTP verbs in a RESTful way. Do not use GET requests to alter the
    state of resources. Mitigates CSRF attacks.
  • [ ] Up to Rails 4, there was a single CSRF token for all forms, actions, and
    methods. Rails 5 implements per-form CSRF tokens, which are only valid for a
    single form and action/method. Enable it by setting
    config.action_controller.per_form_csrf_tokens = true.

Resources:

Cross Origin Resource Sharing (CORS)

  • [ ] Occasionally the need to share some resources across many domains appears.
    For example, you want to upload a file using AJAX request and send it to the
    other app. The receiving side should specify a whitelist of domains that are
    allowed to make those requests. There are few HTTP headers that control that.

You can use rack-cors gem and in config/application.rb specify your
configuration (code sample).

Resources:

Sensitive Data Exposure

  • [ ] If possible, avoid storing sensitive data such as credit cards, tax IDs
    and third-party authentication credentials in your application. If not
    possible, ensure that all sensitive data is encrypted at rest (in the DB) and
    in transit (use HTTPS over TLS). Mitigate theft/leakage of sensitive data.
  • [ ] Do not log sensitive data such as passwords and credit card numbers. You
    may include parameters that hold sensitive data in config.filter_parameters at
    initializers/filter_parameter_logging.rb. For added security, consider
    converting filter_parameters into a whitelist. See sample
    code
    . Prevents plain-text storage
    of sensitive data in log files.
  • [ ] HTML comments are viewable to clients and should not contain details that
    can be useful to attackers. Consider using server-side comments such as <%# This comment syntax with ERB %> instead of HTML comments. Avoids exposure of
    implementation details.
  • [ ] Avoid exposing numerical/sequential record IDs in URLs, form HTML source
    and APIs. Consider using slugs (A.K.A. friendly IDs, vanity URLs) to identify
    records instead of numerical IDs, as implemented by the friendly_id
    gem
    . Additional benefits include SEO and
    better-looking URLs. Mitigates forced browsing attacks and exposure of metrics
    about your business, such as the number of registered users, number of
    products on stock, or number of receipts/purchases.
  • [ ] If using slugs instead of numerical IDs for URLs, consider returning a
    404 Not Found status code instead of 403 Forbidden for authorization errors.
    Prevents leakage of attribute values used to generate the slugs. For instance,
    visiting www.myapp.com/users/john-doe and getting a 403 return status
    indicates the application has a user named John Doe.*
  • [ ] Do not set config.consider_all_requests_local = true in the production
    environment. If you need to set config.consider_all_requests_local = true to
    use the better_errors gem, do it
    on config/environments/development.rb. Prevents leakage of exceptions and
    other information that should only be accessible to developers.
  • [ ] Don’t install development/test-related gems such as
    better_errors and
    web-console in the production
    environment. Place them within a group :development, :test do block
    in the Gemfile. Prevents leakage of exceptions and even REPL access
    if using better_errors + web-console.
Credentials
  • [ ] The encryption key, located on config/master.key is created when you run
    rails new. It’s also added to .gitignore so it doesn’t get committed to your
    repository. Mitigates credential leaks/theft.
  • [ ] Don’t edit the config/credentials.yml.enc file directly. To add
    credentials, run bin/rails credentials:edit. Use a flat format which means you
    don’t have to put development or production anymore. Mitigates credential
    leaks/theft.
  • [ ] If you want to generate a new secret key base run, bin/rails secret and
    add that to your credentials by running bin/rails credentials:edit.
  • [ ] Upload master.key securely. You can scp or sftp the file. Upload the key
    to a shared directory. Shared here means shared between releases, not a shared
    filesystem. On each deploy, you symlink config/master.key to
    /path/to/shared/config/master.key.
  • [ ] If you need to give a developer a copy of the key, never send it via email
    (unless you’re using encrypted emails which most of us don’t!) You can use a
    password manager because they use encryption.
  • [ ] Put the key on the RAILS_MASTER_KEY environment variable. In some cases
    where you can’t upload a file, this is the only option. Even though this is
    convenient, make sure you know the risks of using environment variables. The
    risks can be mitigated, but if you can upload master.key then use that option.

Resources:

Routing, Template Selection, and Redirection

  • [ ] Don’t perform URL redirection based on user inputted strings. In other
    words, don’t pass user input to redirect_to. If you have no choice, create
    a whitelist of acceptable redirect URLs or limit to only redirecting to
    paths within your domain (example code).
    Mitigates redirection to phishing and malware sites. Prevent attackers from
    providing URLs such as
    http://www.my-legit-rails-app.com/redirect?to=www.dangeroussite.com to
    victims.
  • [ ] Do not use a user inputted string to determine the name of the template or
    view to be rendered. Prevents attackers from rendering arbitrary views such as
    admin-only pages.
  • [ ] Avoid “catch-all” routes such as match ':controller(/:action(/:id(.:format)))' and make non-action controller
    methods private. Mitigates unintended access to controller methods.

Resources:

Third-party Software

  • [ ] Apply the latest security patches in the OS frequently. Pay special
    attention to internet-facing services such as application servers (Passenger,
    Puma, Unicorn), web servers (Nginx, Apache, Passenger Standalone) and SSH
    servers.
  • [ ] Update Ruby frequently.
  • [ ] Watch out for security vulnerabilities in your gems. Run
    bundler-audit frequently or use
    a service like Snyk, GuardRails
    (both free for open-source development).

Security Tools

  • [ ] Run Brakeman before each deploy.
    If using an automated code review tool like
    Code Climate, enable the Brakeman
    engine
    .
  • [ ] Adding a gem trust policy with MediumSecurity is a good way to stop
    malicious gems getting installed on the server. For example,
    bundle --trust-policy MediumSecurity.
  • [ ] You can use rubocop gem and enables security-related rules in the
    .rubocop.yml configuration file.
  • [ ] Consider using a continuous security service such as
    Detectify.
  • [ ] Consider using a Web Application Firewall (WAF) such as
    NAXSI for Nginx,
    ModSecurity for Apache and Nginx.
    Mitigates XSS, SQL Injection, DoS, and many other attacks.

Resources:

Testing

  • [ ] Include security tests in your test suite. Look at OWASP’s
    RailsGoat application for examples of
    security-related Capybara
    specs
    .
    Raises additional security awareness and mitigates security-related
    regressions.
  • [ ] Create security tests in pairs: one for the access denied scenario and
    another for the access granted scenario.
  • [ ] When using TDD, consider implementing authentication in the early stages
    of development, as it tends to break multiple preexisting tests.

Others

  • [ ] Use strong parameters in the controllers. This is the default behavior as
    of Rails 4+. Mitigates mass assignment attacks such as overwriting the role
    attribute of the User model for privilege escalation purposes.
  • [ ] Implement Captcha or Negative Captcha on publicly exposed forms.
    reCAPTCHA is a great option, and
    there is a gem that facilitates Rails
    integration. Other options are the
    rucaptcha and
    negative-captcha gems.
    Mitigates automated SPAM (spambots).

Details and Code Samples

Command Injection example

# User input
params[:shop][:items_ids] # Maybe you expect this to be an array inside a string.
                          # But it can contain something very dangerous like:
                          # "Kernel.exec('Whatever OS command you want')"

# Vulnerable code
evil_string = params[:shop][:items_ids]
eval(evil_string)

If you see a call to eval you must be very sure that you are properly sanitizing
it. Using regular expressions is a good way to accomplish that.

# Secure code
evil_string = params[:shop][:items_ids]
secure_string = /\[\d*,?\d*,?\d*\]/.match(evil_string).to_s

eval(secure_string)

Password validation regex

We may implement password strength validation in Devise by adding the
following code to the User model.

validate :password_strength

private

def password_strength
  minimum_length = 8
  # Regex matches at least one lower case letter, one uppercase, and one digit
  complexity_regex = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/
  # When a user is updated but not its password, the password param is nil
  if password.present? &&
    (password.length < minimum_length || !password.match(complexity_regex))
    errors.add :password, 'must be 8 or more characters long, including 
                           at least one lowercase letter, one uppercase
                           letter, and one digit.'
  end
end

Pundit: ensure all actions are authorized

Add the following to app/controllers/application_controller.rb

after_action :verify_authorized, except: :index, unless: :devise_controller?
after_action :verify_policy_scoped, only: :index, unless: :devise_controller?

Add the following to controllers that do not require authorization. You may
create a concern for DRY purposes.

after_action_skip :verify_authorized
after_action_skip :verify_policy_scoped

Pundit: only display appropriate records in select boxes

Think of a blog-like news site where users with editor role have access to
specific news categories, and admin users have access to all categories. The
User and the Category models have an HMT relationship. When creating a blog
post, there is a select box for choosing a category. We want editors only to see
their associated categories in the select box, but admins must see all
categories. We could populate that select box with user.categories. However,
we would have to associate all admin users with all categories (and update these
associations every time a new category is created). A better approach is to use
Pundit Scopes to determine which
categories are visible to each user role and use the policy_scope method when
populating the select box.

# app/views/posts/_form.html.erb
f.collection_select :category_id, policy_scope(Category), :id, :name

Convert filter_parameters into a whitelist

Developers may forget to add one or more parameters that contain sensitive data
to filter_parameters. Whitelists are usually safer than blacklists as they do
not generate security vulnerabilities in case of developer forgetfulness.
The following code converts filter_parameters into a whitelist.

# config/initializers/filter_parameter_logging.rb
if Rails.env.production?
  # Parameters whose values are allowed to appear in the production logs:
  WHITELISTED_KEYS = %w(foo bar baz)
  
  # (^|_)ids? matches the following parameter names: id, *_id, *_ids
  WHITELISTED_KEYS_MATCHER = /((^|_)ids?|#{WHITELISTED_KEYS.join('|')})/.freeze
  SANITIZED_VALUE = '[FILTERED]'.freeze
  
  Rails.application.config.filter_parameters << lambda do |key, value|
    unless key.match(WHITELISTED_KEYS_MATCHER)
      value.replace(SANITIZED_VALUE)
    end
  end
else
  # Keep the default blacklist approach in the development environment
  Rails.application.config.filter_parameters += [:password]
end

rack-cors configuration

module Sample
  class Application < Rails::Application
    config.middleware.use Rack::Cors do
      allow do
        origins 'someserver.example.com'
        resource %r{/users/\d+.json},
          headers: ['Origin', 'Accept', 'Content-Type'],
          methods: [:post, :get]
      end
    end
  end
end

Throttling Requests

On some pages like the login page, you’ll want to throttle your users to a few
requests per minute. This prevents bots from trying thousands of passwords
quickly.

Rack Attack is a Rack middleware that provides throttling among other features.

Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
  req.params['email'] if req.path == '/login' && req.post?
end

When I18n key ends up with _html

Instead of the following example:

# en.yml
en:
  hello: "Welcome <strong>%{user_name}</strong>!"
<%= t('hello', user_name: current_user.first_name).html_safe %>

Use the next one:

# en.yml
en:
  hello_html: "Welcome <strong>%{user_name}</strong>!"
<%= t('hello_html', user_name: current_user.first_name) %>

HAML: XSS protection

By default,

="<em>emphasized<em>"
!= "<em>emphasized<em>"

compiles to:

&lt;em&gt;emphasized&lt;/em&gt;
<em>emphasized<em>

Authors

  • Bruno Facca - LinkedIn -
    Email: bruno at facca dot info

Contributing

Contributions are welcome. If you would like to correct an error or add new
items to the checklist, feel free to create an issue followed by a PR. See the
TODO section for contribution suggestions.

If you are interested in contributing regularly, drop me a line at the above
e-mail to become a collaborator.

TODO

  • Add sample tests (RSpec and/or Minitest) to detect the presence of
    vulnerabilities. See OWASP’s RailsGoat security-related Capybara
    specs
    for
    inspiration.
  • Compare upload gems regarding their implementation of the File
    Uploads
    items of this checklist (build a table).
  • Compare authentication gems regarding their implementation of the
    Authentication items of this checklist (build a table).

References and Further Reading

License

Released under the MIT License.