<- Back to the blog

The ultimate guide to securing data for Rails developers

Secure your apps! Protect sensitive data! Easy to say, harder to find solid answers on all the bits and pieces you need to adjust to make sure that happens. That's why we've put together this list of practical advice for securing your Ruby on Rails applications. Whether you're a Rails developer or work on any stack that relies on cloud technologies, we think you'll find something that stands out.

Use this in conjunction with the OWASP guidance on securing web applications to make sure your Rails apps are as secure as possible. Let's get to it!

Secure Communication

Force all incoming communication to HTTPS

For all traffic to your application to be encrypted though SSL:

config.force_ssl = true

This helps avoid attacks like session hijacking. More importantly, unencrypted HTTP communication sends all requests as plain text, meaning anyone listening in can see all the traffic and extract user data.

Force all outgoing communication to HTTPS

Make sure all your external HTTP calls are performed using HTTPS:

response = HTTParty.get('https://api.stripe.com/payment')

When using SMTP make sure to enable TLS connection

Sending email through SMTP protocol doesn’t necessary means it’s encrypted.

In Rails, to enable SSL encryption on SMTP traffic you need to explicitly enable it

config.action_mailer.smtp_settings = {
	...
	ssl: true
}

Avoid transferring data through FTP

Do not use FTP protocol to transfer data, especially file containing sensitive data.

In Rails it means avoiding the Net::FTP class:

require 'net/ftp'
ftp = Net::FTP.new('example.com')

Instead, use the SFTP protocol:

require 'net/sftp'
Net::SFTP.start('host', 'username', :password => 'password') do |sftp|
	...
end

Always verify SSL certificate validity

When using net-http, the default HTTP client in Ruby, by default SSL verification is on but sometimes developer turn it off for tests. Make sure this is not the case, avoid such code:

#Don't do this! 
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

#default
http.verify_mode = OpenSSL::SSL::VERIFY_PEER

Make sure it’s also the case for your SMTP configuration:

config.action_mailer.smtp_settings = {
  ...
  #openssl_verify_mode = OpenSSL::SSL::VERIFY_NONE
	openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER
}

Make sure database connection is secure from the client side

When setting-up rails database connection, always make sure that this connection is secure in production:

#database.yml
production:
  ...
  sslmode: require

Or even better, now with Rails 7 you can also force check that the server hostname matches the name stored in the server certificate. If the server certificate cannot be verified, the SSL connection will fail:

#database.yml
production:
  ...
  sslmode: verify-full
  sslcert: client.crt
  sslkey: client.key
  sslrootcert: ca.crt

Do not send sensitive data through HTTP Get parameters

Never include sensitive data in GET parameters, use POST instead.

For example, if you’re using Ruby default Net:HTTP library, change this code:

uri = URI('http://my.api.com/user/new')
params = { ssn: "516413249" }
uri.query = URI.encode_www_form(params)

res = Net::HTTP.get_response(uri)

to:

uri = URI('http://my.api.com/user/new')
res = Net::HTTP.post_form(uri, ssn: "516413249")

Encryption

Data-at-rest encryption prevents unauthorized users from making sense of your data if they gained access to your databases. 

Enable encryption for all your database instances storing sensitive data. For instance, you should encrypt your Amazon RDS database instances.

Encrypt data at the application level

Application-layer encryption encrypts data based upon the application that owns it, rather than for the storage medium where the data is stored. Data encryption and decryption is performed at the end application, so someone with legitimate access to a particular user account does not have full access to all the data stored in that account.

Use the Active Record library to encrypt sensitive data at the application layer:

class Article < ApplicationRecord
  encrypts :ssn
end

Do not use weak hash/encryption algorithms

According to OWASP, MD5, RC4, DES, Blowfish, SHA1. 1024-bit RSA or DSA, 160-bit ECDSA (elliptic curves), 80/112-bit 2TDEA (two key triple DES) are considered as weak hash/encryption algorithms and therefor shouldn’t be used.

Avoid using such libraries:

Digest::SHA1.hexdigest 'weak password encryption'
Crypt::Blowfish.new("weak password encryption")
RC4.new("weak password encryption")
OpenSSL::PKey::RSA.new 1024
OpenSSL::PKey::DSA.new 1024
Digest::MD5.hexdigest 'unsecure string'

Logging & Monitoring

Add monitoring and logging to your application

Security logging and monitoring is the ninth item of the OWASP Top Ten. Security logging consists of logging security information during the runtime operation of an application.

Third-party monitoring solutions such as Datadog, New Relic, and Splunk provide these capabilities.

Avoid logging sensitive data

​​For example, do not log passwords, session ID, credit cards, or social security numbers like this:

logger.info 'User ssn is: #{user.ssn}'

Sanitize log for sensitive data

In case you still log sensitive data, without knowing about it, add sanitation rules to remove sensitive data from your logs.

Configure Rails to sanitize sensitive data in log files:

config.filter_parameters << :ssn

Third-party monitoring and tracking solutions like Datadog, New Relic, Splunk and Sentry provide capabilities to scrub sensitive information from your logs, but beware it needs to be configured appropriately.

Third-parties

Monitor third-party libraries and packages for security flaws (aka CVE)

Vulnerabilities are software coding flaws or system misconfigurations through which attackers can directly gain unauthorized and privileged access to a system or network. 

It is estimated that 60-90% of the total codebase of a software project relies on open source or other third party code. If this code contains vulnerabilities, you are exposed.

Use a vulnerability scanner such as GitHub Dependabot, GitLab dependency scanner or Snyk to automate vulnerability testing across your dependencies. You can also use a tool like bundler-audit to further assess gem versions.

Monitor third-party integrations

Third-party data breaches happen when you share sensitive data with vendors with weak security standards. Make sure you have a process that cannot be bypassed to review new integrations with external services.

For each vendor, document:

  • The data you share with them (based on your data taxonomy).
  • Their processing location.
  • Their security standards and certifications (e.g., SOC2, ISO 27001).

Legal documentation such as contractual agreements, Service-Level Agreements, and Data Processing Agreements.

Cookies, Session, Cache, JWT

Avoid storing sensitive data in cookies

For example, avoid code like this:

cookies[:user_zip] = "32792"

Avoid storing sensitive data in JWT

Avoid practices like this:

def gen(hmac_secret)
    # ruleid: ruby-jwt-exposed-credentials
    payload = { data: 'data', password: "unsafe password" }
    token = JWT.encode payload, hmac_secret, 'HS256'
    puts token
end

Don’t store sensitive data using HTM5 Storage

Avoid using HTML5 storage to store sensitive data. For instance, avoid:

var emailObject = { 'email': "john@doe.com", 'password': "1234" };
localStorage.setItem('userData', emailObject);

Encrypt cookie data

In case sensitive data leaks into cookies, you should encrypt their data.

For instance, do:

cookies.encrypted[:user_id] = "d0b78d52-e267-11ec-8fea-0242ac120002"

Since Rails 6, the CookieStore uses the encrypted cookie jar to provide a secure, encrypted location to store session data. Cookie-based sessions thus provide both integrity as well as confidentiality to their contents. The encryption key, as well as the verification key used for signed cookies, is derived from the secret_key_base configuration value.

Safely store sessions

By default, Rails uses a Cookie based session store. This makes invalidating cookies difficult as they are stored on the client. Instead, the best practice is to use a database-based session, which is done easily in Rails through configuration:

Rails.application.config.session_store :active_record_store

Static file & Storage

Don't generate and store files locally

Avoid generating static files that contain sensitive data. If you have to do it, store them directly on a secure file storage system such as Amazon S3 with the appropriate permissions.

Avoid code like:

file = File.new("export.csv", "w")
# or 
file = File.open("export.csv", "w")

Rails provide a complete built-in storage mechanism, Active Storage, that we encourage you to use and especially to not set to “local” in production:

#config/environments/production.rb

####
# Avoid local storage in production
# config.active_storage.service = :local
####
config.active_storage.service = :amazon

‍

Close temporary files

When a Tempfile object is garbage collected, or when the Ruby interpreter exits, its associated temporary file is automatically deleted. This means that it is unnecessary to explicitly delete a Tempfile after use, though it's good practice to do so: not explicitly deleting unused Tempfiles can potentially leave behind large amounts of tempfiles on the filesystem until they're garbage collected. The existence of these temp files can make it harder to determine a new Tempfile filename.

Therefore, you should always call unlink or close in an ensure block, like this:

file = Tempfile.new('foo')
begin
   ...do something with file...
ensure
   file.close
   file.unlink   # deletes the temp file
end

‍

Do not store files containing sensitive data in your Git Repository

Do not store CSV, XLS, SQL dumps or TXT files containing sensitive data in your Git Repository.

Configure S3 buckets for different purposes

Configuring several S3 buckets for different uses gives you better control over the policy of each asset type.

# config/storage.yml
document_store:
  service: S3
  bucket: “my-document-bucket”
  ...
  upload:
    server_side_encryption: "" # 'aws:kms' or 'AES256'

analytic_store:
  service: S3
  bucket: “my-analytic-bucket”
  ...
  upload:
    server_side_encryption: "" # 'aws:kms' or 'AES256'

You can define a default bucket in your application.rb.

# config/application.rb
config.active_storage.service = :document_store

If you want to use the analytic_store bucket, you can update your model to use it explicitly

# app/models/organization.rb
has_one_attached :report, service: :analytic_store

Or if you want to generate a pre-signed URL:

# app/services/request_direct_upload_service.rb
ActiveStorage::Blob.create_before_direct_upload!(
  ...
  service_name: :analytic_store
)

Encrypt files containing sensitive data in S3

Amazon S3 provides a built-in server-side encryption mechanism at the object level to securely store your data.

If you’re using Rails’ built-in Active Storage solution, make sure to activate the server-side encryption setting in the configuration:

#config/storage.yml
amazon:
  ...
  upload:
    server_side_encryption: "" # 'aws:kms' or 'AES256'

Credentials

Store password safely

Use well known open source libraries such as Devise or Authlogic, or use the built-in Rails method has_secure_password that by default uses a bcrypt password-hashing function.

Implement proper password strength controls

OWASP recommends a password length of at least 8 characters. Here is a validation constraint example below:

validates :password, presence: true, length: { minimum: 8, maximum: 255 }

Encrypt credentials

Never commit your credentials in plain text: 

#database.yml
production:
  ...
  password: "unsafe password"

Instead, use a secret manager solution. Rails provide a built-in system allowing to safely store and use credentials. Otherwise, you can use a third-party solution as AWS Secrets Manager, AWS Key Management Service, or HashiCorp Vault.

Do not use the built-in database accounts

Make sure to never use built-in database accounts, as postgres, admin or root.

Monitor for leaked tokens in your Git repositories

Use GitHub secret scanner, GitLab secret detection or a solution like GitGuardian.

Avoid misconfigurations

Protect against CSRF attacks

Cross-Site Request Forgery (CSRF) vulnerabilities allow an attacker to perform actions on behalf of a user.

Rails provide out-of-the box protection against CSRF attacks, just make sure to have enabled it in your configuration as documented here:

Rails.application.config.action_controller.default_protect_from_forgery = true

Do not use subshell commands with user input

Subshell commands are not recommended to be used in web applications. In case you still need them, avoid associating it with user inputs.

#subshell command in Ruby
system 'ls {file}'
%x {ls file}
`ls {file}`
exec("ls {file}")
spawn("ls {file}")
open("| ls {file}")

Conduct database backups

Enable automated backups

Database backups ensure that all your data is available and recoverable in case of loss or corruption due to human error, natural calamities, or malicious black hat hackers.

Your cloud provider should provide automated backup functionalities. Ensure that:

  1. Automated backup is enabled on all your database instances.
  2. The backup retention period is configured in accordance with your Disaster Recovery Plan, your Recovery Point Objectives, and your personal data retention policy if you are subject to privacy laws such as GDPR.

For instance, you should enable automated backups of your Amazon RDS database instances.

Enable cross-region backups

Cross-region automated backups feature enables disaster recovery capability for mission-critical databases by providing you the ability to restore your database to a specific point in time within your backup retention period. 

This allows you to quickly resume operations in the event that your cloud provider services becomes unavailable in your primary region. For instance, you should configure your Amazon RDS database instances to replicate snapshots and transaction logs to another AWS Region

Conduct regular security assessments

Conduct security audits

A security audit is an evaluation of an organization’s security controls against an established set of standards.

They are necessary if you are willing to get information security certifications such as ISO 27001 and SOC 2, which you can then leverage to demonstrate your strong security posture to prospects and customers.

They are also needed if you are subject to data regulations such as HIPAA or PCI.

Perform penetration tests

A penetration test simulates a real-world attack. Testers will attempt to identify and exploit any vulnerabilities within your system. Perform penetration tests at least once a year.

Document your data flows

Catalog your applications’ components

Build a central and unified view of your applications’ architecture by cataloging: 

  • Applications, or code repositories.
  • Databases, data stores and data warehouses.
  • Internal services (APIs, message brokers, gateways, etc.) 
  • External services, or third-party API integrations.

Map sensitive data flows

You can’t secure your data if you don’t know where it lives. Following your data taxonomy, document the data your applications’ components process. 

You can do this manually by digging through your code repositories and storage systems, and by collecting information from developers. You can also rely on data discovery and classification tools to automate this process.

That's it!

If only that were the case. There are always going to be new threats, new best practices, and new risks. Make sure you're in touch with your security team and leadership to make sure any security controls and policies are clear. We'll be sure to add to this list as new techiques arise too. See anything we missed? Let us know on Twitter @trybearer.

Engineering
Share this article:

Bring data security to DevOps

Get a personalized demo to see how Bearer helps you reduce risks of data breaches across your application environment.