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, "  ");
diagnostics = diagnostics.replace(/\[/g, "<div>» ");
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"),
"<script>alert(1)</script>");
+
+ // 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"), "<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 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>"}}),
+ "<script>alert(1)</script>"
+ );
+
+ // IMG onerror injection must be escaped
+ assert.equal(
+ mapper({otherInfo: {diagnostics: "<img src=x onerror=alert(1)>"}}),
+ "<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]"}}),
+ "<div>» section</div>"
+ );
+
+ // Ampersands in diagnostics are escaped
+ assert.equal(
+ mapper({otherInfo: {diagnostics: "a & b"}}),
+ "a & b"
+ );
+});