Secure Rails
Everyone writing code must be responsible for security. đź”’
Start with the Rails Security Guide to see how Rails protects you.
Also, check out this guide for securing sensitive data.
Best Practices
Secrets
-
Keep secret tokens out of your code -
ENV
variables are a good practiceWhy: You don’t want your version control host, CI provider, or any other service with access to your code to have access to your secrets. If one of these services is compromised, or a single developer’s account on one of these services is compromised, you don’t want to lose your secrets.
SQL Injection
-
Even with ActiveRecord, SQL injection is still possible if misused
User.group(params[:column])
is vulnerable to injection. Learn about other methods
Why: This explains it well
Host Header Injection
-
Prevent host header injection - add the following to
config/environments/production.rb
config.action_controller.default_url_options = {host: "www.yoursite.com"} config.action_controller.asset_host = "www.yoursite.com"
Why: An attacker can pass a bad host header. If your app uses caching, this bad host may be cached and served to other users (this can happen with
*_url
helpers).
Data in Transit
-
Protect all data in transit with HTTPS - you can get free SSL certificates from Let’s Encrypt
Add the following to
config/environments/production.rb
config.force_ssl = true
Why: So attackers can’t eavesdrop or modify pages
-
Add your domain to the HSTS Preload List
config.ssl_options = {hsts: {subdomains: true, preload: true, expires: 1.year}}
Why: If someone visits your website over HTTP, even if you have an HTTPS redirect, an attacker can perform a middleperson attack. sslstrip is a popular tool for this. The preload list ships with the browser and instructs it to always use HTTPS for specific domains.
Data at Rest
-
Protect sensitive database fields with application-level encryption - use a library like Lockbox or attr_encrypted and possibly KMS Encrypted
Why: This protects sensitive data if the database or a database backup is compromised
-
Protect sensitive files with application-level encryption - use a library like Lockbox
Why: This protects sensitive data if file storage is compromised, or if someone accidentally makes an S3 bucket public
-
Make sure sensitive request parameters aren’t logged
Rails.application.config.filter_parameters += [:credit_card_number]
Use Logstop as an additional line of defense
Why: You don’t want sensitive data in your log files if they are compromised
Authentication
-
Use a trusted library like Devise for authentication (see Hardening Devise if applicable)
Why: Secure authentication is hard. Use a library that’s battle-tested. Don’t roll your own.
-
Notify users of password changes
Why: So users are aware if someone tries to hijack their account
-
Notify users of email address changes - send an email to the old address
Why: So users can’t silently hijack the account by changing the email, then the password
-
Rate limit login attempts by IP with Rack Attack
Why: To slow down credential stuffing attacks
-
Log all successful and failed login attempts and password reset attempts (check out Authtrail if you use Devise)
Why: So you have an audit trail when accounts are compromised. You can also use this information to detect compromised accounts.
-
Rails has a number of gems for authorization - we like Pundit
Why: To prevent users from accessing unauthorized data
Browser Caching
-
Set
autocomplete="off"
for sensitive form fields, like credit card numberWhy: So other users of the browser can’t access this saved information
-
Ask the browser not to cache pages with sensitive information
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "Sat, 01 Jan 2000 00:00:00 GMT"
Why: So other users of the browser can’t click the back button and view sensitive information
Data Leakage
-
Ask search engines not to index pages with secret tokens in the URL
<meta name="robots" content="noindex, nofollow">
Why: So search engines don’t index (and therefore expose) the tokens
Cross-Site Scripting (XSS)
-
Use
json_escape
when passing variables to JavaScript, or better yet, a library like Gon<script> var currentUser = <%= raw json_escape(current_user.to_json) %>; </script>
Why: To prevent cross-site scripting (XSS)
-
Be careful with
html_safe
Why: It bypasses escaping
-
Don’t use assets from a public CDN, as this creates unnecessary availability and security risk
Why: This adds another attack vector for an attacker
Open Source Tools
-
Brakeman is a great static analysis tool - it scans your code for vulnerabilities
-
bundler-audit checks for vulnerable versions of gems
gem install bundler-audit bundle audit check --update
To fix
Insecure Source URI
issues with thegithub
option, add to the top of yourGemfile
:git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") "https://github.com/#{repo_name}.git" end
And run
bundle install
. -
npm audit checks for vulnerable versions of JavaScript packages (if you use
package.json
) -
git-secrets prevents you from committing sensitive info
brew install git-secrets git secrets --register-aws --global git secrets --install git secrets --scan
Mailing Lists
Subscribe to ruby-security-ann to get security announcements for Ruby, Rails, Rubygems, Bundler, and other Ruby ecosystem projects.
Services
- Observatory scans your site for best practices
- Hakiri monitors for dependency and code vulnerabilities
- CodeClimate provides a hosted version of static analysis
- HackerOne allows you to enlist hackers to surface vulnerabilities
Additional Reading
Contributing
Have other good practices? Know of more great tools? Help make this guide better for everyone.
Also check out Production Rails.