Skip to content
AgileLogics
7 min read

Security Fundamentals

Prerequisites: Working with databases, Environment variables and configuration

Most security bugs in Ruby applications come from a short list of mistakes. Knowing them covers most of the risk.

1. Injection — never mix data into code

Untrusted input must never become part of SQL, shell commands, or Ruby code.

  • SQL — use placeholders (previous lesson).
  • Shell commands — pass arguments as separate strings, so no shell is involved and nothing is interpreted:
require "open3"

filename = "report.pdf; rm -rf ~"   # imagine this came from a user

# DANGEROUS: a single string goes through the shell
# system("convert #{filename} out.png")

# Safe: each argument is passed as-is
output, status = Open3.capture2e("file", "--brief", filename)
  • Ruby code — never call eval, instance_eval with a string, or send/constantize with user input. Map allowed input to behavior with a hash or case instead.

2. Unsafe deserialization

Loading data formats that can create arbitrary objects lets an attacker run code:

  • Use JSON.parse (safe) for data exchange.
  • Use YAML.safe_load / YAML.load_file (safe by default in current Psych) — never YAML.unsafe_load on untrusted input.
  • Never Marshal.load data from outside your own system.

3. Secrets

  • Keep secrets out of source code and logs: use environment variables or an encrypted credentials store (Rails has one built in).
  • Generate tokens with SecureRandom, never rand:
require "securerandom"

p SecureRandom.hex(16).length
p SecureRandom.urlsafe_base64(24).match?(/\A[\w-]+\z/)
32
true
  • Compare secrets in constant time to avoid timing attacks: ActiveSupport::SecurityUtils.secure_compare in Rails, or Rack::Utils.secure_compare in Rack apps.

4. Passwords

Never store passwords, even encrypted. Store a slow, salted hash using bcrypt (the bcrypt gem; Rails' has_secure_password uses it):

require "bcrypt"

hash = BCrypt::Password.create("correct horse battery staple")
p BCrypt::Password.new(hash) == "correct horse battery staple"

General-purpose hashes like MD5 or SHA-256 are far too fast to protect passwords — attackers can try billions of guesses per second.

5. Validate input with anchored patterns

Use \A and \z in validation regexes, not ^ and $ (see the Regular Expressions lesson). Validate on the server even when the browser also validates.

6. Dependencies

Your gems are code running with your app's full permissions.

  • Run bundler-audit (bundle exec bundler-audit check --update) or GitHub's Dependabot to be alerted to gems with known vulnerabilities.
  • Keep Ruby itself on a supported version — each release series gets security fixes for a limited time.
  • For Rails apps, Brakeman statically scans for common vulnerabilities.

Common mistakes

  • Trusting data because it came from your own front end. Anything in a request can be changed by the sender.
  • Showing full error details and backtraces to users in production.