From 15dcb8a075c5a652fef4e48b172c829b5abc2f99 Mon Sep 17 00:00:00 2001 From: Sakthivel Subramanian Date: Tue, 21 Jul 2026 17:50:03 +0000 Subject: [PATCH] fix(spanner): escape embedded backticks in dbapi escape_name In google-cloud-spanner DB-API, escape_name() did not check for or double embedded backtick characters (`) when wrapping identifiers. This allowed identifiers containing backticks to break out of backtick-quoted identifier scopes (CWE-89 identifier injection). This commit updates escape_name() to: - Detect embedded backtick characters in identifier names. - Escape internal backticks by doubling them (replace("`", "``")) when enclosing identifiers in backticks. - Add unit test cases for embedded backticks in test_escape_name. --- .../google/cloud/spanner_dbapi/parse_utils.py | 18 ++++++++++++++---- .../unit/spanner_dbapi/test_parse_utils.py | 7 +++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/google-cloud-spanner/google/cloud/spanner_dbapi/parse_utils.py b/packages/google-cloud-spanner/google/cloud/spanner_dbapi/parse_utils.py index 90907c6a779e..4b24459584d7 100644 --- a/packages/google-cloud-spanner/google/cloud/spanner_dbapi/parse_utils.py +++ b/packages/google-cloud-spanner/google/cloud/spanner_dbapi/parse_utils.py @@ -384,8 +384,8 @@ def ensure_where_clause(sql): def escape_name(name): """ - Apply backticks to the name that either contain '-' or - ' ', or is a Cloud Spanner's reserved keyword. + Apply backticks to the name if it is not a valid regular ASCII identifier, + or if it is a Cloud Spanner's reserved keyword. :type name: str :param name: Name to escape. @@ -393,6 +393,16 @@ def escape_name(name): :rtype: str :returns: Name escaped if it has to be escaped. """ - if "-" in name or " " in name or name.upper() in SPANNER_RESERVED_KEYWORDS: - return "`" + name + "`" + if not name: + return name + + is_valid_regular_identifier = ( + (name[0].isalpha() or name[0] == "_") + and all(c.isalnum() or c == "_" for c in name) + and name.isascii() + ) + + if not is_valid_regular_identifier or name.upper() in SPANNER_RESERVED_KEYWORDS: + return "`" + name.replace("`", "``") + "`" + return name diff --git a/packages/google-cloud-spanner/tests/unit/spanner_dbapi/test_parse_utils.py b/packages/google-cloud-spanner/tests/unit/spanner_dbapi/test_parse_utils.py index 64000a0ae12d..d505ffe2bab2 100644 --- a/packages/google-cloud-spanner/tests/unit/spanner_dbapi/test_parse_utils.py +++ b/packages/google-cloud-spanner/tests/unit/spanner_dbapi/test_parse_utils.py @@ -403,6 +403,13 @@ def test_escape_name(self): ("with space", "`with space`"), ("name", "name"), ("", ""), + ("col`; DROP TABLE t; -- x", "`col``; DROP TABLE t; -- x`"), + ("table`name", "`table``name`"), + ("`", "````"), + ("col/*comment*/name", "`col/*comment*/name`"), + ("123column", "`123column`"), + ("col;select", "`col;select`"), + ("col\nname", "`col\nname`"), ) for name, want in cases: with self.subTest(name=name):