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
# 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
DB["SELECT * FROM users WHERE name = '#{name}'"].all
DB[:users].where("name = '#{name}'").allRails-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,joinsall accept SQL strings.order(params[:sort])is injectable in an identifier context. Rails 6.1+ added protection toorder/pluckthat raises on unrecognised input, but it is not a substitute for an allowlist and does not cover every method.exists?,calculate,sum,countaccept string conditions with the same interpolation risk.sanitize_sqlfamily exists (sanitize_sql_array,sanitize_sql_for_conditions) for the rare case you must build SQL, but prefer placeholders.Arelis powerful but its.to_sqland 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,selecttargets): allowlist. - Grep for
where("followed by#{, and forfind_by_sql/order/pluckwith params. - Use Brakeman in CI — its SQL injection check is specifically tuned for these ActiveRecord patterns.
- Least-privilege database role — see Defense in Depth.
Related
Every ORM has an escape hatch to raw SQL, and every one of them is a footgun. Where the abstraction stops protecting you, and which method names to grep for.
Where your input lands in the query determines which payloads can possibly work. Get the context wrong and every payload fails for the wrong reason.
Input that is stored safely and then used unsafely somewhere else entirely. The payload and the vulnerable query live in different requests, different code paths, and often different applications.
Strict typing, stacked queries, and COPY TO PROGRAM — the engine where an injection most reliably becomes command execution.
Parameterisation removes the bug; these layers limit the blast radius when a parameterisation is missed. Least privilege, allowlists, monitoring, and what each is actually worth.