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

ayushtkn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tez.git


The following commit(s) were added to refs/heads/master by this push:
     new 17d15490c TEZ-4730: Improve Tez UI (#513). (Ayush Saxena, reviewed by 
Laszlo Bodor)
17d15490c is described below

commit 17d15490cedab7d18a4d4c1f83374e30c58a363f
Author: Ayush Saxena <[email protected]>
AuthorDate: Fri Jun 26 04:00:33 2026 +0530

    TEZ-4730: Improve Tez UI (#513). (Ayush Saxena, reviewed by Laszlo Bodor)
---
 tez-ui/src/main/webapp/app/models/vertex.js        |  3 +-
 tez-ui/src/main/webapp/app/serializers/timeline.js |  3 ++
 .../main/webapp/tests/unit/models/vertex-test.js   | 51 ++++++++++++++++++++++
 .../webapp/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 a5904ef71..7926f2e41 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 52bba51be..ecd43421a 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, "&emsp;&emsp;");
   diagnostics = diagnostics.replace(/\[/g, "<div>&#187; ");
   diagnostics = diagnostics.replace(/\]/g, "</div>");
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 c5c6cd29f..1cfe02adf 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: "<script>alert(1)</script>"
+        })
+      }]
+    }));
+    assert.equal(model.get("description"), 
"&lt;script&gt;alert(1)&lt;/script&gt;");
+
+    // IMG onerror injection must be escaped
+    model.set("dag", Ember.Object.create({
+      vertices: [{
+        vertexName: testVertexName,
+        userPayloadAsText: JSON.stringify({
+          desc: "<img src=x onerror=alert(1)>"
+        })
+      }]
+    }));
+    assert.equal(model.get("description"), "&lt;img src=x 
onerror=alert(1)&gt;");
+
+    // Ampersands are escaped
+    model.set("dag", Ember.Object.create({
+      vertices: [{
+        vertexName: testVertexName,
+        userPayloadAsText: JSON.stringify({
+          desc: "a & b"
+        })
+      }]
+    }));
+    assert.equal(model.get("description"), "a &amp; 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 1aaabac2f..8a0b14592 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>"}}),
+    "&lt;script&gt;alert(1)&lt;/script&gt;"
+  );
+
+  // IMG onerror injection must be escaped
+  assert.equal(
+    mapper({otherInfo: {diagnostics: "<img src=x onerror=alert(1)>"}}),
+    "&lt;img src=x onerror=alert(1)&gt;"
+  );
+
+  // Tab formatting still works after escaping
+  assert.equal(
+    mapper({otherInfo: {diagnostics: "\tindented"}}),
+    "&emsp;&emsp;indented"
+  );
+
+  // Bracket formatting still works after escaping
+  assert.equal(
+    mapper({otherInfo: {diagnostics: "[section]"}}),
+    "<div>&#187; section</div>"
+  );
+
+  // Ampersands in diagnostics are escaped
+  assert.equal(
+    mapper({otherInfo: {diagnostics: "a & b"}}),
+    "a &amp; b"
+  );
+});

Reply via email to