Skip to content
CWE-89A03:2021 – Injection

Prevention — Ruby

ActiveRecord placeholders and hash conditions, and the where("...#{x}...") interpolation footgun that dominates Rails SQL injection.

The Rule

Use ActiveRecord's hash conditions or placeholder arrays. The one thing never to do is interpolate a variable into a string condition — where("name = '#{params[:name]}'") is the canonical Rails SQL injection, and it accounts for the overwhelming majority of real cases.

ActiveRecord parameterises hash and placeholder forms automatically. It does nothing for string interpolation, because by the time the string is built the value is already part of the SQL text.

ActiveRecord

Rubyusers_controller.rbVulnerable
# String interpolation into a condition. Injectable.
User.where("name = '#{params[:name]}'")

# The same in find_by / find_by_sql
User.find_by_sql(
  "SELECT * FROM users WHERE id = #{params[:id]}")

# calculate / pluck with interpolation
User.where("created_at > '#{params[:since]}'")

# order/pluck with raw input (identifier context)
Product.order(params[:sort])

Sequel

RubyVulnerable
DB["SELECT * FROM users WHERE name = '#{name}'"].all
DB[:users].where("name = '#{name}'").all

Rails-Specific Footguns

Beyond the obvious where("...#{}"), several ActiveRecord methods take raw SQL and are easy to feed user input:

  • order, reorder, group, having, select, pluck, joins all accept SQL strings. order(params[:sort]) is injectable in an identifier context. Rails 6.1+ added protection to order/pluck that raises on unrecognised input, but it is not a substitute for an allowlist and does not cover every method.
  • exists?, calculate, sum, count accept string conditions with the same interpolation risk.
  • sanitize_sql family exists (sanitize_sql_array, sanitize_sql_for_conditions) for the rare case you must build SQL, but prefer placeholders.
  • Arel is powerful but its .to_sql and raw nodes can reintroduce injection if fed strings.

The resource rails-sqli.org catalogues these method-by-method and is worth a look during review.

Checklist

  • Hash conditions (where(name: x)) by default.
  • Placeholder arrays (where("name = ?", x)) when a string condition is unavoidable.
  • Never #{} inside any query string — this is the rule that matters.
  • Identifiers (order, group, select targets): allowlist.
  • Grep for where(" followed by #{, and for find_by_sql/order/pluck with params.
  • Use Brakeman in CI — its SQL injection check is specifically tuned for these ActiveRecord patterns.
  • Least-privilege database role — see Defense in Depth.