To prevent SQL injection in Scala, send values to the database as bound parameters, not as text spliced into the query. Slick's sql interpolator does this for you, but its hash-dollar form inserts raw text and must never receive user input. Identifiers like table and column names need an allowlist.
What makes Slick interpolation safe?
The Slick documentation explains that its plain SQL interpolators turn each $value into a bound parameter. For an insert, the generated statement is always the same, with question marks in place of the values, and the docs say the value is not inserted directly into a query string, so there is no danger of SQL injection attacks.
That is the opposite of normal Scala string interpolation, and the two look almost identical. The danger is a developer who sees sql"..." and s"..." as interchangeable.
Where does it go wrong?
Slick documents an escape hatch: you can use #$ instead of $ in all interpolators when you need to splice raw text, for example a table name. Its example selects from a table held in a variable, with a bound parameter for the name column. The docs mention this for cases like abstracting over table names or running dynamically generated SQL.
Raw splicing is correct for a constant you control. It is a vulnerability for anything a user influences.
A constructed example: an export feature lets a user pick a sort column. The code reads sql"select * from orders order by #$col", where col is a query parameter. Sending a crafted value changes the statement. The same feature written with an allowlist maps the parameter to one of three known column names before splicing, and rejects everything else.
The Rails and OWASP guidance gives the same lesson in other stacks: attackers manipulate parameters to change database queries, to bypass authorization or read arbitrary data.
What does OWASP recommend?
OWASP's query parameterization cheat sheet recommends prepared statements with bound variables, and the idea applies to any language and library. Use it with whatever Scala database library you choose: bind values. For the few things that cannot be bound, such as identifiers, allowlist the permitted names in code.
How do you find the risky spots?
Search the code for these patterns: #$ in Slick strings, s" or + near words like select, insert, update, delete, where or order, and any raw Statement created from concatenated text. Check that each hit uses a constant or an allowlisted value. Also read stored procedure calls, since dynamic SQL inside a procedure has the same issue.
Least privilege reduces the blast radius. The database account for the app should not own the schema or read tables it does not need.
What to do
- Write queries with the
sqlorsqluinterpolators using$for every user value. - Ban
#$and manual concatenation in a linter rule or code review check, except for allowlisted constants. - For sort columns, table names and directions, map the input to a fixed set of values in code.
- Use type-checked or compile-time query features where your library offers them, and check the library's documentation for details.
- Give the application database user the minimum privileges.
- Add tests that send quote characters and common injection strings to each search and sort parameter and assert the results are unchanged.
- Scan dependencies, including the JDBC driver and connection pool. Safeguard's SBOM and dependency scanning can show known issues in the full Scala and Java tree.
Quick check
Is sql with a dollar sign safe in Slick?
Yes for values. The docs say the interpolated value becomes a bound parameter and is not inserted into the query string.
What about hash-dollar?
It splices raw text into the SQL. Use it only with constants or allowlisted values, never with user input.
Sources
- Slick: Plain SQL queries
- OWASP Query Parameterization Cheat Sheet
- Ruby on Rails Security Guide: SQL injection
Last reviewed: 18 October 2026.