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

beto pushed a commit to branch url-redirect
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 323248e1d7410386d686f2cdd381e37b68ba0e13
Author: Beto Dealmeida <robe...@dealmeida.net>
AuthorDate: Thu Sep 4 11:39:41 2025 -0400

    More improvements
---
 docs/docs/configuration/alerts-reports.mdx         | 37 ++++++++++++++++++++++
 superset/config.py                                 |  4 +++
 superset/views/redirect.py                         |  5 +++
 .../integration_tests/views/test_redirect_view.py  | 13 ++++++++
 tests/unit_tests/utils/test_link_redirect.py       | 26 +++++++++++++++
 5 files changed, 85 insertions(+)

diff --git a/docs/docs/configuration/alerts-reports.mdx 
b/docs/docs/configuration/alerts-reports.mdx
index 5f4f028957..97847b1c87 100644
--- a/docs/docs/configuration/alerts-reports.mdx
+++ b/docs/docs/configuration/alerts-reports.mdx
@@ -217,6 +217,43 @@ def alert_dynamic_minimal_interval(**kwargs) -> int:
 ALERT_MINIMUM_INTERVAL = alert_dynamic_minimal_interval
 ```
 
+## Security Configuration
+
+### External Link Redirection
+
+For security purposes, Superset automatically processes HTML content in alert 
and report emails to redirect external links through a warning page. This helps 
protect users from potentially malicious links.
+
+#### Configuration Options
+
+```python
+# Enable/disable external link redirection (default: True)
+ALERT_REPORTS_ENABLE_LINK_REDIRECT = True
+
+# Show visual indicators for external links in emails (default: True)
+ALERT_REPORTS_EXTERNAL_LINK_INDICATOR = True
+```
+
+#### How it Works
+
+1. **HTML Processing**: When generating alert/report emails, Superset scans 
HTML content for external links
+2. **Link Replacement**: External links (those not pointing to your Superset 
instance) are replaced with redirect URLs
+3. **Warning Page**: Users who click external links see a warning page before 
being redirected
+4. **Internal Links**: Links pointing to your Superset instance are not 
modified
+
+#### Security Features
+
+- **Dangerous Scheme Blocking**: Automatically blocks `javascript:`, `data:`, 
`vbscript:`, and `file:` URLs
+- **Host Validation**: Uses your `WEBDRIVER_BASEURL_USER_FRIENDLY` or 
`WEBDRIVER_BASEURL` configuration to determine internal vs external links
+- **Logging**: All redirect attempts are logged for security monitoring
+
+#### Disabling the Feature
+
+To disable external link redirection entirely:
+
+```python
+ALERT_REPORTS_ENABLE_LINK_REDIRECT = False
+```
+
 ## Troubleshooting
 
 There are many reasons that reports might not be working.  Try these steps to 
check for specific issues.
diff --git a/superset/config.py b/superset/config.py
index b41992fe1e..613479ce12 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -1692,6 +1692,10 @@ ALERT_REPORTS_QUERY_EXECUTION_MAX_TRIES = 1
 # Custom width for screenshots
 ALERT_REPORTS_MIN_CUSTOM_SCREENSHOT_WIDTH = 600
 ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH = 2400
+# External link redirection in alert/report emails
+ALERT_REPORTS_ENABLE_LINK_REDIRECT = True
+# Show visual indicators for external links in emails
+ALERT_REPORTS_EXTERNAL_LINK_INDICATOR = True
 # Set a minimum interval threshold between executions (for each Alert/Report)
 # Value should be an integer i.e. int(timedelta(minutes=5).total_seconds())
 # You can also assign a function to the config that returns the expected 
integer
diff --git a/superset/views/redirect.py b/superset/views/redirect.py
index 35e63141f9..4404395aa6 100644
--- a/superset/views/redirect.py
+++ b/superset/views/redirect.py
@@ -22,6 +22,7 @@ from flask import abort, redirect, request
 from flask_appbuilder import expose
 from flask_appbuilder.security.decorators import has_access
 
+from superset import is_feature_enabled
 from superset.superset_typing import FlaskResponse
 from superset.utils.link_redirect import is_safe_redirect_url
 from superset.views.base import SupersetModelView
@@ -43,6 +44,10 @@ class RedirectView(SupersetModelView):
         """
         Show a warning page before redirecting to an external URL
         """
+        # Check if ALERT_REPORTS feature is enabled
+        if not is_feature_enabled("ALERT_REPORTS"):
+            abort(404, description="Feature not enabled")
+
         # Get the target URL from query parameters
         target_url = request.args.get("url", "")
 
diff --git a/tests/integration_tests/views/test_redirect_view.py 
b/tests/integration_tests/views/test_redirect_view.py
index b21ca8d559..73b49411c5 100644
--- a/tests/integration_tests/views/test_redirect_view.py
+++ b/tests/integration_tests/views/test_redirect_view.py
@@ -267,3 +267,16 @@ class TestRedirectView(SupersetTestCase):
 
             # Should return 400 for dangerous schemes regardless of case
             assert response.status_code == 400
+
+    @with_config({"ALERT_REPORTS": False})
+    def test_redirect_feature_flag_disabled(self):
+        """Test that redirect endpoint returns 404 when ALERT_REPORTS is 
disabled"""
+        self.login(username="admin")
+
+        external_url = "https://external.com";
+        encoded_url = quote(external_url, safe="")
+
+        response = self.client.get(f"/redirect/?url={encoded_url}")
+
+        # Should return 404 when feature is disabled
+        assert response.status_code == 404
diff --git a/tests/unit_tests/utils/test_link_redirect.py 
b/tests/unit_tests/utils/test_link_redirect.py
index 009d70baca..909fd95625 100644
--- a/tests/unit_tests/utils/test_link_redirect.py
+++ b/tests/unit_tests/utils/test_link_redirect.py
@@ -300,3 +300,29 @@ def test_process_html_links_invalid_base_url(app):
 
         # Should return unchanged HTML when base URL is invalid
         assert result == html
+
+
+def test_process_html_links_feature_disabled(app):
+    """Test behavior when feature is disabled"""
+    app.config["ALERT_REPORTS_ENABLE_LINK_REDIRECT"] = False
+
+    with app.app_context():
+        html = '<a href="https://external.com";>External</a>'
+        result = process_html_links(html)
+
+        # Should return unchanged HTML when feature is disabled
+        assert result == html
+
+
+def test_process_html_links_large_html(app):
+    """Test processing very large HTML content"""
+    with app.app_context():
+        # Create large HTML content
+        large_html = (
+            "<div>" + '<a href="https://external.com";>External</a>' * 1000 + 
"</div>"
+        )
+        result = process_html_links(large_html)
+
+        # Should still process correctly
+        assert "/redirect?" in result
+        assert result.count("/redirect?") == 1000  # All links should be 
processed

Reply via email to