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_evalwith a string, orsend/constantizewith user input. Map allowed input to behavior with a hash orcaseinstead.
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) — neverYAML.unsafe_loadon untrusted input. - Never
Marshal.loaddata 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, neverrand:
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_comparein Rails, orRack::Utils.secure_comparein 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.