[13.x] Escape single quotes in Postgres JSON path attributes - #60923
Merged
taylorotwell merged 2 commits intoJul 30, 2026
Conversation
`PostgresGrammar::wrapJsonPathAttributes()` interpolates JSON path attributes into single quoted SQL string literals without escaping single quotes, so a quote in a JSON path terminates the literal early. The equivalent path is already escaped for every other driver by `CompilesJsonPaths::wrapJsonPath()` (laravel#28160), and within this same grammar by `compileJsonContainsKey()`, which uses `str_replace("'", "''", ...)`. `compileJsonUpdateColumn()` delimits path attributes with double quotes but still nests the result inside a `'{...}'` literal, so single quotes are escaped regardless of the configured delimiter.
taylorotwell
added a commit
that referenced
this pull request
Aug 14, 2026
…#61192) * [13.x] Escape single quotes in Postgres JSON path attributes `PostgresGrammar::wrapJsonPathAttributes()` interpolates JSON path attributes into single quoted SQL string literals without escaping single quotes, so a quote in a JSON path terminates the literal early. The equivalent path is already escaped for every other driver by `CompilesJsonPaths::wrapJsonPath()` (#28160), and within this same grammar by `compileJsonContainsKey()`, which uses `str_replace("'", "''", ...)`. `compileJsonUpdateColumn()` delimits path attributes with double quotes but still nests the result inside a `'{...}'` literal, so single quotes are escaped regardless of the configured delimiter. * Update PostgresGrammar.php --------- Co-authored-by: Anuragh K.P <36616831+kpanuragh@users.noreply.github.com> Co-authored-by: Taylor Otwell <taylor@laravel.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PostgresGrammar::wrapJsonPathAttributes()interpolates JSON path attributes into single quoted SQL string literals without escaping single quotes:A single quote in a JSON path therefore terminates the literal early:
This is inconsistent with the rest of the codebase in two ways:
Every other driver escapes this. MySQL, MariaDB, SQLite and SQL Server all go through
CompilesJsonPaths::wrapJsonPath(), which has appliedpreg_replace("/([\\\\]+)?\\'/", "''", $value)since [5.8] Correctly escape single quotes in json paths #28160. The same input is inert on those drivers. Postgres uses a separate->/->>code path that never received the equivalent treatment.This grammar already escapes it elsewhere.
PostgresGrammar::compileJsonContainsKey()usesstr_replace("'", "''", $lastSegment), which is exactly the escaping applied here.I checked the sinks that reach
wrapJsonPathAttributes()—select,addSelect,where/orWhere,whereIn,whereNull,whereNotNull,whereBetween,whereDate,orderBy,groupBy,having,join,whereJsonContains,whereJsonLength,updateandupsertare all affected.whereJsonContainsKeyis the only one that was already safe, via thestr_replaceabove.Note that plain (non-JSON) column names are not affected — identifier wrapping already neutralises those, e.g.
orderBy('id, (select ...)')compiles toorder by "id, (select ...)" asc. The JSON path is the only place where the quoting is escaped.On the update path
compileJsonUpdateColumn()passes"as the attribute delimiter, but the resulting path is still nested inside a'{...}'string literal:So escaping only the configured delimiter leaves that sink open. This PR escapes single quotes unconditionally, and additionally doubles the delimiter when it isn't a single quote.
Tests
Added
testPostgresJsonPathEscapingandtestPostgresUpdateJsonPathEscaping, mirroring the existingtestJsonPathEscapingcoverage added in #28160. Both fail on the current13.xand pass with this change. The fulltests/Databasesuite passes (2776 tests, 7659 assertions).I appreciate that the documentation advises against letting user input dictate column names, and I'd echo the advice given on #28160 that column names should still be whitelisted. This is submitted as a consistency fix so that the JSON path behaves the same across all supported drivers.