arel本身不防sql注入,仅是生成sql字符串的dsl工具;其安全性取决于使用方式:必须将arel对象直接传给where等方法参与参数化,不可用to_sql后喂给find_by_sql,且字段名等结构部分须白名单校验。

Arel 本身不防 SQL 注入,它只是生成 SQL 字符串的 DSL 工具;你用它拼出来的字符串,最终仍要交给 where、find_by_sql 等方法执行——而这些方法是否安全,取决于你怎么传参,不取决于 Arel 有没有“自动消毒”。
Arel 生成的 SQL 字符串必须走参数化入口
Arel 的常见误用是:用它拼出带用户输入的条件,再转成 SQL 字符串,最后喂给 find_by_sql 或 where 的字符串形式。这种写法等于白忙活。
-
错误示例:
users = Arel::Table.new(:users) sql = users.where(users[:email].eq(params[:email])).to_sql User.find_by_sql(sql) # ❌ 日志里看到 raw SQL,无绑定上下文,完全裸奔
-
正确做法是:把
Arel对象直接传给 ActiveRecord 方法(如where),让它参与绑定流程:users = Arel::Table.new(:users) condition = users[:email].eq(params[:email]).and(users[:status].eq("active")) User.where(condition) # ✅ condition 被识别为 Arel 节点,值自动参数化 如果你非得用
to_sql(比如调试),就绝不能把它塞进find_by_sql——那和手写字符串插值没区别。
Arel 的字段名、表名、函数名仍需白名单校验
Arel 允许动态构造字段引用,比如 users[params[:column]],但这会绕过所有防护:
-
users[params[:column]].eq("value")中,若params[:column]是"email; DROP TABLE users--",Arel不会拦截,to_sql输出的就是危险字符串; - 即使你把它传给
User.where(...),Active Record 也不会校验字段是否存在或是否合法——它只负责把值参数化。
所以,对任何来自用户输入的结构部分(列名、排序字段、JSONB 路径、自定义函数名),都得先白名单过滤:
%w[email name created_at].include?(params[:column]) ? params[:column] : :email- 或更健壮地查元数据:
User.column_names.include?(params[:column]) ? params[:column] : :id
Arel 和 sanitize_sql 不兼容,别混用
ActiveRecord::Base.sanitize_sql 只处理数组形式的参数化片段(如 ["WHERE email = ?", params[:email]]),它不接受 Arel 对象,也不理解 Arel 的 AST 结构。
- 你不能写:
sanitize_sql(users[:email].eq(params[:email]))—— 会报错; - 也不能指望
Arel“自带转义”:它的to_sql输出是纯字符串,没有占位符,也没有绑定上下文。
真正需要手写 SQL 的场景(如 PostgreSQL 的 jsonb_path_query),应放弃 Arel,改用 sanitize_sql 处理值 + 白名单控制结构,例如:
sql = ActiveRecord::Base.sanitize_sql([ "SELECT * FROM users WHERE data @? '$.tags[*] ? (@ == ?)'", params[:tag] ]) User.find_by_sql(sql)
注意:'$.tags[*] ? (@ == ?)' 是硬编码的 JSONPath 表达式,不可由用户控制。
最常被忽略的一点:Arel 的安全性完全依赖于你把它用在哪儿、怎么用——它不是防护层,而是构造器;一旦你调用 to_sql,你就已经退出了 ActiveRecord 的参数化世界。











