From 4b05eefee6bea50ffd251c3e5e9dcc22e1d6c1b8 Mon Sep 17 00:00:00 2001 From: Ayush Saxena Date: Fri, 12 Jun 2026 14:01:20 +0530 Subject: [PATCH 1/2] TEZ-4730: Improve Tez UI --- tez-ui/src/main/webapp/app/models/vertex.js | 3 +- .../main/webapp/app/serializers/timeline.js | 3 ++ .../webapp/tests/unit/models/vertex-test.js | 51 +++++++++++++++++++ .../tests/unit/serializers/timeline-test.js | 39 ++++++++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/tez-ui/src/main/webapp/app/models/vertex.js b/tez-ui/src/main/webapp/app/models/vertex.js index a5904ef717..7926f2e41e 100644 --- a/tez-ui/src/main/webapp/app/models/vertex.js +++ b/tez-ui/src/main/webapp/app/models/vertex.js @@ -151,7 +151,8 @@ export default AMTimelineModel.extend({ description: Ember.computed("dag.vertices", "name", function () { try { let vertex = this.get("dag.vertices").findBy("vertexName", this.get("name")); - return JSON.parse(vertex.userPayloadAsText).desc; + let desc = JSON.parse(vertex.userPayloadAsText).desc; + return desc ? Ember.Handlebars.Utils.escapeExpression(desc) : undefined; }catch(e) {} }), diff --git a/tez-ui/src/main/webapp/app/serializers/timeline.js b/tez-ui/src/main/webapp/app/serializers/timeline.js index 52bba51be4..ecd43421a4 100644 --- a/tez-ui/src/main/webapp/app/serializers/timeline.js +++ b/tez-ui/src/main/webapp/app/serializers/timeline.js @@ -23,6 +23,9 @@ import LoaderSerializer from './loader'; function getDiagnostics(source) { var diagnostics = Ember.get(source, 'otherInfo.diagnostics') || ""; + // Escape HTML entities before adding formatting markup + diagnostics = Ember.Handlebars.Utils.escapeExpression(diagnostics); + diagnostics = diagnostics.replace(/\t/g, "  "); diagnostics = diagnostics.replace(/\[/g, "
» "); diagnostics = diagnostics.replace(/\]/g, "
"); diff --git a/tez-ui/src/main/webapp/tests/unit/models/vertex-test.js b/tez-ui/src/main/webapp/tests/unit/models/vertex-test.js index c5c6cd29fa..1cfe02adfb 100644 --- a/tez-ui/src/main/webapp/tests/unit/models/vertex-test.js +++ b/tez-ui/src/main/webapp/tests/unit/models/vertex-test.js @@ -96,3 +96,54 @@ test('description test', function(assert) { assert.equal(model.get("description"), testDesc); }); }); + +test('description escapes HTML to prevent XSS', function(assert) { + let testVertexName = "TestVertexName", + model = this.subject({ + name: testVertexName + }); + + Ember.run(function () { + // Script injection must be escaped + model.set("dag", Ember.Object.create({ + vertices: [{ + vertexName: testVertexName, + userPayloadAsText: JSON.stringify({ + desc: "" + }) + }] + })); + assert.equal(model.get("description"), "<script>alert(1)</script>"); + + // IMG onerror injection must be escaped + model.set("dag", Ember.Object.create({ + vertices: [{ + vertexName: testVertexName, + userPayloadAsText: JSON.stringify({ + desc: "" + }) + }] + })); + assert.equal(model.get("description"), "<img src=x onerror=alert(1)>"); + + // Ampersands are escaped + model.set("dag", Ember.Object.create({ + vertices: [{ + vertexName: testVertexName, + userPayloadAsText: JSON.stringify({ + desc: "a & b" + }) + }] + })); + assert.equal(model.get("description"), "a & b"); + + // Null/undefined desc returns undefined + model.set("dag", Ember.Object.create({ + vertices: [{ + vertexName: testVertexName, + userPayloadAsText: JSON.stringify({}) + }] + })); + assert.equal(model.get("description"), undefined); + }); +}); diff --git a/tez-ui/src/main/webapp/tests/unit/serializers/timeline-test.js b/tez-ui/src/main/webapp/tests/unit/serializers/timeline-test.js index 1aaabac2fb..f0df472b68 100644 --- a/tez-ui/src/main/webapp/tests/unit/serializers/timeline-test.js +++ b/tez-ui/src/main/webapp/tests/unit/serializers/timeline-test.js @@ -42,3 +42,42 @@ test('extractArrayPayload test', function(assert) { assert.equal(serializer.extractArrayPayload(testPayload), testPayload.entities); }); + +test('getDiagnostics escapes HTML', function(assert) { + let serializer = this.subject(), + mapper = serializer.maps.diagnostics; + + // Empty/missing diagnostics + assert.equal(mapper({}), ""); + assert.equal(mapper({otherinfo: {}}), ""); + + // Script injection must be escaped + assert.equal( + mapper({otherinfo: {diagnostics: ""}}), + "<script>alert(1)</script>" + ); + + // IMG onerror injection must be escaped + assert.equal( + mapper({otherinfo: {diagnostics: ""}}), + "<img src=x onerror=alert(1)>" + ); + + // Tab formatting still works after escaping + assert.equal( + mapper({otherinfo: {diagnostics: "\tindented"}}), + "  indented" + ); + + // Bracket formatting still works after escaping + assert.equal( + mapper({otherinfo: {diagnostics: "[section]"}}), + "
» section
" + ); + + // Ampersands in diagnostics are escaped + assert.equal( + mapper({otherinfo: {diagnostics: "a & b"}}), + "a & b" + ); +}); From 912a7a9c5f928f3c45efa70592b28401542e4ce9 Mon Sep 17 00:00:00 2001 From: Ayush Saxena Date: Thu, 25 Jun 2026 19:45:15 +0530 Subject: [PATCH 2/2] Make Camel Casing in Test due to Hadoop Upgrade --- .../webapp/tests/unit/serializers/timeline-test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tez-ui/src/main/webapp/tests/unit/serializers/timeline-test.js b/tez-ui/src/main/webapp/tests/unit/serializers/timeline-test.js index f0df472b68..8a0b145924 100644 --- a/tez-ui/src/main/webapp/tests/unit/serializers/timeline-test.js +++ b/tez-ui/src/main/webapp/tests/unit/serializers/timeline-test.js @@ -49,35 +49,35 @@ test('getDiagnostics escapes HTML', function(assert) { // Empty/missing diagnostics assert.equal(mapper({}), ""); - assert.equal(mapper({otherinfo: {}}), ""); + assert.equal(mapper({otherInfo: {}}), ""); // Script injection must be escaped assert.equal( - mapper({otherinfo: {diagnostics: ""}}), + mapper({otherInfo: {diagnostics: ""}}), "<script>alert(1)</script>" ); // IMG onerror injection must be escaped assert.equal( - mapper({otherinfo: {diagnostics: ""}}), + mapper({otherInfo: {diagnostics: ""}}), "<img src=x onerror=alert(1)>" ); // Tab formatting still works after escaping assert.equal( - mapper({otherinfo: {diagnostics: "\tindented"}}), + mapper({otherInfo: {diagnostics: "\tindented"}}), "  indented" ); // Bracket formatting still works after escaping assert.equal( - mapper({otherinfo: {diagnostics: "[section]"}}), + mapper({otherInfo: {diagnostics: "[section]"}}), "
» section
" ); // Ampersands in diagnostics are escaped assert.equal( - mapper({otherinfo: {diagnostics: "a & b"}}), + mapper({otherInfo: {diagnostics: "a & b"}}), "a & b" ); });