Treat ORM queries like any other injection surface. Use parameterized query APIs, avoid string concatenation, and validate or constrain any user-supplied field used in a filter or sort clause. ORM abstraction does not neutralize attacker-controlled input by itself. Security review should also include taint tracking and tests that follow data through query builders, not just simple pattern matching.
Why HQL and JPQL Injection Happens in ORM Code
HQL and JPQL are still query languages, so the same core mistake that drives SQL injection applies here: attacker-controlled text becomes part of the query structure instead of the query data. ORM APIs reduce boilerplate, but they do not make concatenated input safe. The real boundary is whether user input is treated as a value or as executable query syntax.
That distinction matters most when developers build dynamic filters, search clauses, pagination logic, or sortable fields from request parameters. A safe ORM query usually keeps the structure fixed and binds values separately. When the code starts assembling fragments for entity names, predicates, or order clauses, the injection risk returns even if the application never touches raw SQL.
For teams that want a broader appsec baseline, the OWASP Top 10 is the right frame of reference: injection remains a top web risk because the attack pattern is about untrusted input crossing into executable syntax, not about a specific database API.
Which Query Parts Are Safe to Parameterize, and Which Are Not?
Bind variables solve the value side of the problem, so user-controlled strings, numbers, dates, and identifiers that appear as literal values should go through parameters. That includes exact-match filters, ranges, and most search conditions. The ORM should hand those values to the database driver as data, not splice them into the HQL or JPQL text.
Identifiers and keywords are different. Field names in an order-by clause, entity names, direction flags, and some dynamic predicate fragments are part of the query structure, so they cannot usually be parameterized the same way as values. If the application must accept them from users, it should constrain them to a strict allowlist, map them from a fixed lookup table, or translate them into server-side enums before query construction.
That control boundary is closely aligned with the OWASP Cheat Sheet Series, which consistently recommends parameterization for data and strict input handling for any remaining dynamic query structure.
What a Safe ORM Query Pattern Looks Like in Practice
A safe pattern keeps the query template stable and varies only the bound data. For example, a search by customer name, status, or date range should use named parameters, while the application decides the allowed sort column from a fixed set. If the user requests a field that is not on the approved list, the code should reject it or fall back to a known-safe default rather than trying to “sanitize” arbitrary syntax.
Security review should also cover how the query is assembled across helper methods. Injection often appears when one layer builds a base query, another layer appends optional filters, and a third layer adds sorting from request parameters. Taint tracking is useful here because the dangerous path may be indirect: the unsafe string may travel through a builder, mapper, or repository abstraction before it reaches the final query string.
From a secure development perspective, the OWASP SAMM view is helpful because it treats secure query construction as a development practice, not just a code review issue. Teams should be able to point to the rule that limits dynamic query parts, not just to one-off fixes after a bug is found.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
OWASP ASVS and CIS Controls v8 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| OWASP ASVS | V5 — File Handling | Covers injection-safe handling of user-controlled input before it reaches query construction. |
| V8 — Authorization | Dynamic query structure can expose unauthorized rows or sorts if user input changes access scope. | |
| V15 — Secure Coding and Architecture | Secure ORM query construction is an architectural coding practice that must resist injection by design. | |
| Recommendation — Use parameterized APIs and reject free-form query fragments before they reach the ORM. Restrict user-controlled query paths to allowlisted fields and server-side authorization rules. Design query builders so input becomes data, not query syntax. | ||
| CIS Controls v8 | CIS-16 — Application Software Security | Directly addresses secure handling of application input and unsafe query construction. |
| CIS-6 — Access Control Management | Sorting and dynamic query filters can change what data a user can reach. | |
| Recommendation — Test ORM code for injection paths and fix concatenation in repository layers. Constrain dynamic query fields to approved server-side choices. | ||
Practitioner Guidance
What to verify: Verify that every user-influenced query value is bound, and that every user-influenced query structure element, especially sort fields, entity names, and operators, is drawn from a fixed allowlist. If a code path still accepts free-form HQL or JPQL fragments, treat it as an injection candidate even if most inputs are already parameterized.
Common mistake: Developers often parameterize the WHERE clause but leave ORDER BY, field selection, or query concatenation “for convenience.” That is where many ORM injection bugs survive code review, because the unsafe part looks small and peripheral while still controlling query execution.
Practitioner takeaway: The safe default is to keep ORM queries declarative, bind values only, and convert any dynamic query structure into a constrained server-side choice before the query ever reaches the ORM.
Related resources from NHI Mgmt Group
- How should Rails teams prevent SQL injection when building database queries from user input?
- How should Go teams prevent SQL injection when building database queries from user input?
- How should developers prevent SQL injection when user input must be handled safely?
- How should developers prevent SQL injection in login forms and API queries?