
Introduction
Building applications with Ruby on Rails can be a lot of fun. One of the great things about Rails is the framework takes care of so much of the heavy lifting that it’s easy to assume security is “handled for you.” But even a very well‑architected app can be undermined by a single misconfigured controller action, an unescaped parameter or an outdated gem. Let’s explore ten common ways Rails applications become security risks—and how you can fix them before a hacker finds them. As with any craft, developing good habits protect you and your users.
1. SQL Injection
SQL injections are a simple concept. When you drop user input directly into an SQL query you are effectively opening up a doorway directly into your database. An attacker an type SQL directly into a form field and make the database do something. A classic example generating a dump of other users’ data.
SQL injections commonly occur when string concatenation is used within a query.
# Bad idea: string interpolation within the query
“SELECT * FROM users WHERE email = ‘“ + userInput + “‘“
A hacker could enter ‘a’ OR 1=1 – which evaluates true for every row in the table, potentially returning the entire users list.
Rails already protects your query methods by using parameter binding. To avoid injection vulnerabilities, never build SQL by concatenating strings. Instead, use placeholders or named parameters:
# insecure
User.find_by("login = '#{params[:name]}' AND password = '#{params[:password]}'")
# secure: parameter binding
User.find_by(login: params[:name], password: params[:password])
# or with placeholders
User.where("post_code = ? AND quantity >= ?", entered_post_code, entered_quantity).first
Think of all external inputs as risky data and always let Rails handle the escaping.
2. Mass Assignment Vulnerabilities
Mass assignment is convenient: it allows you to set an entire params hash into create() or update(). The problem is that attackers can include unwanted keys, such as admin: true, and the model will happily set them. Rails uses strong parameters to counter this. To overcome mass assignment vulnerabilities define a private method in your controller that explicitly lists permitted fields:
def create
# Only permitted keys from resource_params can be mass-assigned
@resource = Resource.new(resource_params.merge(owner_id: current_user.id))
@resource.save!
render json: @resource, status: :created
end
Private
# Strong Parameters (general shape: scalars, arrays, nested objects)
def resource_params
params.require(:resource).permit(
:name, :email, :status,
addresses: [
:line1, :line2, :city, :postcode, :country
],
)
end
3. Session Hijacking and Cookie Security
Sessions let users stay logged in, but the session ID essentially acts as a bearer token. If an attacker can steal the cookie—via sniffing on an unencrypted Wi‑Fi network it’s game over.
To reduce this risk:
- Enforce HTTPS. Rails lets you enforce SSL across the app by adding config.force_ssl = true. This encrypts traffic so cookies aren’t sent in plain text.
Set secure cookie attributes. Configure the session store in config/application.rb:
Rails.application.config.session_store :cookie_store,
key: '_my_app_session',
httponly: true,
secure: Rails.env.production?,
same_site: :strict,
expire_after: 1.hour
The HttpOnly flag prevents JavaScript from reading the cookie, mitigating XSS attacks. The Secure flag ensures the cookie is sent only over HTTPS. SameSite: :strict stops the cookie from being sent on cross‑site requests (greatly reducing CSRF). The expiration prevents old sessions from lingering as is always a good practice.
4. Insecure File Uploads & Downloads
File uploads come with many pitfalls. An attacker can use a technique known as path traversal such as ../../../etc/passwd to overwrite system files. Always sanitize file names to remove dangerous characters and normalizes the path:
def sanitize_filename(filename)
filename.strip
.tap { |name| name.sub!(/\A.*(\\|\/)/, "") }
.gsub(/[^\w.-]/, "_")
end
Another approach is to avoid processing large files synchronously. Attackers can upload multiple files simultaneously to tie up your worker. This is a very simple form of DDoS attack. Save the file to storage and process it asynchronously (e.g., with Active Job).
5. Open Redirects
Redirecting users is common after login or in legacy actions. But if you pass through a user‑supplied host or to parameter, an attacker can redirect your users to a phishing site. For instance, calling redirect_to(params[:to]) will forward the browser to whatever URL the attacker provides. Instead, build a list of allowed hosts or relative paths and reject or ignore anything else:
ALLOWED_REDIRECTS = %w[/dashboard /profile]
def legacy
to = params[:to]
redirect_to(to) if ALLOWED_REDIRECTS.include?(to)
end
Never redirect blindly on untrusted input.
6. Secrets & Credentials
Rails makes it tempting to drop API keys and tokens into your config/*.yml files. Doing so and then committing them to Github means anyone with repository access has your secrets. There are bots constantly scanning Github, just waiting for an accidental public repo to share your credentials. Rails 5.2 introduced encrypted credentials to solve this. Secrets are stored in config/credentials.yml.enc and decrypted with a master key that you keep out of version control. To edit credentials, run:
bin/rails credentials:edit
Doing this will open a temporary decrypted file in your editor. You can make the changes and then re‑encrypt it. Store the config/master.key in a secure password manager or environment variable (RAILS_MASTER_KEY), and never commit it. This approach protects secrets at rest and reduces the risk of accidental exposure.
7. Outdated Gems & Dependencies
Gems are constantly updated to fix bugs and security issues. But many gems get abandoned and fall behind in critical maintenance actions. If you don’t stay on top of updates, your app may rely on a gem with known vulnerabilities. Use bundle audit (checks your Gemfile.lock against a CVE database) and bundle outdated to see which gems have newer versions. Do this liberally as part of your deploy process. Make a habit of updating gems regularly, ideally weekly or monthly. When updating, change one gem at a time and run your test suite, so you can track which update might introduce issues.
Conclusion
In Rails development security is a mindset. Rails provides a really good solid foundation, but it doesn’t exempt you from thinking critically about how data flows through your app. This is especially important for junior and mid level developers. You have to keep your eye on the points listed above. By understanding and mitigating these common risks you protect not just your users’ data but also your reputation as a developer.
Ps. if you have any questions
Ask here