From 10ae33f5e52a8ab329a3158ba678358903ab39a5 Mon Sep 17 00:00:00 2001 From: Holden Karau Date: Mon, 31 Aug 2026 20:53:59 +0000 Subject: [PATCH 1/2] [MINOR][UI] Better handling for custom log names and URLs Custom log names and URLs can contain markup characters, so escape them when rendering the logs column and quote the href attribute. Only http(s) and relative URLs are rendered as links; anything else is shown as plain text. Co-authored-by: Cursor --- .../org/apache/spark/ui/static/utils.js | 17 +++++++++++++- ui-test/tests/utils.test.js | 22 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/core/src/main/resources/org/apache/spark/ui/static/utils.js b/core/src/main/resources/org/apache/spark/ui/static/utils.js index 3f0885b9c4f58..f637d78814084 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/utils.js +++ b/core/src/main/resources/org/apache/spark/ui/static/utils.js @@ -88,12 +88,27 @@ function getTimeZone() { } } +/* Custom log URLs are rendered as links only when they are http(s) or relative + * URLs; anything else is shown as plain text. Browsers strip ASCII whitespace + * and control characters when parsing URLs, so remove them before checking + * the scheme. */ +function isHttpOrRelativeUrl(url) { + if (typeof url !== 'string') return false; + var normalized = url.replace(/[\u0000-\u0020]/g, '').toLowerCase(); + return /^https?:\/\//.test(normalized) || !/^[a-z][a-z0-9+.-]*:/.test(normalized); +} + function formatLogsCells(execLogs, type) { if (type !== 'display') return Object.keys(execLogs); if (!execLogs) return; var result = ''; $.each(execLogs, function (logName, logUrl) { - result += '
' + logName + '
' + // Custom log names and URLs can contain markup characters. + if (isHttpOrRelativeUrl(logUrl)) { + result += '
' + escapeHtml(logName) + '
' + } else { + result += '
' + escapeHtml(logName) + '
' + } }); return result; } diff --git a/ui-test/tests/utils.test.js b/ui-test/tests/utils.test.js index b49f5034ff72a..a2774475bf4f5 100644 --- a/ui-test/tests/utils.test.js +++ b/ui-test/tests/utils.test.js @@ -16,6 +16,7 @@ */ +import '../../core/src/main/resources/org/apache/spark/ui/static/jquery.min.js'; import * as utils from '../../core/src/main/resources/org/apache/spark/ui/static/utils.js'; test('ConvertDurationString', function () { @@ -88,3 +89,24 @@ test('errorMessageCell escapes HTML in error messages', function () { expect(multiLine).not.toContain(payload); expect(multiLine).toContain('<img src=x onerror=alert(document.domain)>'); }); + +test('formatLogsCells escapes custom log names and URLs', function () { + // Names and URLs are escaped, and the href attribute is quoted. + const rendered = utils.formatLogsCells( + {'stdout': 'http://worker:8081/log?a=1&b="2"'}, 'display'); + expect(rendered).toBe( + '
<b>stdout</b>
'); + + // Only http(s) and relative URLs become links; other schemes render as text. + ['javascript:alert(1)', 'JAVAS\tCRIPT:alert(1)', 'data:text/html,x'].forEach(function (url) { + expect(utils.formatLogsCells({'stdout': url}, 'display')).toBe('
stdout
'); + }); + + expect(utils.formatLogsCells({'stderr': 'logPage/?self&logType=stderr'}, 'display')) + .toBe('
stderr
'); + expect(utils.formatLogsCells({'stdout': 'https://worker:8081/logPage'}, 'display')) + .toBe('
stdout
'); + + // Non-display rendering returns the log names for sorting and filtering. + expect(utils.formatLogsCells({'stdout': 'javascript:alert(1)'}, 'sort')).toEqual(['stdout']); +}); From 912760b631f34bd3a314d5119fdf49224e3a8cdb Mon Sep 17 00:00:00 2001 From: Holden Karau Date: Tue, 1 Sep 2026 18:07:39 +0000 Subject: [PATCH 2/2] [MINOR][UI][FOLLOWUP] Tell eslint the URL-stripping character class is intentional The class covers the ASCII range browsers strip when parsing URLs, so no-control-regex does not apply here. Co-Authored-By: Claude Opus 5 (1M context) --- core/src/main/resources/org/apache/spark/ui/static/utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/resources/org/apache/spark/ui/static/utils.js b/core/src/main/resources/org/apache/spark/ui/static/utils.js index f637d78814084..bc3143fa4efec 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/utils.js +++ b/core/src/main/resources/org/apache/spark/ui/static/utils.js @@ -94,6 +94,7 @@ function getTimeZone() { * the scheme. */ function isHttpOrRelativeUrl(url) { if (typeof url !== 'string') return false; + /* eslint-disable-next-line no-control-regex */ var normalized = url.replace(/[\u0000-\u0020]/g, '').toLowerCase(); return /^https?:\/\//.test(normalized) || !/^[a-z][a-z0-9+.-]*:/.test(normalized); }