Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
Development Version
-------------------

Nothing yet.
* IMPORTANT: Dollar signs are now escaped in the `php` output format. PHP
interpolates `$var`, `${expr}` and `{$expr}` inside double-quoted strings,
so SQL text containing them could inject PHP code into the generated
snippet (CVE-2026-112307, pr930 by xiehuanyi).


Release 0.6.0 (Aug 13, 2026)
Expand Down
8 changes: 6 additions & 2 deletions sqlparse/filters/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,13 @@ def _process(self, stream, varname, has_nl):

# Escape backslashes before quotes so a backslash preceding a
# quote cannot break out of the generated string literal
# (GHSA-3496-9g83-7v6x).
# (GHSA-3496-9g83-7v6x). Dollar signs are escaped last because
# PHP interpolates $var, ${expr} and {$expr} inside double-quoted
# strings (CVE-2026-112307).
else:
token.value = token.value.replace('\\', '\\\\').replace('"', '\\"')
token.value = (token.value.replace('\\', '\\\\')
.replace('"', '\\"')
.replace('$', '\\$'))

# Put the token
yield sql.Token(T.Text, token.value)
Expand Down
39 changes: 39 additions & 0 deletions tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,45 @@ def test_php_escapes_backslashes(self):
f = lambda sql: sqlparse.format(sql, output_format='php')
assert f(sql) == '$sql = "select \'\\\\foo\\\\\'";'

@pytest.mark.parametrize('sql, expected', [
# plain variable interpolation
("SELECT '$name'", r'''$sql = "SELECT '\$name'";'''),
# expression interpolation payloads (CVE-2026-112307)
("SELECT '${expr}'", r'''$sql = "SELECT '\${expr}'";'''),
("SELECT '{${expr}}'", r'''$sql = "SELECT '{\${expr}}'";'''),
("SELECT '{$obj->prop}'", r'''$sql = "SELECT '{\$obj->prop}'";'''),
# PostgreSQL dollar-quoted strings
('SELECT $body$text$body$',
r'''$sql = "SELECT \$body\$text\$body\$";'''),
# quoted identifiers and comments
('SELECT "$table"', r'''$sql = "SELECT \"\$table\"";'''),
('SELECT 1 /* $name */', r'''$sql = "SELECT 1 /* \$name */";'''),
# an existing backslash must not neutralize the dollar escape
("SELECT '\\$name'", r'''$sql = "SELECT '\\\$name'";'''),
])
def test_php_escapes_dollar_signs(self, sql, expected):
# CVE-2026-112307: "$" starts variable/expression interpolation in
# PHP double-quoted strings and must be escaped.
assert sqlparse.format(sql, output_format='php') == expected

def test_php_dollar_escape_multiline(self):
sql = 'select $a$x$a$\nfrom foo where b = \'$c\''
formatted = sqlparse.format(sql, output_format='php')
assert formatted == '\n'.join([
'$sql = "select \\$a\\$x\\$a\\$ ";',
'$sql .= "from foo where b = \'\\$c\'";'])

def test_php_variable_name_keeps_dollar(self):
# only SQL content is escaped, not the generated assignment target
formatted = sqlparse.format('select 1; select 2', output_format='php')
assert formatted == '$sql = "select 1; ";\n$sql2 = "select 2";'

def test_python_dollar_signs_unchanged(self):
# "$" has no special meaning in single-quoted Python strings
sql = "select '$name' from $t$x$t$"
formatted = sqlparse.format(sql, output_format='python')
assert formatted == "sql = 'select \\'$name\\' from $t$x$t$'"

def test_sql(self):
# "sql" is an allowed option but has no effect
sql = 'select * from foo;'
Expand Down