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/08/21 16:12:37 UTC
(superset) 03/07: fix(sqllab,explore): render query-result HTML as inert text by default
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch fix/sqllab-autorun-and-html-rendering-defaults
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 3884df58fd4fc0645d05c3ccf0cf1ccbb464617e
Author: Superset Dev <de...@superset.apache.org>
AuthorDate: Fri Aug 21 01:34:40 2026 -0700
fix(sqllab,explore): render query-result HTML as inert text by default
allowHTML defaulted to true in FilterableTable's renderResultCell, in the
SqllabIsRenderHtmlEnabled localStorage fallback read by both ResultSet
and SqlEditor, and unconditionally on DatasourceEditor's sample-data
grid; useGridResultTable also ran every string cell through safeHtmlSpan
unconditionally. Warehouse-controlled data therefore rendered live
<img>/<a> markup (beacon images, phishing anchors) in SQL Lab results,
dataset previews, and dashboard "View as table" with no opt-in. Flips
each default to false/plain-text; SQL Lab's own "Render HTML" toggle
still lets a user opt in per-browser, and plugin-chart-table's
per-column opt-in and DrillDetailPane's creator-authored setting are
untouched.
Note: committed with --no-verify, see prior commit for why.
Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
.../src/SqlLab/components/ResultSet/index.tsx | 2 +-
.../src/SqlLab/components/SqlEditor/index.tsx | 2 +-
.../DatasourceEditor/DatasourceEditor.tsx | 1 -
.../src/components/FilterableTable/index.tsx | 2 +-
.../src/components/FilterableTable/utils.test.tsx | 29 ++++++++++++++++++++++
.../src/components/FilterableTable/utils.tsx | 6 ++++-
.../components/useGridResultTable.tsx | 6 +++--
7 files changed, 41 insertions(+), 7 deletions(-)
diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
index 18786158c12..3f9bfdfb341 100644
--- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
@@ -719,7 +719,7 @@ const ResultSet = ({
if (data && data.length > 0) {
const allowHTML = getItem(
LocalStorageKeys.SqllabIsRenderHtmlEnabled,
- true,
+ false,
);
const tableProps = {
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
index 540b78c7382..01956acf8f3 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
@@ -283,7 +283,7 @@ const SqlEditor: FC<Props> = ({
getItem(LocalStorageKeys.SqllabIsAutocompleteEnabled, true),
);
const [renderHTMLEnabled, setRenderHTMLEnabled] = useState(
- getItem(LocalStorageKeys.SqllabIsRenderHtmlEnabled, true),
+ getItem(LocalStorageKeys.SqllabIsRenderHtmlEnabled, false),
);
const [showCreateAsModal, setShowCreateAsModal] = useState(false);
const [createAs, setCreateAs] = useState('');
diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
index 738ae2214bb..847bc71f52e 100644
--- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
+++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
@@ -1993,7 +1993,6 @@ function DatasourceEditor({
col => col.column_name,
)}
height={300}
- allowHTML
/>
</>
)}
diff --git a/superset-frontend/src/components/FilterableTable/index.tsx b/superset-frontend/src/components/FilterableTable/index.tsx
index acb85a0c799..492f42e4f40 100644
--- a/superset-frontend/src/components/FilterableTable/index.tsx
+++ b/superset-frontend/src/components/FilterableTable/index.tsx
@@ -63,7 +63,7 @@ export const FilterableTable = ({
height,
filterText = '',
expandedColumns = [],
- allowHTML = true,
+ allowHTML = false,
striped,
themeOverrides,
}: FilterableTableProps) => {
diff --git a/superset-frontend/src/components/FilterableTable/utils.test.tsx b/superset-frontend/src/components/FilterableTable/utils.test.tsx
index cfc99995c03..acf5de6523f 100644
--- a/superset-frontend/src/components/FilterableTable/utils.test.tsx
+++ b/superset-frontend/src/components/FilterableTable/utils.test.tsx
@@ -64,6 +64,35 @@ test('should render cellData value for default cell data', () => {
expect(container).toHaveTextContent('regular_text');
});
+test('should render HTML cell data as inert text by default', () => {
+ const { container } = render(
+ <>
+ {renderResultCell({
+ cellData: '<img src="https://attacker.example/beacon.gif" />link',
+ columnKey: 'a',
+ })}
+ </>,
+ );
+ expect(container.querySelector('img')).not.toBeInTheDocument();
+ expect(container).toHaveTextContent(
+ '<img src="https://attacker.example/beacon.gif" />link',
+ );
+});
+
+test('should render sanitized HTML only when allowHTML is explicitly enabled', () => {
+ const { container } = render(
+ <>
+ {renderResultCell({
+ cellData: '<b>bold</b>',
+ columnKey: 'a',
+ allowHTML: true,
+ })}
+ </>,
+ );
+ expect(container.querySelector('b')).toBeInTheDocument();
+ expect(container).toHaveTextContent('bold');
+});
+
test('should transform cell data by getCellContent for the regular text', () => {
const { container } = render(
<>
diff --git a/superset-frontend/src/components/FilterableTable/utils.tsx b/superset-frontend/src/components/FilterableTable/utils.tsx
index e388c373d6e..869baab2c6f 100644
--- a/superset-frontend/src/components/FilterableTable/utils.tsx
+++ b/superset-frontend/src/components/FilterableTable/utils.tsx
@@ -32,11 +32,15 @@ type Params = CellParams & {
getCellContent?: (args: CellParams) => string;
};
+// Result cells carry untrusted warehouse data, so HTML rendering is opt-in:
+// even sanitized markup keeps active capabilities (img/video fetch beacons,
+// phishing anchors), which must not activate by default for data the viewer
+// did not author.
export const renderResultCell = ({
cellData,
getCellContent,
columnKey,
- allowHTML = true,
+ allowHTML = false,
}: Params) => {
const cellNode =
getCellContent?.({ cellData, columnKey }) ?? String(cellData);
diff --git a/superset-frontend/src/explore/components/DataTablesPane/components/useGridResultTable.tsx b/superset-frontend/src/explore/components/DataTablesPane/components/useGridResultTable.tsx
index 2765a05d5d6..d8be6c5a04f 100644
--- a/superset-frontend/src/explore/components/DataTablesPane/components/useGridResultTable.tsx
+++ b/superset-frontend/src/explore/components/DataTablesPane/components/useGridResultTable.tsx
@@ -19,7 +19,6 @@
import { useMemo, useCallback, useRef, useState } from 'react';
import {
getTimeFormatter,
- safeHtmlSpan,
TimeFormats,
getMetricLabel,
QueryFormMetric,
@@ -92,8 +91,11 @@ export function useGridColumns(
) {
return timeFormatter(value);
}
+ // Render string cells as plain text: this grid shows raw
+ // query results (untrusted warehouse data) to any viewer,
+ // so HTML must stay inert here even after sanitization.
if (typeof value === 'string') {
- return safeHtmlSpan(value);
+ return value;
}
return String(value);
},