You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/06/01 23:52:06 UTC

(superset) branch fix/chart-schema-validation-hardening created (now 162d3753e73)

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

rusackas pushed a change to branch fix/chart-schema-validation-hardening
in repository https://gitbox.apache.org/repos/asf/superset.git


      at 162d3753e73 fix(charts): tighten chart schema input validation

This branch includes the following new commits:

     new 162d3753e73 fix(charts): tighten chart schema input validation

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(superset) 01/01: fix(charts): tighten chart schema input validation

Posted by ru...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch fix/chart-schema-validation-hardening
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 162d3753e73331728eb603b62578d16ed8a55559
Author: Claude Code <no...@anthropic.com>
AuthorDate: Mon Jun 1 16:51:47 2026 -0700

    fix(charts): tighten chart schema input validation
    
    Three small validation gaps in superset/charts/schemas.py:
    
    - ChartPutSchema.query_context lacked the validate=utils.validate_json validator
      that ChartPostSchema.query_context already has, allowing invalid JSON to be
      stored on chart update and fail later at render time. Added it (allow_none and
      empty values remain valid, matching POST).
    - ChartDataProphetOptionsSchema.periods documented "min: 0" but enforced no
      bound. Added Range(min=0, max=10000) to prevent unbounded forecast horizons.
    - ChartDataRollingOptionsSchema.window had no bound. Added Range(min=1,
      max=10000), consistent with the downstream window > 0 requirement.
    
    These bound user-supplied values that flow into Prophet/rolling computations,
    reducing a resource-exhaustion surface. Added unit tests for each.
    
    Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
 superset/charts/schemas.py              | 18 +++++++++-
 tests/unit_tests/charts/test_schemas.py | 62 +++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 1 deletion(-)

diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py
index e0cee7758c4..e333f507af1 100644
--- a/superset/charts/schemas.py
+++ b/superset/charts/schemas.py
@@ -274,7 +274,9 @@ class ChartPutSchema(Schema):
         validate=utils.validate_json,
     )
     query_context = fields.String(
-        metadata={"description": query_context_description}, allow_none=True
+        metadata={"description": query_context_description},
+        allow_none=True,
+        validate=utils.validate_json,
     )
     query_context_generation = fields.Boolean(
         metadata={"description": query_context_generation_description}, allow_none=True
@@ -518,6 +520,13 @@ class ChartDataRollingOptionsSchema(ChartDataPostProcessingOperationOptionsSchem
     window = fields.Integer(
         metadata={"description": "Size of the rolling window in days.", "example": 7},
         required=True,
+        validate=[
+            Range(
+                min=1,
+                max=10000,
+                error=_("`window` must be between 1 and 10000"),
+            )
+        ],
     )
     rolling_type_options = fields.Dict(
         metadata={
@@ -659,6 +668,13 @@ class ChartDataProphetOptionsSchema(ChartDataPostProcessingOperationOptionsSchem
             "min": 0,
         },
         required=True,
+        validate=[
+            Range(
+                min=0,
+                max=10000,
+                error=_("`periods` must be between 0 and 10000"),
+            )
+        ],
     )
     confidence_interval = fields.Float(
         metadata={
diff --git a/tests/unit_tests/charts/test_schemas.py b/tests/unit_tests/charts/test_schemas.py
index 5466a0deadd..25d11392a65 100644
--- a/tests/unit_tests/charts/test_schemas.py
+++ b/tests/unit_tests/charts/test_schemas.py
@@ -22,6 +22,8 @@ from marshmallow import ValidationError
 from superset.charts.schemas import (
     ChartDataProphetOptionsSchema,
     ChartDataQueryObjectSchema,
+    ChartDataRollingOptionsSchema,
+    ChartPutSchema,
     get_time_grain_choices,
 )
 
@@ -91,6 +93,66 @@ def test_chart_data_prophet_options_schema_time_grain_validation(
     assert "time_grain" in exc_info.value.messages
 
 
+def test_chart_put_schema_query_context_json_validation(
+    app_context: None,
+) -> None:
+    """ChartPutSchema.query_context must reject invalid JSON (parity with POST)."""
+    schema = ChartPutSchema()
+
+    # Valid JSON passes
+    assert schema.load({"query_context": '{"a": 1}'})["query_context"] == '{"a": 1}'
+
+    # None is allowed (allow_none)
+    assert schema.load({"query_context": None})["query_context"] is None
+
+    # Invalid JSON is rejected
+    with pytest.raises(ValidationError) as exc_info:
+        schema.load({"query_context": "{not valid json"})
+    assert "query_context" in exc_info.value.messages
+
+
+def test_chart_data_prophet_options_schema_periods_range(
+    app_context: None,
+) -> None:
+    """`periods` must be a bounded non-negative integer."""
+    schema = ChartDataProphetOptionsSchema()
+    base = {"time_grain": "P1D", "confidence_interval": 0.8}
+
+    # Valid value passes
+    assert schema.load({**base, "periods": 7})["periods"] == 7
+
+    # Negative value rejected
+    with pytest.raises(ValidationError) as exc_info:
+        schema.load({**base, "periods": -1})
+    assert "periods" in exc_info.value.messages
+
+    # Excessively large value rejected (resource-exhaustion guard)
+    with pytest.raises(ValidationError) as exc_info:
+        schema.load({**base, "periods": 1_000_000})
+    assert "periods" in exc_info.value.messages
+
+
+def test_chart_data_rolling_options_schema_window_range(
+    app_context: None,
+) -> None:
+    """`window` must be a bounded positive integer."""
+    schema = ChartDataRollingOptionsSchema()
+    base = {"rolling_type": "mean"}
+
+    # Valid value passes
+    assert schema.load({**base, "window": 7})["window"] == 7
+
+    # Zero window rejected (rolling requires window > 0)
+    with pytest.raises(ValidationError) as exc_info:
+        schema.load({**base, "window": 0})
+    assert "window" in exc_info.value.messages
+
+    # Excessively large window rejected
+    with pytest.raises(ValidationError) as exc_info:
+        schema.load({**base, "window": 1_000_000})
+    assert "window" in exc_info.value.messages
+
+
 def test_chart_data_query_object_schema_time_grain_sqla_validation(
     app_context: None,
 ) -> None: