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 bbf0fa5f42f fix(core): stop discarding API errors that quote an HTML
tag (#42489)
bbf0fa5f42f is described below
commit bbf0fa5f42fbb3e3a96f3d82074b236f0700ef00
Author: Gaurav Dubey <[email protected]>
AuthorDate: Thu Oct 1 05:33:04 2026 +0530
fix(core): stop discarding API errors that quote an HTML tag (#42489)
Co-authored-by: Evan Rusackas <[email protected]>
Co-authored-by: Evan Rusackas <[email protected]>
Co-authored-by: Claude Sonnet 5 <[email protected]>
---
.../src/query/getClientErrorObject.ts | 29 +++++++++-
.../test/query/getClientErrorObject.test.ts | 66 ++++++++++++++++++++++
2 files changed, 94 insertions(+), 1 deletion(-)
diff --git
a/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts
b/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts
index eb96ab28d05..454fd4999c4 100644
---
a/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts
+++
b/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts
@@ -89,7 +89,34 @@ const ERROR_CODE_LOOKUP = {
};
export function checkForHtml(str: string): boolean {
- return !isJsonString(str) && isProbablyHTML(str);
+ // An HTML error page served by a proxy or gateway begins with markup, and
+ // has no message worth showing. A server-authored error that merely quotes
+ // a tag — a database syntax error echoing back the offending `<a>`, say —
+ // begins with prose, and is the whole point of the response. Only the
+ // former may be collapsed into a generic status message.
+ //
+ // A leading `<` alone isn't enough: a message that opens with a quoted,
+ // unclosed tag (`<a> is not valid syntax`) also starts with `<`, and
+ // isProbablyHTML()'s DOMParser auto-closes stray tags on parse, so it
+ // can't tell that case apart either. Requiring the leading tag to actually
+ // close somewhere in the string does: a real fragment like
+ // `<div>500: Internal Server Error</div>` closes what it opens, a quoted
+ // tag doesn't. A `<!doctype html>` preamble is exempt from this check
+ // since isProbablyHTML() already gates that case on its own.
+ if (isJsonString(str)) {
+ return false;
+ }
+ const trimmed = str.trimStart();
+ if (!trimmed.startsWith('<')) {
+ return false;
+ }
+ if (!/^<!doctype html>/i.test(trimmed)) {
+ const leadingTag = trimmed.match(/^<([a-z][a-z0-9]*)\b/i)?.[1];
+ if (!leadingTag || !new RegExp(`</${leadingTag}\\s*>`, 'i').test(str)) {
+ return false;
+ }
+ }
+ return isProbablyHTML(str);
}
export function parseStringResponse(str: string): string {
diff --git
a/superset-frontend/packages/superset-ui-core/test/query/getClientErrorObject.test.ts
b/superset-frontend/packages/superset-ui-core/test/query/getClientErrorObject.test.ts
index f763f9c6ff5..375b97f71ef 100644
---
a/superset-frontend/packages/superset-ui-core/test/query/getClientErrorObject.test.ts
+++
b/superset-frontend/packages/superset-ui-core/test/query/getClientErrorObject.test.ts
@@ -311,6 +311,72 @@ test('parseErrorJson with HTML message and status code',
() => {
});
});
+test('parseErrorJson keeps a server message that only quotes an HTML tag', ()
=> {
+ // A database driver echoes the offending fragment back in its syntax error.
+ // The message is prose, not an HTML error page, so it must survive intact
+ // instead of collapsing into the status-code message.
+ const message =
+ 'Error: HTTPDriver received ClickHouse error code 62. DB::Exception: ' +
+ "Syntax error: failed at position 37 ('<') (line 1, col 37): <a> AS " +
+ '`My column_b77020` FROM (select number from numbers(10)) AS ' +
+ '`virtual_table` LIMIT 1000 FORMAT Native.';
+
+ expect(parseErrorJson({ status: 400, message })).toEqual({
+ status: 400,
+ message,
+ error: message,
+ });
+
+ // An actual error page still collapses, even when the server pads it with
+ // leading whitespace.
+ const page = '\n <!doctype html><title>502 Bad Gateway</title>';
+ expect(parseErrorJson({ status: 502, message: page })).toEqual({
+ status: 502,
+ message: page,
+ error: 'Bad gateway',
+ });
+});
+
+test('parseErrorJson keeps a server message that opens with an unclosed HTML
tag', () => {
+ // The tag itself is never closed anywhere in the string, unlike a real
+ // fragment such as `<div>...</div>`, so this isn't an HTML error page —
+ // just a message that happens to start by quoting the offending markup.
+ const message = '<a> is not valid syntax';
+
+ expect(parseErrorJson({ status: 400, message })).toEqual({
+ status: 400,
+ message,
+ error: message,
+ });
+});
+
+test('parseErrorJson keeps a message that starts with < but is not a tag', ()
=> {
+ // No leading tag name to match at all, so this never reaches the
+ // closing-tag check; it must still be treated as plain prose.
+ const message = '<3 is not an HTML tag';
+
+ expect(parseErrorJson({ status: 400, message })).toEqual({
+ status: 400,
+ message,
+ error: message,
+ });
+});
+
+test('parseErrorJson keeps a message that itself parses as JSON', () => {
+ // isJsonString() short-circuits checkForHtml before the tag checks ever
+ // run: a message that happens to parse as JSON on its own is never
+ // treated as an HTML error page, whatever text it contains — otherwise
+ // isProbablyHTML()'s full-string scan would flag the embedded tag below
+ // and incorrectly collapse this into a generic status message.
+ const message = '{"detail": "<div>not actually markup</div>"}';
+
+ expect(parseErrorJson({ status: 400, message })).toEqual({
+ status: 400,
+ message,
+ error: message,
+ });
+});
+
test('parseErrorJson with stacktrace', () => {
expect(
parseErrorJson({ error: 'error message', stack: 'stacktrace' }),