Everyone writing code must be responsible for security. :lock:
Start with the Rails Security Guide to see how Rails protects you.
Also, check out this guide for securing sensitive data.
Keep secret tokens out of your code - ENV
variables are a good practice
Why: 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.
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
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).
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.
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
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
Set autocomplete="off"
for sensitive form fields, like credit card number
Why: 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-store, must-revalidate, private, max-age=0"
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
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
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
bundler-audit checks for vulnerable versions of gems
gem install bundler-audit
bundle audit check --update
To fix Insecure Source URI
issues with the github
option, add to the top of your Gemfile
:
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
.
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
Subscribe to ruby-security-ann to get security announcements for Ruby, Rails, Rubygems, Bundler, and other Ruby ecosystem projects.
Have other good practices? Know of more great tools? Help make this guide better for everyone.
Also check out Production Rails.