Search for the calls that accept a raw SQL string: queryRawUnsafe and executeRawUnsafe in Prisma, query in Sequelize, createQuery or native queries in Hibernate, and text in SQLAlchemy. Each is safe when values travel as parameters and unsafe when request data is concatenated into the string.
Why list them
An ORM protects you while you use its builder methods. The risk lives in the escape hatch for the query the builder cannot express. These calls are rare, which is why they are worth listing. A reviewer can find all of them in minutes.
The table
| Library | Escape hatch to search for | Safe form from the docs | Unsafe form |
|---|---|---|---|
| Prisma | queryRawUnsafe, executeRawUnsafe | queryRaw or executeRaw with a template tag | Building the string, then passing it to an Unsafe method |
| Sequelize | sequelize.query | replacements or bind in the options | A template string with request data inside |
| Hibernate | createQuery, native queries | Named parameters with setParameter | Concatenating a variable into the query text |
| SQLAlchemy | text | Colon names such as :user_id with values passed separately | An f string or concatenation passed to text |
Each safe form comes from the library's own documentation. Prisma says the template tag form is safe with no string building. Sequelize describes replacements and bind parameters. The Hibernate guide says concatenating user input into HQL is extremely dangerous. SQLAlchemy documents bind parameters specified by name using the colon format.
One example per hatch
# SQLAlchemy: values passed separately
t = text("SELECT * FROM users WHERE id = :user_id")
result = connection.execute(t, {"user_id": uid})
// Hibernate: named parameter
Query q = session.createQuery("from Inventory where productID=:productid");
q.setParameter("productid", userInput);
The Prisma and Sequelize equivalents are in our posts on queryRaw versus queryRawUnsafe and Sequelize replacements versus bind. For Hibernate, see HQL injection.
What parameters cannot cover
Parameters carry values. They do not carry table names, column names, sort directions or keywords. For those, map a fixed choice to a known identifier in code. The Prisma docs state this limit for table names in queryRaw on PostgreSQL.
Turn the list into a check
- Add a code search for each escape hatch to CI and fail the build on a new match that has no review note.
- Keep an allowlist file of reviewed uses with a reason.
- For each hit, trace the argument back. If it reaches a request, rewrite it.
- Look for wrappers. A helper called runQuery that calls the escape hatch hides it from a naive search. Search for the helper too.
- Add a test that sends a quote character and a comment marker through each endpoint that uses a raw query.
Wrappers are the usual gap. Teams build a convenience function once and then every caller inherits its weakness.
When the ORM itself has a bug
Sometimes the library is at fault. The post on Django ORM injection edge cases covers cases where even builder methods were affected. Keep your ORM and driver versions current and watch advisories.
Safeguard scans dependencies, including ORMs and drivers, and can open fix pull requests when you turn that feature on. A free scan is at gold.safeguard.sh/scan.
Try it yourself
Run the code scan and look at how queries with request input are flagged. Use that as a model for the search patterns in the table above.
Click through it at your own pace, or open it full screen.
Quick check
Is the ORM builder always safe?
It is safer by design, but libraries have had bugs. Keep versions current.
Do wrappers matter?
Yes. A helper that calls the raw method hides it from a simple search, so search for the helper too.
Can a parameter hold a column name?
No. Map a fixed choice to a known identifier in code.
Sources
- Prisma: Raw queries
- Sequelize: Raw queries
- Hibernate query language guide
- SQLAlchemy: Column elements and expressions
Last reviewed: 19 October 2026.