codeant-ai-for-open-source[bot] commented on code in PR #42095:
URL: https://github.com/apache/superset/pull/42095#discussion_r3596836050


##########
superset/models/helpers.py:
##########
@@ -749,6 +749,109 @@ def modified(self) -> Markup:
 _NO_BYPASS: frozenset[type] = frozenset()
 
 
+def _make_metric_line_comments_safe(  # noqa: C901
+    expression: str,
+) -> tuple[str, bool]:
+    """Convert SQL line comments without regenerating otherwise valid SQL."""
+    result: list[str] = []
+    index = 0
+    quote: str | None = None
+    dollar_quote: str | None = None
+    backslash_escapes = False
+    in_block_comment = False
+    while index < len(expression):
+        if in_block_comment:
+            end = expression.find("*/", index)
+            if end < 0:
+                return expression, False
+            result.append(expression[index : end + 2])
+            index = end + 2
+            in_block_comment = False
+            continue
+        if dollar_quote:
+            end = expression.find(dollar_quote, index)
+            if end < 0:
+                return expression, False
+            result.append(expression[index : end + len(dollar_quote)])
+            index = end + len(dollar_quote)
+            dollar_quote = None
+            continue
+        if quote:
+            character = expression[index]
+            result.append(character)
+            index += 1
+            if backslash_escapes and character == "\\" and index < 
len(expression):
+                result.append(expression[index])
+                index += 1
+            elif character == quote:
+                if index < len(expression) and expression[index] == quote:
+                    result.append(expression[index])
+                    index += 1
+                else:
+                    quote = None
+                    backslash_escapes = False
+            continue
+
+        if expression.startswith("/*", index):
+            in_block_comment = True
+            continue
+        if expression.startswith("--", index):
+            line_end = len(expression)
+            for separator in ("\n", "\r"):
+                separator_index = expression.find(separator, index + 2)
+                if separator_index >= 0:
+                    line_end = min(line_end, separator_index)
+            contents = expression[index + 2 : line_end]
+            if "*/" in contents:
+                return expression, False
+            result.append(f"/*{contents} */")
+            index = line_end
+            continue
+        if expression[index] in {"'", '"'}:
+            quote = expression[index]
+            backslash_escapes = (
+                quote == "'"
+                and index > 0
+                and expression[index - 1] in {"E", "e"}
+                and (index == 1 or not expression[index - 2].isalnum())
+            )
+            result.append(quote)
+            index += 1
+            continue
+        if expression[index] == "$":
+            match = re.match(r"\$[A-Za-z_][A-Za-z0-9_]*\$|\$\$", 
expression[index:])
+            if match:
+                dollar_quote = match.group(0)
+                result.append(dollar_quote)
+                index += len(dollar_quote)
+                continue
+        result.append(expression[index])
+        index += 1
+    if quote or dollar_quote or in_block_comment:
+        return expression, False
+    return "".join(result), True
+
+
+def _normalize_custom_sql_metric(
+    expression: str,
+    engine: str,
+    normalizer: Callable[[str], str],
+) -> tuple[str, bool]:
+    """Normalize a metric and report whether its source is safe to preserve."""
+    expression = normalizer(expression)
+    if engine not in {"postgresql", "redshift"}:
+        return expression, False

Review Comment:
   **Suggestion:** The PostgreSQL-family guard omits the supported `postgres` 
backend alias, so alias-based Postgres connections skip the Postgres-specific 
safe-source path and fall back to generic clause sanitization behavior. This 
can reintroduce SQL regeneration differences for comment-bearing expressions on 
valid Postgres connections. Include the alias in this engine check (or key off 
the engine spec instead of a hard-coded string set). [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Postgres metrics on 'postgres://' skip comment-preserving safe path.
   - ⚠️ DATE_TRUNC unit normalization inconsistent across Postgres backend 
variants.
   - ⚠️ Alias Postgres connections can rehit grouping mismatch bug.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure a Database using a SQLAlchemy URI with the "postgres" scheme 
(for example
   "postgres://user:pass@host/db"); the Database.backend property at
   "superset/models/core.py:93-95" returns the backend name "postgres" based on 
the URL.
   
   2. Attach a SqlaTable and a SqlMetric to this Database, following the 
pattern in
   "superset/connectors/sqla/models.py:1230-25" where SqlMetric.get_sqla_col() 
reads
   metric.expression and then calls `_normalize_custom_sql_metric(expression,
   self.table.database.backend,
   self.table.database.db_engine_spec.normalize_custom_sql_metric)`.
   
   3. Use a metric expression matching the tested Postgres DATE_TRUNC patterns 
but with a
   trailing line comment, for example "DATE_TRUNC('QUARTER', created_at) -- 
trailing", as in
   "superset/tests/integration_tests/sqla_models_tests.py:80-15", and compile 
the metric via
   `metric.get_sqla_col()` on the alias-backed Database.
   
   4. When `_normalize_custom_sql_metric()` executes in 
"superset/models/helpers.py:141-145",
   the engine argument is "postgres", so the guard at lines 842-843 (`if engine 
not in
   {"postgresql", "redshift"}: return expression, False`) short-circuits before 
the
   PostgreSQL-family comment-safe path; saved metrics on "postgres://" backends 
never run
   `_make_metric_line_comments_safe()` and adhoc metrics processed via
   `_process_sql_expression()` likewise skip the `source_is_safe` restoration, 
causing
   alias-based Postgres connections to use generic sanitization semantics and 
potentially
   reintroducing the DATE_TRUNC grouping mismatch described in SC-112048.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=5e4de6699b50425ea333818f89c63bdb&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=5e4de6699b50425ea333818f89c63bdb&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/models/helpers.py
   **Line:** 842:843
   **Comment:**
        *Api Mismatch: The PostgreSQL-family guard omits the supported 
`postgres` backend alias, so alias-based Postgres connections skip the 
Postgres-specific safe-source path and fall back to generic clause sanitization 
behavior. This can reintroduce SQL regeneration differences for comment-bearing 
expressions on valid Postgres connections. Include the alias in this engine 
check (or key off the engine spec instead of a hard-coded string set).
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42095&comment_hash=cd0ae770e8f4356fddf55379012cb30801915bd545d82a996f0ae48a2163d8e3&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42095&comment_hash=cd0ae770e8f4356fddf55379012cb30801915bd545d82a996f0ae48a2163d8e3&reaction=dislike'>👎</a>



##########
superset/models/helpers.py:
##########
@@ -749,6 +749,109 @@ def modified(self) -> Markup:
 _NO_BYPASS: frozenset[type] = frozenset()
 
 
+def _make_metric_line_comments_safe(  # noqa: C901
+    expression: str,
+) -> tuple[str, bool]:
+    """Convert SQL line comments without regenerating otherwise valid SQL."""
+    result: list[str] = []
+    index = 0
+    quote: str | None = None
+    dollar_quote: str | None = None
+    backslash_escapes = False
+    in_block_comment = False
+    while index < len(expression):
+        if in_block_comment:
+            end = expression.find("*/", index)
+            if end < 0:
+                return expression, False
+            result.append(expression[index : end + 2])
+            index = end + 2
+            in_block_comment = False
+            continue
+        if dollar_quote:
+            end = expression.find(dollar_quote, index)
+            if end < 0:
+                return expression, False
+            result.append(expression[index : end + len(dollar_quote)])
+            index = end + len(dollar_quote)
+            dollar_quote = None
+            continue
+        if quote:
+            character = expression[index]
+            result.append(character)
+            index += 1
+            if backslash_escapes and character == "\\" and index < 
len(expression):
+                result.append(expression[index])
+                index += 1
+            elif character == quote:
+                if index < len(expression) and expression[index] == quote:
+                    result.append(expression[index])
+                    index += 1
+                else:
+                    quote = None
+                    backslash_escapes = False
+            continue
+
+        if expression.startswith("/*", index):
+            in_block_comment = True
+            continue
+        if expression.startswith("--", index):
+            line_end = len(expression)
+            for separator in ("\n", "\r"):
+                separator_index = expression.find(separator, index + 2)
+                if separator_index >= 0:
+                    line_end = min(line_end, separator_index)
+            contents = expression[index + 2 : line_end]
+            if "*/" in contents:
+                return expression, False
+            result.append(f"/*{contents} */")
+            index = line_end
+            continue
+        if expression[index] in {"'", '"'}:
+            quote = expression[index]
+            backslash_escapes = (
+                quote == "'"
+                and index > 0
+                and expression[index - 1] in {"E", "e"}
+                and (index == 1 or not expression[index - 2].isalnum())
+            )
+            result.append(quote)
+            index += 1
+            continue
+        if expression[index] == "$":
+            match = re.match(r"\$[A-Za-z_][A-Za-z0-9_]*\$|\$\$", 
expression[index:])
+            if match:
+                dollar_quote = match.group(0)
+                result.append(dollar_quote)
+                index += len(dollar_quote)
+                continue
+        result.append(expression[index])
+        index += 1
+    if quote or dollar_quote or in_block_comment:
+        return expression, False
+    return "".join(result), True
+
+
+def _normalize_custom_sql_metric(
+    expression: str,
+    engine: str,
+    normalizer: Callable[[str], str],
+) -> tuple[str, bool]:
+    """Normalize a metric and report whether its source is safe to preserve."""
+    expression = normalizer(expression)
+    if engine not in {"postgresql", "redshift"}:
+        return expression, False
+
+    expression, source_is_safe = _make_metric_line_comments_safe(expression)
+    if source_is_safe:
+        return expression, True
+
+    try:
+        return sanitize_clause(expression, engine), False
+    except QueryClauseValidationException:
+        return expression, False

Review Comment:
   **Suggestion:** This fallback swallows clause-validation failures and 
returns the original expression, which can propagate unnormalized/unsafe SQL to 
callers that reuse this helper without re-validating (for example saved-metric 
compilation paths). Instead of silently returning raw SQL here, propagate a 
validation error or return a sanitized-safe fallback so all call sites get 
consistent safety guarantees. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Saved Postgres metrics can bypass clause validation and sanitization.
   - ⚠️ Invalid or unsafe metric SQL reaches query construction unchanged.
   - ⚠️ Inconsistent behavior versus adhoc metric validation path.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Create a PostgreSQL or Redshift Database (for example with 
"postgresql://" as in
   "superset/tests/integration_tests/sqla_models_tests.py:80-15") and attach a 
SqlaTable and
   SqlMetric, so that SqlMetric.get_sqla_col() in
   "superset/connectors/sqla/models.py:1230-25" is used to compile the saved 
metric.
   
   2. Define the saved metric.expression as a clause that is invalid for the 
SQL parser, such
   as a multi-statement or otherwise malformed fragment, so that 
`sanitize_clause(expression,
   engine)` in "superset/sql/parse.py:2124-39" would raise 
QueryClauseValidationException
   when invoked.
   
   3. Call `metric.get_sqla_col()`; its implementation at
   "superset/connectors/sqla/models.py:1230-25" optionally applies a Jinja 
template processor
   and then calls `_normalize_custom_sql_metric(expression, 
self.table.database.backend,
   self.table.database.db_engine_spec.normalize_custom_sql_metric)` before 
wrapping the
   result in `literal_column(expression)`.
   
   4. Inside `_normalize_custom_sql_metric()` at 
"superset/models/helpers.py:141-153", after
   normalizing DATE_TRUNC units, the block at lines 849-852 (`try: return
   sanitize_clause(expression, engine), False except 
QueryClauseValidationException: return
   expression, False`) catches any clause-validation failure from 
`sanitize_clause` and
   silently returns the original, unvalidated expression; since 
SqlMetric.get_sqla_col()
   ignores the boolean flag and performs no further validation, the raw invalid 
or
   potentially unsafe SQL is propagated into query construction, whereas adhoc 
metric paths
   using `_process_sql_expression()` re-run `sanitize_clause` and raise
   QueryObjectValidationError, creating inconsistent safety guarantees across 
call sites.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=85fada5cabc84f5a93fc3e7da45dfab8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=85fada5cabc84f5a93fc3e7da45dfab8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/models/helpers.py
   **Line:** 849:852
   **Comment:**
        *Security: This fallback swallows clause-validation failures and 
returns the original expression, which can propagate unnormalized/unsafe SQL to 
callers that reuse this helper without re-validating (for example saved-metric 
compilation paths). Instead of silently returning raw SQL here, propagate a 
validation error or return a sanitized-safe fallback so all call sites get 
consistent safety guarantees.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42095&comment_hash=b7b2919ec793d6d5b1d8b21b387f7f4b1b93d77d8052cb67fa8fa944d82ed5c9&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42095&comment_hash=b7b2919ec793d6d5b1d8b21b387f7f4b1b93d77d8052cb67fa8fa944d82ed5c9&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to