fix: prevent SQL injection via SelectQueryBuilder.distinctOn (CVE-2026-76848) - #12804
fix: prevent SQL injection via SelectQueryBuilder.distinctOn (CVE-2026-76848)#12804Amine-H wants to merge 5 commits into
Conversation
Code Review by Qodo
1. payload comment repeats assertions
|
Address review feedback: resolve an exact registered alias prefix before applying strict identifier validation, so aliases containing characters such as hyphens remain usable. Document the new distinctOn restriction.
|
Thanks for the review. Both findings are addressed in commit 1. Updated 2. Registered aliases are no longer rejected
Tests added for both behaviors; the full |
…d of allowlisting Follow the query-builder's existing pattern: reject semicolons (statement stacking) rather than arbitrary expressions, and escape every value through the driver so correlated subqueries and other expressions cannot execute.
|
Updated the approach in commit
The strict allowlist from the earlier revision is removed. Tests and docs updated accordingly; full |
distinctOn now resolves relation-plus-referenced-column paths such as 'post.category.id' to the relation's join column (e.g. 'post.categoryId'), preserving the mapping the whole-query property replacement previously performed. The assertNoSemicolon JSDoc no longer claims sort/group usage.
|
Addressed the two remaining findings in commit 2. Relation column paths break — fixed. 4. Full |
distinctOn now resolves physical database column names that contain dots (e.g. 'post.profile.name') as a single identifier instead of splitting them into separate path segments, and the helper no longer carries redundant inline comments.
|
Addressed the remaining findings in commit 1. Verbose helper comments — removed. 5. Dotted column names split — fixed. 6. Full |
Description of change
Fixes CVE-2026-76848: SQL injection via
SelectQueryBuilder.distinctOn.Current behavior
SelectQueryBuilder.distinctOnaccepts an array of strings and stores them on the expression map without validation. For PostgreSQL-family drivers,createSelectDistinctExpressionjoins that array and interpolates the result into the generated statement asSELECT DISTINCT ON (values), with no escaping, quoting, identifier validation, or allowlist, and without routing the values throughreplacePropertyNamesor the driver's escape helper.Because the interpolation point is a parenthesized SQL expression list rather than an identifier-only position, a supplied element could carry arbitrary expressions — including correlated subqueries. An application that forwards a client-controlled value into
distinctOn(for example, to let a caller choose a deduplication column) allowed that client to read data anywhere the application's database role can reach through boolean or time-based inference, independently of the entity being queried.New behavior
distinctOnrejects semicolons, using the same guard as the group-by / order-by query-builder methods, to prevent statement stacking.createSelectDistinctExpressionescapes every value throughbuildDistinctOnExpression:post.authorare resolved to the actual database column via entity metadata, andescape()helper.Unknown property paths, metadata-less aliases (for example, subqueries), and arbitrary expressions are also escaped segment by segment, so no value is ever interpolated verbatim and nothing can execute as raw SQL.
How it was verified
test/functional/query-builder/sql-injection/sql-injection.test.tscovering escaping of valid property paths, aliases without metadata (including hyphens), rejection of semicolon-based statement stacking, and escaping of correlated-subquery /DROP TABLE/UNIONpayloads.distinct-onfunctional tests pass (3/3).query-buildertest suite passes (436 passing, 0 failures).tsc --noEmitand ESLint are clean.Pull-Request Checklist
masterbranchCloses #12805tests/**.test.ts)docs/docs/**.md)