This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new a03cabffa77 perf(csv): avoid regex in CSV value escaping (#40195)
a03cabffa77 is described below

commit a03cabffa774a9ed58ca0a3b915828bb383248ae
Author: Dhimas Ardinata <[email protected]>
AuthorDate: Sun Jul 12 01:52:50 2026 +0700

    perf(csv): avoid regex in CSV value escaping (#40195)
    
    Co-authored-by: Superset Dev <[email protected]>
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 superset/utils/csv.py               | 48 +++++++++++++++++++++++++------------
 tests/unit_tests/utils/csv_tests.py |  3 +++
 2 files changed, 36 insertions(+), 15 deletions(-)

diff --git a/superset/utils/csv.py b/superset/utils/csv.py
index 5c6decdc76a..20d17702c1d 100644
--- a/superset/utils/csv.py
+++ b/superset/utils/csv.py
@@ -15,7 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 import logging
-import re
 import urllib.request
 from typing import Any, Optional, Union
 from urllib.error import URLError
@@ -27,18 +26,37 @@ from superset.utils.core import GenericDataType
 
 logger = logging.getLogger(__name__)
 
-negative_number_re = re.compile(r"^-[0-9.]+$")
+PROBLEMATIC_CSV_PREFIXES: str = "-@+|=%"
 
-# This regex will match if the string starts with:
-#
-#     1. one of -, @, +, |, =, %, a tab, or a carriage return
-#     2. two double quotes immediately followed by one of -, @, +, |, =, %
-#     3. one or more spaces immediately followed by one of -, @, +, |, =, %
-#
-# A leading tab or carriage return is treated as dangerous on its own because
-# some spreadsheet software trims that leading whitespace and then evaluates
-# the remaining cell content as a formula.
-problematic_chars_re = 
re.compile(r'^(?:"{2}|\s{1,})(?=[\-@+|=%])|^[\-@+|=%\t\r]')
+
+def _starts_with_formula_prefix(value: str) -> bool:
+    first = value[0]
+    if first in PROBLEMATIC_CSV_PREFIXES:
+        return True
+    if first == '"' and len(value) > 2:
+        return value[1] == '"' and value[2] in PROBLEMATIC_CSV_PREFIXES
+    return False
+
+
+def _starts_like_spreadsheet_formula(value: str) -> bool:
+    # A leading tab or carriage return is treated as dangerous on its own
+    # because some spreadsheet software trims that leading whitespace and
+    # then evaluates the remaining cell content as a formula.
+    first = value[0]
+    if first in ("\t", "\r"):
+        return True
+    if first.isspace():
+        stripped = value.lstrip()
+        return bool(stripped) and _starts_with_formula_prefix(stripped)
+    return _starts_with_formula_prefix(value)
+
+
+def _is_negative_number(value: str) -> bool:
+    return (
+        len(value) > 1
+        and value[0] == "-"
+        and all("0" <= character <= "9" or character == "." for character in 
value[1:])
+    )
 
 
 def escape_value(value: str) -> str:
@@ -47,10 +65,10 @@ def escape_value(value: str) -> str:
 
     http://georgemauer.net/2017/10/07/csv-injection.html
     """
-    needs_escaping = problematic_chars_re.match(value) is not None
-    is_negative_number = negative_number_re.match(value) is not None
+    if not value:
+        return value
 
-    if needs_escaping and not is_negative_number:
+    if _starts_like_spreadsheet_formula(value) and not 
_is_negative_number(value):
         # Escape pipe to be extra safe as this
         # can lead to remote code execution
         value = value.replace("|", "\\|")
diff --git a/tests/unit_tests/utils/csv_tests.py 
b/tests/unit_tests/utils/csv_tests.py
index 17c60060875..bac067874c7 100644
--- a/tests/unit_tests/utils/csv_tests.py
+++ b/tests/unit_tests/utils/csv_tests.py
@@ -67,6 +67,9 @@ def test_escape_value():
     result = csv.escape_value(" =10+2")
     assert result == "' =10+2"
 
+    result = csv.escape_value('  ""=10+2')
+    assert result == '\'  ""=10+2'
+
     # A leading tab or carriage return followed by a dangerous char was already
     # handled by \s{1,} in the pre-existing regex. The cases below test the
     # new behavior: tab/CR alone (not followed by a dangerous char) are now

Reply via email to