CVE-2021-41132: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: OMERO.web provides a web based client and plugin infrastructure. In versions prior to 5.11.0, a variety of templates do not perform proper sanitization through HTML escaping. Due to the lack of sanitization and use of ``jQuery.html()``, there are a whole host of cross-site scripting possibilities with specially crafted input to a variety of fields. This issue is patched in version 5.11.0. There are no known workarounds aside from upgrading.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2021-10-14T16:15Z
Repository: https://github.com/ome/omero-web
Language: JavaScript
Files changed: CHANGELOG.md, omeroweb/webadmin/templates/webadmin/includes/drivespaceStats.js, omeroweb/webclient/static/webclient/javascript/ome.chgrp.js, omeroweb/webclient/static/webclient/javascript/ome.chown.js, omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js, omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js, omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js, omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js, omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js, omeroweb/webclient/static/webclient/javascript/ome.thumbnail_figure.js, omeroweb/webclient/templates/webclient/annotations/comments_underscore.html, omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html, omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html, omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html, omeroweb/webclient/templates/webclient/annotations/metadata_general.html, omeroweb/webclient/templates/webclient/annotations/tags_underscore.html, omeroweb/webclient/templates/webclient/data/icon_thumbnails_underscore.html, omeroweb/webclient/templates/webclient/data/includes/center_plugin.thumbs.js.html, omeroweb/webclient/templates/webclient/data/plate.html, omeroweb/webclient/templates/webclient/scripts/upload_script.html, omeroweb/webclient/views.py, omeroweb/webgateway/static/webgateway/js/ome.popup.js, omeroweb/webgateway/static/webgateway/js/ome.roidisplay.js, omeroweb/webgateway/static/webgateway/js/ome.spwgridview.js, omeroweb/webgateway/static/webgateway/js/omero_image.js, omeroweb/webgateway/templates/webgateway/viewport/omero_image.html
Commit date: 2021-10-07 11:17:38 +0100
Commit message: Fix issues with inconsistency in input sanitisation leading to XSS vectors

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0c9a6d46..9a5b599e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
 
+5.11.0 (October 2021)
+---------------------
+
+- Security vulnerability fixes for
+  [2021-SV3](https://www.openmicroscopy.org/security/advisories/2021-SV3)
+
 5.11.0.rc1 (September 2021)
 ---------------------------
 
 ## New features
 
diff --git a/omeroweb/webadmin/templates/webadmin/includes/drivespaceStats.js b/omeroweb/webadmin/templates/webadmin/includes/drivespaceStats.js
index 43f8dc0cd..c6fa25357 100644
--- a/omeroweb/webadmin/templates/webadmin/includes/drivespaceStats.js
+++ b/omeroweb/webadmin/templates/webadmin/includes/drivespaceStats.js
@@ -16,11 +16,11 @@
                 var tableRows = [],
                     rowData, rowId;
                 for(i=0; i<chart_data.length; i++) {
                     rowData = chart_data[i];
                     rowId = typeof rowData.userId != 'undefined' ? rowData.userId : rowData.groupId;
-                    tableRows.push('<tr><td class="link">' + rowData.label + '(id:'+ rowId +')</td>');
+                    tableRows.push('<tr><td class="link">' + rowData.label.escapeHTML() + '(id:'+ rowId +')</td>');
                     tableRows.push('<td class="link">' + rowData.data.filesizeformat() + '</td></tr>');
                 }
                 
                 $('#drivespaceTable tbody').html(tableRows.join(""));
                 $('#drivespaceTable').show();
@@ -48,10 +48,11 @@
                         chart_data = [],
                         MAX_SLICES = 10;
 
                     for(i=0; i<data.length; i++) {
                         var slice = data[i];
+                        slice.label = slice.label.escapeHTML();
                         if(i === MAX_SLICES){
                             chart_data.push({label:'Others', data:slice.data});
                         } else if (i > MAX_SLICES) {
                             chart_data[MAX_SLICES].data = chart_data[MAX_SLICES].data + slice.data;
                         } else {
diff --git a/omeroweb/webclient/static/webclient/javascript/ome.chgrp.js b/omeroweb/webclient/static/webclient/javascript/ome.chgrp.js
index 90cbf2285..3c37bc02a 100644
--- a/omeroweb/webclient/static/webclient/javascript/ome.chgrp.js
+++ b/omeroweb/webclient/static/webclient/javascript/ome.chgrp.js
@@ -60,21 +60,21 @@ $(function() {
         var url = webindex_url + "load_chgrp_groups/?" + OME.get_tree_selection();
         $.getJSON(url, function(data){
             data_owners = data.owners;  // save for later
             var ownernames = [];
             for (var o=0; o<data.owners.length; o++) {ownernames.push(data.owners[o][1]);}
-            var headerTxt = "<p>Move data owned by " + ownernames.join(", ") + ".</p>" +
+            var headerTxt = "<p>Move data owned by " + ownernames.join(", ").escapeHTML() + ".</p>" +
                             "<h1>Please choose target group below:</h1>";
             $group_chooser.append(headerTxt);
 
             // List the target groups...
             var html = "";
             for (var i=0; i<data.groups.length; i++){
                 var g = data.groups[i];
                 html += "<div class='chgrpGroup' data-gid='"+ g.id + "'>";
                 html += "<img src='" + permsIcon(g.perms) + "'/>";
-                html += g.name + "<hr></div>";
+                html += g.name.escapeHTML() + "<hr></div>";
             }
             // If no target groups found...
             if (data.groups.length === 0) {
                 html = "<hr><p>No target groups found</p><hr>";
                 if (data.owners.length === 1) {
@@ -405,11 +405,11 @@ $(function() {
             count = unlinked.length;
             if (count === 0) return '';
             if (count === 1) otype = otype.slice(0, -1);  // remove s
             var namesList = [], names;
             unlinked.forEach(function (u) {
-                namesList.push(u.name);
+                namesList.push(u.name.escapeHTML());
             });
             names = namesList.join(", ");
             names = " <i title='" + namesList.join("\n") + "'>(" + names.slice(0, 40) + (names.length > 40 ? "..." : "") + ")</i>";
             return count + " " + otype + names;
         }
diff --git a/omeroweb/webclient/static/webclient/javascript/ome.chown.js b/omeroweb/webclient/static/webclient/javascript/ome.chown.js
index 784f9e741..0d12918a8 100644
--- a/omeroweb/webclient/static/webclient/javascript/ome.chown.js
+++ b/omeroweb/webclient/static/webclient/javascript/ome.chown.js
@@ -35,11 +35,11 @@ $(function() {
 
     // template literals not supported on IE 11 (1.3% global browser share)
     var templateText = `
         <!-- Hidden fields for objects. e.g. name='Image' value='1,2,3' -->
         <% _.each(selobjs, function(obj, idx) { %>
-            <input name='<%= obj.split("=")[0] %>' value='<%= obj.split("=")[1] %>' hidden/>
+            <input name='<%- obj.split("=")[0] %>' value='<%- obj.split("=")[1] %>' hidden/>
         <% }) %>
 
         <!-- List target new owners -->
 
         <% if (loadingExps) { %>
@@ -48,11 +48,11 @@ $(function() {
             <h1>Please choose new owner for the selected data:</h1>
 
             <% _.each(exps, function(exp, idx) { %>
                 <label>
                     <input name='owner_id' type='radio' value='<%= exp['@id'] %>'/>
-                    <%= exp.FirstName%> <%= exp.LastName %> 
+                    <%- exp.FirstName%> <%- exp.LastName %>
                 </label>
                 <br/>
             <% }) %>
         <% } else { %>
             <p>No users found</p>
diff --git a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js
index 910f4dbf8..ff1f72cf1 100644
--- a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js
+++ b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js
@@ -108,11 +108,10 @@ var CommentsPane = function CommentsPane($element, opts) {
                     ann.owner = experimenters[ann.owner.id];
                     if (ann.link && ann.link.owner) {
                         ann.link.owner = experimenters[ann.link.owner.id];
                     }
                     ann.addedBy = [ann.link.owner.id];
-                    ann.textValue = _.escape(ann.textValue);
                     return ann;
                 });
 
                 // Show most recent comments at the top
                 anns.sort(function(a, b) {
diff --git a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js
index 56f5bfb82..38bac8336 100644
--- a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js
+++ b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js
@@ -86,11 +86,11 @@ var CustomAnnsPane = function CustomAnnsPane($element, opts) {
                     // convert 'class' to 'type' E.g. XmlAnnotationI to Xml
                     ann.type = ann.class.replace('AnnotationI', '');
                     var attrs = ['textValue', 'timeValue', 'termValue', 'longValue', 'doubleValue', 'boolValue'];
                     attrs.forEach(function(a){
                         if (ann[a] !== undefined){
-                            ann.value = _.escape(ann[a]);
+                            ann.value = ann[a];
                         }
                     });
                     if (objects.length > 1) {
                         ann.parent = {
                             'class': ann.link.parent.class.slice(0, -1), // slice parent class 'ProjectI' > 'Project'
diff --git a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js
index 02a1ab52b..a823b9629 100644
--- a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js
+++ b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js
@@ -162,11 +162,10 @@ var FileAnnsPane = function FileAnnsPane($element, opts) {
                     if (ann.link && ann.link.owner) {
                         ann.link.owner = experimenters[ann.link.owner.id];
                     }
                     // AddedBy IDs for filtering
                     ann.addedBy = [ann.link.owner.id];
-                    ann.description = _.escape(ann.description);
                     ann.file.size = ann.file.size !== null ? ann.file.size.filesizeformat() : "";
                     return ann;
                 });
                 // Don't show companion files
                 anns = anns.filter(isNotCompanionFile);
diff --git a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js
index 631bf46af..7aa858bc6 100644
--- a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js
+++ b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js
@@ -141,12 +141,12 @@ var TagPane = function TagPane($element, opts) {
                     if (tag.link && tag.link.owner) {
                         tag.link.owner = experimenters[tag.link.owner.id];
                     }
                     // AddedBy IDs for filtering
                     tag.addedBy = [tag.link.owner.id];
-                    tag.textValue = _.escape(tag.textValue);
-                    tag.description = _.escape(tag.description);
+                    tag.textValue = tag.textValue;
+                    tag.description = tag.description;
                     tag.canRemove = tag.link.permissions.canDelete;
                     return tag;
                 });
 
                 // If we are batch annotating multiple objects, we show a summary of each tag
@@ -156,11 +156,11 @@ var TagPane = function TagPane($element, opts) {
                     var summary = {};
                     tags.forEach(function(tag){
                         var tagId = tag.id,
                             linkOwner = tag.link.owner.id;
                         if (summary[tagId] === undefined) {
-                            summary[tagId] = {'textValue': tag.textValue,
+                            summary[tagId] = {'textValue': _.escape(tag.textValue),
                                               'id': tag.id,
                                               'canRemove': false,
                                               'canRemoveCount': 0,
                                               'links': [],
                                               'addedBy': []
diff --git a/omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js b/omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js
index b161ea020..6d5b29a82 100644
--- a/omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js
+++ b/omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js
@@ -96,39 +96,32 @@ var tagging_form = function(
     var create_html = function() {
         var html = "";
         for (var id in all_tags) {
             var tag = all_tags[id];
             if (tag && !(id in child_tags)) {
-                html += create_tag_html(tag.t, tag.d, owners[tag.o], tag.i,
-                                        null, tag.s !== 0);
+                html += create_tag_html(
+                    tag.t, tag.d, tag.i, null, tag.s !== 0
+                );
                 tag.sort_key = tag.t.toLowerCase();
                 if (tag.s) {
                     for (var sid in tag.s) {
                         var child = all_tags[tag.s[sid]];
                         if (child) {
                             child.sort_key = (tag.t.toLowerCase() +
                                 child.t.toLowerCase());
                             html += create_tag_html(
-                                child.t, child.d, owners[child.o], child.i,
-                                tag.i);
+                                child.t, child.d, child.i, tag.i
+                            );
                         }
                     }
                 }
             }
         }
         div_all_tags.append(html);
         // TODO This tooltip application is used until the extra data has loaded
         // at which point the tooltips are updated and this handler is replaced?
-        $(".tag_selection div").on('click', tag_click).tooltip({
-            track: false,
-            show: false,
-            hide: false,
-            items: '[data-content]',
-            content: function() {
-                return $(this).data('content');
-            }
-        });
+        $(".tag_selection div").on('click', tag_click);
     };
 
     var update_selected_labels = function() {
         var count = 0;
         $(".ui-selected", div_all_tags).not(".filtered").each(function() {
@@ -143,11 +136,11 @@ var tagging_form = function(
         $("#id_tags_selected").text(count ? count + " selected" : "");
         var tagset = get_selected_tagset();
         if (tagset) {
             $("#id_selected_tag_set").html(
                 "Add a new tag in <span class='tagset-title'>" +
-                tagset.text() + "</span> tag set and select it immediately:");
+                tagset.text().escapeHTML() + "</span> tag set and select it immediately:");
         } else {
             $("#id_selected_tag_set").text(
                 "Add a new tag and select it immediately:");
         }
     };
@@ -167,10 +160,11 @@ var tagging_form = function(
             parent_id ? parent_id.t : null, link_owner);
         $this.tooltip({
             track: false,
             show: false,
             hide: false,
+            items: '[data-id]',  // Just needs an attribute that exists
             content: title
         });
     };
 
     var update_html_list = function(list) {
@@ -367,49 +361,39 @@ var tagging_form = function(
               $("#add_tags_form").parent()
               ).prop("disabled", false).removeClass('ui-state-disabled');
         }
     };
 
-    var encode_html = function(text) {
-        return $('<div/>').text(text).html();
-    };
-
     var create_tag_title = function(description, owner, tagset, link_owner) {
         var title = "";
         if (owner) {
-            title += "<b>Owner:</b> " + owner + "<br />";
+            title += "<b>Owner:</b> " + owner.escapeHTML() + "<br />";
         }
         if (link_owner) {
-            title += "<b>Linked by:</b> " + link_owner + "<br />";
+            title += "<b>Linked by:</b> " + link_owner.escapeHTML() + "<br />";
         }
         if (description) {
-            title += "<b>Description:</b> " + description + "<br />";
+            title += "<b>Description:</b> " + description.escapeHTML() + "<br />";
         }
         if (tagset) {
-            title += "<b>Tag set:</b> " + tagset + "<br />";
+            title += "<b>Tag set:</b> " + tagset.escapeHTML() + "<br />";
         }
         return title;
     };
 
-    var create_tag_html = function(text, description, owner, id, parent_id,
+    var create_tag_html = function(text, description, id, parent_id,
                                    is_tagset) {
         var cls = is_tagset ? "alltags-tagset" :
             (parent_id ? "alltags-childtag" : "alltags-tag");
         var html = "<div class='" + cls + "' data-id='" + id + "'";
-        var tagset;
         if (parent_id) {
             html += " data-set='" + parent_id + "'";
-            tagset = all_tags[parent_id].t;
         }
         if (id < 0) { // new tag, save description
-            html += " data-description='" + encode_html(description) + "'";
+            html += " data-description='" + description.escapeHTML() + "'";
         }
-        var title = create_tag_title(description, owner, tagset);
-        // Add content even if it is empty
-        // and handle case for no tooltip in the tooltip code
-        html += " data-content='" + title.replace(/'/g, "&#39;") + "'";
-        html += ">" + encode_html(text) + "</div>";
+        html += ">" + text.escapeHTML() + "</div>";
         return html;
     };
 
     var select_tags = function(event) {
         // clear selections in right box
@@ -631,31 +615,33 @@ var tagging_form = function(
             }
 
             new_tag_counter -= 1;
             var tagset_id = (tagset ? parseInt(tagset.attr('data-id'), 10) :
                              false);
-            owners[me] = owners[me] || my_name;
+            owners[me] = owners[me] || _.unescape(my_name);
             all_tags[new_tag_counter] = {
                 i: new_tag_counter,
                 d: description,
                 t: text,
                 o: me,
                 s: tagset_id,
                 sort_key: (tagset_id ? all_tags[tagset_id].t.toLowerCase() :
                            '') + text.toLowerCase()
             };
             var div = $(create_tag_html(
-                text, description, my_name, new_tag_counter,
+                text, description, new_tag_counter,
                 tagset ? tagset.attr('data-id') : null));
+            var title = create_tag_title(
+                description, _.unescape(my_name),
+                tagset_id? all_tags[tagset_id].t : null
+            );
             div.addClass('ui-selected').on('click', tag_click).tooltip({
                 track: true,
                 show: false,
                 hide: false,
-                items: '[data-content]',
-                content: function() {
-                    return $(this).data('content');
-                }
+                items: '[data-id]',  // Just needs an attribute that exists
+                content: title
             });
             $("div.ui-selected", div_selected_tags).removeClass('ui-selected');
             div_selected_tags.append(div);
             tag_input.val('').trigger('focus');
             description_input.val('');
diff --git a/omeroweb/webclient/static/webclient/javascript/ome.thumbnail_figure.js b/omeroweb/webclient/static/webclient/javascript/ome.thumbnail_figure.js
index 0de438efc..6d2b5d681 100644
--- a/omeroweb/webclient/static/webclient/javascript/ome.thumbnail_figure.js
+++ b/omeroweb/webclient/static/webclient/javascript/ome.thumbnail_figure.js
@@ -199,11 +199,11 @@ $(document).ready(function() {
                     }
                     if (tagsText.length === 0 || tagData.tags[0] !== topLevelTag) {
                         // start a new container...
                         topLevelTag = tagData.tags[0] || tagsText;      // Group under 1st tag (unless we have no tags)
 
-                        $tr = $('<tr><th><h2>' + topLevelTag + '</h2></th></tr>');
+                        $tr = $('<tr><th><h2>' + topLevelTag.escapeHTML() + '</h2></th></tr>');
                         $tr.appendTo($this);
                         $td = $('<td></td>').appendTo($tr);
                     }
                     $td.append('<div class="subsetLabel">'+ tagsText + '</div>');
 
@@ -261,6 +261,6 @@ $(document).ready(function() {
         slide: function(event, ui) {
             setImgSize(ui.value);
         }
     });
 
-});
\ No newline at end of file
+});
diff --git a/omeroweb/webclient/templates/webclient/annotations/comments_underscore.html b/omeroweb/webclient/templates/webclient/annotations/comments_underscore.html
index 03103528a..bdf030cef 100644
--- a/omeroweb/webclient/templates/webclient/annotations/comments_underscore.html
+++ b/omeroweb/webclient/templates/webclient/annotations/comments_underscore.html
@@ -1,38 +1,38 @@
 <% _.each(anns, function(ann) { %>
-<div class="ann_comment_wrapper" data-added-by="<% print (ann.addedBy.join(',')) %>">
+<div class="ann_comment_wrapper" data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
 
     <div class="avatar">
-        <img src="<%= webindex %>avatar/<%= ann.owner.id %>/"
-        alt="<%= ann.owner.firstName %> <%= ann.owner.lastName %>"
-        title="<%= ann.owner.firstName %> <%= ann.owner.lastName %>" />
+        <img src="<%= webindex %>avatar/<%- ann.owner.id %>/"
+        alt="<%- ann.owner.firstName %> <%- ann.owner.lastName %>"
+        title="<%- ann.owner.firstName %> <%- ann.owner.lastName %>" />
     </div>
 
     <div class="ann_comment_text tooltip">
         <div class="ann_comment_header">
             <strong>
-                <%= ann.owner.firstName %> <%= ann.owner.lastName %>
-            </strong> 
+                <%- ann.owner.firstName %> <%- ann.owner.lastName %>
+            </strong>
             at
             <% print(OME.formatDate(ann.link.date)) %>
         </div>
-    
+
         <% if (ann.permissions.canDelete) { %>
-            <img class='removeComment' id="<%= ann.id %>-comment"
+            <img class='removeComment' id="<%- ann.id %>-comment"
                 src="<%= static %>image/icon_basic_delete.png"
-                url='<%= webindex %>action/remove/comment/<%= ann.id %>/' 
+                url='<%= webindex %>action/remove/comment/<%- ann.id %>/'
                 title="Delete comment"/>
         <% } %>
 
-        <div class='commentText'><%= ann.textValue %></div>
+        <div class='commentText'><%- ann.textValue %></div>
     </div>
 
     <% if (ann.ns || ann.description) { %>
     <span class="tooltip_html" style='display:none'>
-        <b>ID:</b> <%= ann.id %><br />
-        <% if (ann.ns) { %><b>Namespace:</b> <%= ann.ns %><br /><% } %>
-        <% if (ann.description) { %><b>Description:</b> <%= ann.description %><br /><% } %>
+        <b>ID:</b> <%- ann.id %><br />
+        <% if (ann.ns) { %><b>Namespace:</b> <%- ann.ns %><br /><% } %>
+        <% if (ann.description) { %><b>Description:</b> <%- ann.description %><br /><% } %>
     </span>
     <% } %>
 
 </div>
 <% }) %>
diff --git a/omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html b/omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html
index eb8cdd8e0..af3086490 100644
--- a/omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html
+++ b/omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html
@@ -1,31 +1,31 @@
 
 <% _.each(anns, function(ann) { %>
 
-<tr data-added-by="<% print (ann.addedBy.join(',')) %>">
-    <th><%= ann.type %></th>
+<tr data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
+    <th><%- ann.type %></th>
     <td>
         <div class="tooltip">
         <% if (ann.type === 'Xml') { %>
-            <div><% print((ann.value + "").slice(0, 20)) %>...</div>
+            <div><% print(_.escape((ann.value + "").slice(0, 20))) %>...</div>
             <div class="show_xml">Open in new window</div>
-            <div style="display: none"><%= ann.value %></div>
+            <div style="display: none"><%- ann.value %></div>
         <% } else { %>
-            <div><%= ann.value %></div>
+            <div><%- ann.value %></div>
         <% } %>
         </div>
-        
+
         <span class="tooltip_html" style='display:none'>
-            <b>ID:</b> <%= ann.id %><br />
-            <% if (ann.ns) { %><b>Namespace:</b> <%= ann.ns %><br /><% } %>
-            <% if (ann.description) { %><b>Description:</b> <%= ann.description %><br /><% } %>
-            <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %><br />
+            <b>ID:</b> <%- ann.id %><br />
+            <% if (ann.ns) { %><b>Namespace:</b> <%- ann.ns %><br /><% } %>
+            <% if (ann.description) { %><b>Description:</b> <%- ann.description %><br /><% } %>
+            <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %><br />
             <b>Date:</b> <% print(OME.formatDate(ann.date)) %>
 
             <% if (ann.parent) { %>
             <br />
-            <b>Linked to:</b> <%= ann.parent.class %> <%= ann.parent.id %>
+            <b>Linked to:</b> <%- ann.parent.class %> <%- ann.parent.id %>
             <% } %>
         </span>
     </td>
 </tr>
-<% }) %>
\ No newline at end of file
+<% }) %>
diff --git a/omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html b/omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html
index 55af29ff4..79edbb194 100644
--- a/omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html
+++ b/omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html
@@ -1,68 +1,68 @@
 
 <% _.each(anns, function(ann) { %>
 <li class="file_ann_wrapper"
-    id="file_ann-<%= ann.id %>"
-    data-added-by="<% print (ann.addedBy.join(',')) %>">
+    id="file_ann-<%- ann.id %>"
+    data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
 
     <a class='tooltip'
         href="<% if (ann.permissions.canDownload) { print(webindex + 'annotation/' + ann.id)} else {print('#') } %>">
-        <%= ann.file.name %>
+        <%- ann.file.name %>
         <% if (ann.file.size) { %>
-            <span>(<%= ann.file.size %>)</span>
+            <span>(<%- ann.file.size %>)</span>
         <% } %>
     </a>
 
     <span class="tooltip_html" style='display:none'>
         <!-- show different tooltip for batch_annotate panel -->
         <% if (ann.links) { %>
-            Can remove File from <b><%= ann.canRemoveCount %>
+            Can remove File from <b><%- ann.canRemoveCount %>
             object<% if(ann.canRemoveCount !== 1) {print('s')} %></b>:<br/>
             <% _.each(ann.links, function(link, idx) { %>
                 <div>
                     <% if (idx < 20) { %>
-                        <b><%= link.parent.class %> <%= link.parent.id %></b>
+                        <b><%- link.parent.class %> <%- link.parent.id %></b>
                         <% print(_.escape(link.parent.name).slice(0, 28)) %>
                         <% if (link.owner.id !== userId){
                             print("(" + link.owner.firstName.slice(0, 1) + " " + _.escape(link.owner.lastName) + ")")
                         } %>
                     <% } else if (idx === 20) { %>
                         and <b><% print(ann.links.length - 20) %></b> other objects...
                     <% } %>
                 </div>
             <% }) %>
         <% } else { %>
-            <b>Annotation ID:</b> <%= ann.id %><br />
-            <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %><br />
-            <b>Linked by:</b> <%= ann.link.owner.firstName %> <%= ann.link.owner.lastName %><br />
+            <b>Annotation ID:</b> <%- ann.id %><br />
+            <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %><br />
+            <b>Linked by:</b> <%- ann.link.owner.firstName %> <%- ann.link.owner.lastName %><br />
             <b>On:</b> <% print(OME.formatDate(ann.link.date)) %> <br />
-            <b>Description:</b> <%= ann.description %>
+            <b>Description:</b> <%- ann.description %>
             <% if (ann.ns){ %>
-                <br/><b>Namespace:</b> <%= ann.ns %>
+                <br/><b>Namespace:</b> <%- ann.ns %>
             <% } %>
             <% if (ann.file.mimetype){ %>
-                <br/><b>Mimetype:</b> <%= ann.file.mimetype %>
+                <br/><b>Mimetype:</b> <%- ann.file.mimetype %>
             <% } %>
-            <br/><b>File ID:</b> <%= ann.file.id %>
+            <br/><b>File ID:</b> <%- ann.file.id %>
         <% } %>
     </span>
 
     <div class="attachment_actions">
         <input type="checkbox" style="display:none;"/>
         <% if ((ann.ns && ann.ns === 'openmicroscopy.org/omero/bulk_annotations') || ann.file.mimetype == 'OMERO.tables' ){ %>
             <a class='action btn_view' title="View OMERO.table" target="_blank"
-            href='<%= webindex %>omero_table/<%= ann.file.id %>/'>&nbsp;</a>
+            href='<%= webindex %>omero_table/<%- ann.file.id %>/'>&nbsp;</a>
         <% } %>
         <% if (ann.link.permissions.canDelete) { %> <!-- and not ann.isOriginalMetadata -->
-            <a class='removeFile action' id="<%= ann.id %>-file" title="Remove File"
-            href='<%= webindex %>action/remove/file/<%= ann.id %>/'>&#8211</a>
+            <a class='removeFile action' id="<%- ann.id %>-file" title="Remove File"
+            href='<%= webindex %>action/remove/file/<%- ann.id %>/'>&#8211</a>
         <% } %>
 
         <% if (ann.permissions.canDelete) { %> <!-- and not ann.isOriginalMetadata -->
-            <a id="<%= ann.id %>-file" type="image" class="deleteFile action" title="Delete File"
-                href="<%= webindex %>action/delete/file/<%= ann.id %>/"> &#215 </a>
+            <a id="<%- ann.id %>-file" type="image" class="deleteFile action" title="Delete File"
+                href="<%= webindex %>action/delete/file/<%- ann.id %>/"> &#215 </a>
         <% } %>
 
     </div>
 </li>
 
-<% }) %>
\ No newline at end of file
+<% }) %>
diff --git a/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html b/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html
index 6e50702bb..fc2842799 100644
--- a/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html
+++ b/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html
@@ -1,68 +1,68 @@
 <% _.each(anns, function(ann) { %>
 
 <table <% if (ann.id && ann.addedBy) { %>
-            data-annId="<%= ann.id %>"
-            data-added-by="<% print (ann.addedBy.join(',')) %>"
+            data-annId="<%- ann.id %>"
+            data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>"
         <% } else { %>
             data-added-by="<%= WEBCLIENT.USER.id %>"
         <% } %>
         class="keyValueTable
             <% if (!ann.id || (ann.permissions.canEdit && clientMapAnn)){ %> editableKeyValueTable <% } %>
             ">
     <thead>
       <% if (showNs && ann.ns) { %>
-      <tr title="<%= ann.ns %>">
+      <tr title="<%- ann.ns %>">
           <th colspan="2">
-              <%= ann.ns.slice(0, 50) %>
+              <%- ann.ns.slice(0, 50) %>
           </th>
       </tr>
       <% } %>
       <tr class="tooltip">
           <th colspan="2">
             <% if (ann.id) { %>
-                Added by: <%= ann.owner.firstName %> <%= ann.owner.lastName %>
+                Added by: <%- ann.owner.firstName %> <%- ann.owner.lastName %>
                 <% if (showParent && ann.link.parent.name){ %>
                   <br>
                   <% if (ann.parentNames) { %>
-                    <%= ann.parentNames.length %> Annotations linked to:
+                    <%- ann.parentNames.length %> Annotations linked to:
                   <% } else { %>
                     To:
                   <% } %>
-                  <%= ann.parentNames ? (ann.parentNames.length + " objects") : ann.link.parent.name %>
+                  <%- ann.parentNames ? (ann.parentNames.length + " objects") : ann.link.parent.name %>
                 <% } %>
 
                 <span class="tooltip_html" style='display:none'>
                     <% if (ann.parentNames) { %>
                       You are
                       <% print (ann.permissions.canEdit && clientMapAnn ? 'editing' : 'viewing') %>
-                      <b><%= ann.parentNames.length %></b> identical Key-Value annotations:
+                      <b><%- ann.parentNames.length %></b> identical Key-Value annotations:
                     <% } %>
                     <% if (!ann.parentNames && ann.link) { %>
                         <!-- If single object show e.g. Image ID: (slice ImageI -> Image) -->
-                        <b><%= ann.link.parent.class.slice(0, ann.link.parent.class.length-1) %>
-                            ID:</b> <%= ann.link.parent.id %><br />
+                        <b><%- ann.link.parent.class.slice(0, ann.link.parent.class.length-1) %>
+                            ID:</b> <%- ann.link.parent.id %><br />
                     <% } %>
                     <b>Annotation ID<% if (ann.parentNames) { %>s<% } %>:</b> <%= ann.id %><br />
                     <% if (ann.parentNames) { %>
                         <b>Linked to:</b><br>
                         <% _.each(ann.parentNames, function(pName) { %>
-                            &nbsp <%= pName %><br />
+                            &nbsp <%- pName %><br />
                         <% }) %>
                     <% } %>
                     <% if (ann.owner) { %>
-                        <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %>
+                        <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %>
                     <% } %>
                     <% if (ann.link) { %>
-                        <br /><b>Linked by:</b> <%= ann.link.owner.firstName %> <%= ann.link.owner.lastName %>
+                        <br /><b>Linked by:</b> <%- ann.link.owner.firstName %> <%- ann.link.owner.lastName %>
                         <% if (ann.link.date) { %>
                             <br /><b>On:</b> <% print(OME.formatDate(ann.link.date)) %>
                         <% } %>
                     <% } %>
                 </span>
             <% } else if (objCount && objCount > 1) { %>
-                Add annotations to <%= objCount %> objects
+                Add annotations to <%- objCount %> objects
                 <span class="tooltip_html" style='display:none'>
                     Identical Key-Value annotations will be added to each selected object.
                 </span>
             <% } %>
           </th>
@@ -78,12 +78,12 @@
 
     <!-- Last row always has empty key & value fields -->
     <% if (ann.id) { %>
       <% _.each(ann.values, function(row) { %>
         <tr>
-            <td><% print(_.escape(row['0'])) %></td>
-            <td><% print(_.escape(row['1'])) %></td>
+            <td><%- row['0'] %></td>
+            <td><%- row['1'] %></td>
         </tr>
       <% }) %>
     <% } else { %>
       <tr class="placeholder">
           <td>Add Key</td>
diff --git a/omeroweb/webclient/templates/webclient/annotations/metadata_general.html b/omeroweb/webclient/templates/webclient/annotations/metadata_general.html
index e8e808443..0ebb08f66 100644
--- a/omeroweb/webclient/templates/webclient/annotations/metadata_general.html
+++ b/omeroweb/webclient/templates/webclient/annotations/metadata_general.html
@@ -357,14 +357,14 @@
                     var showBulkAnnTooltip = function(data) {
                         if (!data || !data.id) return;
                         var bulkAnnTooltip = "<span class='tooltip_html' style='display:none'>" +
                             "Data from tables file:<br />" +
                             "<b>File ID:</b> " + data.id + "<br />" +
-                            "<b>Owner:</b> " + data.owner + " <br />" +
+                            "<b>Owner:</b> " + data.owner.escapeHTML() + " <br />" +
                             "<b>Annotation ID:</b> " + data.annId + "<br />" +
                             "<b>Linked to:</b> " + data.parentType + " " + data.parentId + "<br />" +
-                            "<b>Linked by:</b> " + data.addedBy + " <br />" +
+                            "<b>Linked by:</b> " + data.addedBy.escapeHTML() + " <br />" +
                             "<b>On:</b> " + OME.formatDate(data.addedOn) + "</span>";
                         $("#bulk-annotations").append(bulkAnnTooltip);
                         $("#bulk-annotations").parent().tooltip({
                                 items: '.bulk_annotation',
                                 content: function() {
diff --git a/omeroweb/webclient/templates/webclient/annotations/tags_underscore.html b/omeroweb/webclient/templates/webclient/annotations/tags_underscore.html
index 6f078c095..3ff977abd 100644
--- a/omeroweb/webclient/templates/webclient/annotations/tags_underscore.html
+++ b/omeroweb/webclient/templates/webclient/annotations/tags_underscore.html
@@ -1,48 +1,48 @@
 
 <% _.each(tags, function(tag) { %>
 <span class="tag_annotation_wrapper"
-    data-tag-id="<%= tag.id %>"
-    data-added-by="<% print (tag.addedBy.join(',')) %>">
-	
+    data-tag-id="<%- tag.id %>"
+    data-added-by="<% print(_.escape(tag.addedBy.join(','))) %>">
+
     <div class="tag">
-        <a class='tooltip tag_inner' href="?show=tag-<%= tag.id %>" target="_top">
-            <%= tag.textValue %>
+        <a class='tooltip tag_inner' href="?show=tag-<%- tag.id %>" target="_top">
+            <%- tag.textValue %>
         </a>
 
         <% if (tag.canRemove) { %>
-        <span class='removeTag' title="Remove Tag" id="<%= tag.id %>-tag"
-                url='<%= webindex %>action/remove/tag/<%= tag.id %>/'>
+        <span class='removeTag' title="Remove Tag" id="<%- tag.id %>-tag"
+                url='<%= webindex %>action/remove/tag/<%- tag.id %>/'>
                 -
         </span>
         <% } %>
 
         <span class="tooltip_html" style='display:none'>
         <!-- show different tooltip for batch_annotate panel -->
         <% if (tag.links) { %>
-            Can remove Tag from <b><%= tag.canRemoveCount %> object<% if(tag.canRemoveCount !== 1) {print('s')} %></b>:<br/>
+            Can remove Tag from <b><%- tag.canRemoveCount %> object<% if(tag.canRemoveCount !== 1) {print('s')} %></b>:<br/>
             <% _.each(tag.links, function(link, idx) { %>
                 <div>
                     <% if (idx < 20) { %>
-                        <b><%= link.parent.class %> <%= link.parent.id %></b>
-                        <% print(_.escape(link.parent.name).slice(0, 28)) %>
-                        <% if (link.owner.id !== userId){
+                        <b><%- link.parent.class %> <%- link.parent.id %></b>
+                        <%- link.parent.name.slice(0, 28) %>
+                        <% if (link.owner.id !== userId) {
                             print("(" + link.owner.firstName.slice(0, 1) + " " + _.escape(link.owner.lastName) + ")")
                         } %>
                     <% } else if (idx === 20) { %>
                         and <b><% print(tag.links.length - 20) %></b> other objects...
                     <% } %>
                 </div>
             <% }) %>
         <% } else { %>
-            <b>ID:</b> <%= tag.id %><br />
-            <b>Owner:</b> <%= tag.owner.firstName %> <%= tag.owner.lastName %><br />
-            <b>Linked by:</b> <%= tag.link.owner.firstName %> <%= tag.link.owner.lastName %><br />
+            <b>ID:</b> <%- tag.id %><br />
+            <b>Owner:</b> <%- tag.owner.firstName %> <%- tag.owner.lastName %><br />
+            <b>Linked by:</b> <%- tag.link.owner.firstName %> <%- tag.link.owner.lastName %><br />
             <b>On:</b> <% print(OME.formatDate(tag.link.date)) %><br />
-            <b>Description:</b> <%= tag.description %>
+            <b>Description:</b> <%- tag.description %>
         <% } %>
         </span>
     </div>
-    
+
 </span>
 
-<% }) %>
\ No newline at end of file
+<% }) %>
diff --git a/omeroweb/webclient/templates/webclient/data/icon_thumbnails_underscore.html b/omeroweb/webclient/templates/webclient/data/icon_thumbnails_underscore.html
index 18df09a3a..524aa0ad2 100644
--- a/omeroweb/webclient/templates/webclient/data/icon_thumbnails_underscore.html
+++ b/omeroweb/webclient/templates/webclient/data/icon_thumbnails_underscore.html
@@ -12,34 +12,34 @@
     </li>
 
     <% _.each(images, function(img) { %>
         <li class="row <% if(img.selected) {print ('ui-selected')} %>
             <% if(img.fsSelected) {print ('fs-selected')} %> "
-            id="image_icon-<%= img.id %>"
-            data-fileset="<%= img.data.obj.filesetId %>"
+            id="image_icon-<%- img.id %>"
+            data-fileset="<%- img.data.obj.filesetId %>"
             data-type="image"
-            data-id="<%= img.id %>"
+            data-id="<%- img.id %>"
             data-perms="<%= img.data.obj.permsCss %>"
             tabindex="0"
-            <% if(img.shareId) { %> data-share="<%= img.shareId %>" <% }%>
+            <% if(img.shareId) { %> data-share="<%- img.shareId %>" <% }%>
             data-owned="">
 
             <div class="image">
                 <!-- we wrap img with <a> so you can right-click -> open link in new tab -->
-                <a href="<%= webindex %><% if(img.shareId) {print( img.shareId + '/')} %>img_detail/<%= img.id %>/<% if (dataset) print('?dataset=' + dataset) %>" >
+                <a href="<%= webindex %><% if(img.shareId) {print( img.shareId + '/')} %>img_detail/<%- img.id %>/<% if (dataset) print('?dataset=' + dataset) %>" >
                     <img/>
                 </a>
             </div>
             <!-- NB: '#image_icon-123 div.desc' etc is used to update name when changed in right panel via "editinplace" -->
             <div class="desc" valign="middle">
-                 <%= img.name %>
-                <span class="hidden_sort_text"> <%= img.name %> </span>
+                 <%- img.name %>
+                <span class="hidden_sort_text"> <%- img.name %> </span>
             </div>
-            <div class="date" valign="middle"><%= img.date %></div>
-            <div class="sizeX" valign="middle"><%= img.data.obj.sizeX %></div>
-            <div class="sizeY" valign="middle"><%= img.data.obj.sizeY %></div>
-            <div class="sizeZ" valign="middle"><%= img.data.obj.sizeZ %></div>
+            <div class="date" valign="middle"><%- img.date %></div>
+            <div class="sizeX" valign="middle"><%- img.data.obj.sizeX %></div>
+            <div class="sizeY" valign="middle"><%- img.data.obj.sizeY %></div>
+            <div class="sizeZ" valign="middle"><%- img.data.obj.sizeZ %></div>
         </li>
     <% }) %>
 </ul>
 
 <% if (paging) { %>
@@ -60,6 +60,6 @@
         <input data-page="<%= page+1 %>" class="button_pagination" type="button" value="Next" alt="Next"/>
     <% } %>
 
 </div>
 <div class="clear"> </div>
-<% } %>
\ No newline at end of file
+<% } %>
diff --git a/omeroweb/webclient/templates/webclient/data/includes/center_plugin.thumbs.js.html b/omeroweb/webclient/templates/webclient/data/includes/center_plugin.thumbs.js.html
index be739c298..4a6cdd209 100644
--- a/omeroweb/webclient/templates/webclient/data/includes/center_plugin.thumbs.js.html
+++ b/omeroweb/webclient/templates/webclient/data/includes/center_plugin.thumbs.js.html
@@ -640,11 +640,11 @@ $(document).ready(function() {
                 return a.text < b.text ? -1 : 1;
             });
             var html = tagList.map(function(t){
                 // don't show tags we're filtering by
                 if (filter_tag_ids.indexOf(t.id) > -1) return "";
-                return "<option value='" + t.id + "'>" + t.text + "</option>";
+                return "<option value='" + t.id + "'>" + t.text.escapeHTML() + "</option>";
             }).join("");
             html = "<option value='0'>Choose Tag</option>" + html;
             $("#filter_by_tag").html(html);
 
             // hide filtered images 
@@ -661,11 +661,11 @@ $(document).ready(function() {
                     $this.addClass('tagFilter_hidden');
                 }
             });
             // show tags
             var html = filter_tag_ids.map(function(tagId){
-                var tagText = usedTags[tagId].textValue;
+                var tagText = usedTags[tagId].textValue.escapeHTML();
                 return "<div class='tag' data-tagId='" + tagId + "'><a class='tag_inner' href='#'>" + tagText + "</a><span class='removeTag removefilter' title='Remove Tag'>-</span></div>";
             }).join("");
             $("#currentFilterTags").html(html);
         };
 
diff --git a/omeroweb/webclient/templates/webclient/data/plate.html b/omeroweb/webclient/templates/webclient/data/plate.html
index c86fb5ae1..b56234ab4 100644
--- a/omeroweb/webclient/templates/webclient/data/plate.html
+++ b/omeroweb/webclient/templates/webclient/data/plate.html
@@ -265,11 +265,11 @@
                         var html = data.reduce(function(html, w, idx){
                             var cls = '';
                             if (selectImageIds && selectImageIds.indexOf(w.id) > -1) {
                                 cls = 'class="ui-selected"';
                             }
-                            html += '<li ' + cls + ' title="' + w.name + '" data-imageId="' + w.id + '">';
+                            html += '<li ' + cls + ' title="' + w.name.escapeHTML() + '" data-imageId="' + w.id + '">';
                             html += '<a href="{% url 'webindex' %}img_detail/' + w.id + '/">';
                             html += '<div class="wellSize"><img /></div>';
                             html += '</a></li>';
                             if (!isNaN(imagesPerRow) && (idx+1)%imagesPerRow === 0) {
                                 html += '<br>';
diff --git a/omeroweb/webclient/templates/webclient/scripts/upload_script.html b/omeroweb/webclient/templates/webclient/scripts/upload_script.html
index 3095045e7..eb36183a4 100644
--- a/omeroweb/webclient/templates/webclient/scripts/upload_script.html
+++ b/omeroweb/webclient/templates/webclient/scripts/upload_script.html
@@ -121,11 +121,11 @@
                     $('#submit').attr('disabled', 'disabled');
                     return;
                 }
                 $('#targetInfo').show();
                 if (!chosen_file.endsWith(".py")) {
-                    $("#targetMessage").html("File: " + chosen_file + " not valid.<br/>File must be Python script (.py)");
+                    $("#targetMessage").html("File: " + chosen_file.escapeHTML() + " not valid.<br/>File must be Python script (.py)");
                     return;
                 }
 
                 $('#submit').prop('disabled', false);
 
diff --git a/omeroweb/webclient/views.py b/omeroweb/webclient/views.py
index 49df928c0..0b4bf835c 100755
--- a/omeroweb/webclient/views.py
+++ b/omeroweb/webclient/views.py
@@ -33,10 +33,11 @@ import json
 import re
 import sys
 import warnings
 from past.builtins import unicode
 from future.utils import bytes_to_native_str
+from django.utils.html import escape
 from django.utils.http import is_safe_url
 
 from time import time
 
 from omeroweb.version import omeroweb_buildyear as build_year
@@ -1791,11 +1792,11 @@ def load_metadata_preview(request, c_type, c_id, conn=None, share_id=None, **kwa
                 "%s%s|%s:%s%s$%s" % (act, i + 1, c["start"], c["end"], reverse, color)
             )
         rdefQueries.append(
             {
                 "id": r["id"],
-                "owner": r["owner"],
+                "owner": escape(r["owner"]),  # May be used unsafe later
                 "c": ",".join(chs),
                 "m": r["model"] == "greyscale" and "g" or "c",
             }
         )
     max_w, max_h = conn.getMaxPlaneSize()
diff --git a/omeroweb/webgateway/static/webgateway/js/ome.popup.js b/omeroweb/webgateway/static/webgateway/js/ome.popup.js
index acf3dd9b3..f53afd7a7 100644
--- a/omeroweb/webgateway/static/webgateway/js/ome.popup.js
+++ b/omeroweb/webgateway/static/webgateway/js/ome.popup.js
@@ -167,11 +167,13 @@ String.prototype.escapeHTML = function(){
     s = s + "";
     return s.replace(/[\&"<>\\]/g, function(s) {
         switch(s) {
             case "&": return "&amp;";
             case "\\": return "&#92;";
-            case '"': return '\"';
+            case "`": return '&#x60;';
+            case "'": return '&#x27;';
+            case '"': return '&quot;';
             case "<": return "&lt;";
             case ">": return "&gt;";
             default: return s;
         }
     });
diff --git a/omeroweb/webgateway/static/webgateway/js/ome.roidisplay.js b/omeroweb/webgateway/static/webgateway/js/ome.roidisplay.js
index 1a02a52d4..73d6c9b3d 100644
--- a/omeroweb/webgateway/static/webgateway/js/ome.roidisplay.js
+++ b/omeroweb/webgateway/static/webgateway/js/ome.roidisplay.js
@@ -794,16 +794,16 @@ $.fn.roi_display = function(options) {
                                 } else if (roi_label_displayed) {
                                     // otherwise, add a new label in the centre of the shape.
                                     var bb = newShape.getBBox();
                                     var textx = bb.x + (bb.width/2);
                                     var texty = bb.y + (bb.height/2);
-                                    var text_string = formatShapeText(shape['textValue'].escapeHTML())
+                                    var text_string = formatShapeText(shape['textValue'])
                                     var txt = paper.text(textx, texty, text_string);    // draw a 'dummy' paragraph to work out it's dimensions
                                     var newY = (texty-txt.getBBox().height/2)+9;
                                     // moving the existing text to newY doesn't seem to work - instead, remove and draw a new one
                                     txt.remove();
-                                    txt = paper.text(textx, newY, formatShapeText(shape['textValue'].escapeHTML()))
+                                    txt = paper.text(textx, newY, formatShapeText(shape['textValue']))
                                                .attr({'cursor':'default', 'fill': shape['strokeColor']}); // this is Insight's behavior
                                     txt_box = txt.getBBox();
                                     var txt_w = txt_box.width*1.3;
                                     var txt_h = txt_box.height*1.3;
                                     txt.toFront();
diff --git a/omeroweb/webgateway/static/webgateway/js/ome.spwgridview.js b/omeroweb/webgateway/static/webgateway/js/ome.spwgridview.js
index 4a5ad2b51..a0c16c1b3 100644
--- a/omeroweb/webgateway/static/webgateway/js/ome.spwgridview.js
+++ b/omeroweb/webgateway/static/webgateway/js/ome.spwgridview.js
@@ -125,11 +125,11 @@ $(function(){
                 // Add images, positioned by percent...
                 var html = data.map(function(ws){
                     // check if min===max to avoid zero-division error
                     var x = (maxX === minX) ? 0.5 : (ws.position.x.value - minX)/(maxX - minX);
                     var y = (maxY === minY) ? 0.5 : (ws.position.y.value - minY)/(maxY - minY);
-                    return '<img style="left: ' + (x * 100) + '%; top: ' + (y * 100) + '%" title="' + ws.name + '" data-imageId="' + ws.id + '" />';
+                    return '<img style="left: ' + (x * 100) + '%; top: ' + (y * 100) + '%" title="' + ws.name.escapeHTML() + '" data-imageId="' + ws.id + '" />';
                 }, "");
                 $well_birds_eye.append(html.join(""));
             }
         }
     };
diff --git a/omeroweb/webgateway/static/webgateway/js/omero_image.js b/omeroweb/webgateway/static/webgateway/js/omero_image.js
index 277203020..d5adaed0a 100644
--- a/omeroweb/webgateway/static/webgateway/js/omero_image.js
+++ b/omeroweb/webgateway/static/webgateway/js/omero_image.js
@@ -312,14 +312,14 @@
     // This is called on load of viewport in Image viewer, but not preview panel
     window._load_metadata = function(ev, viewport) {
 
         /* Image details */
         var tmp = viewport.getMetadata();
-        $('#wblitz-image-name').html(tmp.imageName);
-        $('#wblitz-image-description-content').html(tmp.imageDescription.replace(/\n/g, '<br />'));
-        $('#wblitz-image-author').html(tmp.imageAuthor);
-        $('#wblitz-image-pub').html(tmp.projectName);
+        $('#wblitz-image-name').html(tmp.imageName.escapeHTML());
+        $('#wblitz-image-description-content').html(tmp.imageDescription.escapeHTML().replace(/\n/g, '<br />'));
+        $('#wblitz-image-author').html(tmp.imageAuthor.escapeHTML());
+        $('#wblitz-image-pub').html(tmp.projectName.escapeHTML());
         $('#wblitz-image-pubid').html(tmp.projectId);
         $('#wblitz-image-timestamp').html(tmp.imageTimestamp);
 
         $("#bulk-annotations").hide();
         $("#bulk-annotations").next().hide();
@@ -390,12 +390,12 @@
         };
         for (i=0; i<channels.length; i++) {
             $('<button id="wblitz-ch'+i+
                 '" class="squared' + (channels[i].active?' pressed':'') +
                 '" style="background-color: #'+ channels[i].color +
-                '" title="' + channels[i].label +
-                '"><div class="lutBackground"></div><div class="btnLabel">'+channels[i].label+'</div></button>')
+                '" title="' + channels[i].label.escapeHTML() +
+                '"><div class="lutBackground"></div><div class="btnLabel">'+channels[i].label.escapeHTML()+'</div></button>')
             .appendTo(box)
             .on('click', doToggle(i));
         }
 
         // disable 'split' view for single channel images.
@@ -540,15 +540,15 @@
                 lbl = lbl.slice(0, 5) + "...";
             }
             tmp.after(template
                 .replace(/\$class/g, btnClass)
                 .replace(/\$col/g, '#' + channels[i].color)
-                .replace(/\$label/g, channels[i].label)
-                .replace(/\$l/g, lbl)
+                .replace(/\$label/g, channels[i].label.escapeHTML())
+                .replace(/\$l/g, lbl.escapeHTML())
                 .replace(/\$idx0/g, i) // Channel Index, 0 based
                 .replace(/\$idx1/g, i+1) // Channel Index, 1 based
-                .replace(/\$cwl/g, channels[i].label) // Wavelength
+                .replace(/\$cwl/g, channels[i].label.escapeHTML()) // Wavelength
                 .replace(/\$cls/g, i/2!=parseInt(i/2, 10)?'even':'odd') // class
             );
 
             $('#wblitz-ch'+(i)+'-cw').append('<td width="10%"><span class="min" title="min: ' + channels[i].window.min + '"><input type="text" id="wblitz-ch' + i + '-cw-start" /></span></td><td><div class="rangeslider" id="wblitz-ch' + i + '-cwslider"></div></td> <td width="10%"><span class="max" title="max: ' + channels[i].window.max + '"><input type="text" id="wblitz-ch' + i + '-cw-end" /></span></td>');
             init_ch_slider(i, channels);
diff --git a/omeroweb/webgateway/templates/webgateway/viewport/omero_image.html b/omeroweb/webgateway/templates/webgateway/viewport/omero_image.html
index 11704dbbf..7036a5ea2 100644
--- a/omeroweb/webgateway/templates/webgateway/viewport/omero_image.html
+++ b/omeroweb/webgateway/templates/webgateway/viewport/omero_image.html
@@ -542,11 +542,11 @@
                   typeof shape.theZ === "undefined"? NOZT : shape.theZ + 1;
               shapesHtml += "<td class='shape_cell'>" + theZ + "</td>";
               var theT =
                   typeof shape.theT === "undefined"? NOZT : shape.theT + 1;
               shapesHtml += "<td class='shape_cell'>" + theT + "</td>";
-              shapesHtml += "<td class='shape_cell shape_text' title=\""+ text +"\"><p>"+ truncate_text(text.escapeHTML()) + "</p></td>";
+              shapesHtml += "<td class='shape_cell shape_text' title=\""+ text.escapeHTML() +"\"><p>"+ truncate_text(text.escapeHTML()) + "</p></td>";
               shapesHtml += "<td class='shape_cell'>" + shapeThumbHtml + "</td>";
               shapesHtml += "<td class='shape_cell'>" + shapeVisibilityHtml + "</td>";
               shapesHtml += "</tr>";
               if (roiIcon == null) roiIcon = shape['type'];
               else if (roiIcon != shape['type']) roiIcon = "";    // mixture of shapes - show no icon
@@ -561,11 +561,11 @@
           roi_html += "<th><img class='expand_arrow' src='{% static "webgateway/img/arrowRight_grey.png" %}' /></th>";
           roi_html += "<th align='left'> &nbsp"+ get_shape_icon_src(roiIcon, 'roi_icon') + " (" + shapes.length + ")</th>"; // first shape (count)
           roi_html += "<th>" + roi['id'] + "</th>";
           roi_html += "<th style='white-space: nowrap'> "+ tRange +" </th>"; // no Z for ROI
           roi_html += "<th style='white-space: nowrap'> "+ zRange +" </th>"; // no T for ROI
-          roi_html += "<th class='shape_text' title=\""+ roiText +"\"><p>"+ truncate_text(roiText) +"</p></th>";
+          roi_html += "<th class='shape_text' title=\""+ roiText.escapeHTML() +"\"><p>"+ truncate_text(roiText).escapeHTML() +"</p></th>";
           roi_html += "<th>"+ roiThumbHtml +"</th>";
           roi_html += "<th>" + roiVisibilityHtml + "</th>";
           roi_html += "</tr></thead>";
 
           var tbody = $(shapesHtml);

Vulnerable code:                     tableRows.push('<tr><td class="link">' + rowData.label + '(id:'+ rowId +')</td>');
            var headerTxt = "<p>Move data owned by " + ownernames.join(", ") + ".</p>" +
                html += g.name + "<hr></div>";
                namesList.push(u.name);
            <input name='<%= obj.split("=")[0] %>' value='<%= obj.split("=")[1] %>' hidden/>
                    <%= exp.FirstName%> <%= exp.LastName %> 
                    ann.textValue = _.escape(ann.textValue);
                            ann.value = _.escape(ann[a]);
                    ann.description = _.escape(ann.description);
                    tag.textValue = _.escape(tag.textValue);
                    tag.description = _.escape(tag.description);
                            summary[tagId] = {'textValue': tag.textValue,
                html += create_tag_html(tag.t, tag.d, owners[tag.o], tag.i,
                                        null, tag.s !== 0);
                                child.t, child.d, owners[child.o], child.i,
                                tag.i);
        $(".tag_selection div").on('click', tag_click).tooltip({
            track: false,
            show: false,
            hide: false,
            items: '[data-content]',
            content: function() {
                return $(this).data('content');
            }
        });
                tagset.text() + "</span> tag set and select it immediately:");
    var encode_html = function(text) {
        return $('<div/>').text(text).html();
    };

            title += "<b>Owner:</b> " + owner + "<br />";
            title += "<b>Linked by:</b> " + link_owner + "<br />";
            title += "<b>Description:</b> " + description + "<br />";
            title += "<b>Tag set:</b> " + tagset + "<br />";
    var create_tag_html = function(text, description, owner, id, parent_id,
        var tagset;
            tagset = all_tags[parent_id].t;
            html += " data-description='" + encode_html(description) + "'";
        var title = create_tag_title(description, owner, tagset);
        // Add content even if it is empty
        // and handle case for no tooltip in the tooltip code
        html += " data-content='" + title.replace(/'/g, "&#39;") + "'";
        html += ">" + encode_html(text) + "</div>";
            owners[me] = owners[me] || my_name;
                text, description, my_name, new_tag_counter,
                items: '[data-content]',
                content: function() {
                    return $(this).data('content');
                }
                        $tr = $('<tr><th><h2>' + topLevelTag + '</h2></th></tr>');
});
<div class="ann_comment_wrapper" data-added-by="<% print (ann.addedBy.join(',')) %>">
        <img src="<%= webindex %>avatar/<%= ann.owner.id %>/"
        alt="<%= ann.owner.firstName %> <%= ann.owner.lastName %>"
        title="<%= ann.owner.firstName %> <%= ann.owner.lastName %>" />
                <%= ann.owner.firstName %> <%= ann.owner.lastName %>
            </strong> 
    
            <img class='removeComment' id="<%= ann.id %>-comment"
                url='<%= webindex %>action/remove/comment/<%= ann.id %>/' 
        <div class='commentText'><%= ann.textValue %></div>
        <b>ID:</b> <%= ann.id %><br />
        <% if (ann.ns) { %><b>Namespace:</b> <%= ann.ns %><br /><% } %>
        <% if (ann.description) { %><b>Description:</b> <%= ann.description %><br /><% } %>
<tr data-added-by="<% print (ann.addedBy.join(',')) %>">
    <th><%= ann.type %></th>
            <div><% print((ann.value + "").slice(0, 20)) %>...</div>
            <div style="display: none"><%= ann.value %></div>
            <div><%= ann.value %></div>
        
            <b>ID:</b> <%= ann.id %><br />
            <% if (ann.ns) { %><b>Namespace:</b> <%= ann.ns %><br /><% } %>
            <% if (ann.description) { %><b>Description:</b> <%= ann.description %><br /><% } %>
            <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %><br />
            <b>Linked to:</b> <%= ann.parent.class %> <%= ann.parent.id %>
<% }) %>
    id="file_ann-<%= ann.id %>"
    data-added-by="<% print (ann.addedBy.join(',')) %>">
        <%= ann.file.name %>
            <span>(<%= ann.file.size %>)</span>
            Can remove File from <b><%= ann.canRemoveCount %>
                        <b><%= link.parent.class %> <%= link.parent.id %></b>
            <b>Annotation ID:</b> <%= ann.id %><br />
            <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %><br />
            <b>Linked by:</b> <%= ann.link.owner.firstName %> <%= ann.link.owner.lastName %><br />
            <b>Description:</b> <%= ann.description %>
                <br/><b>Namespace:</b> <%= ann.ns %>
                <br/><b>Mimetype:</b> <%= ann.file.mimetype %>
            <br/><b>File ID:</b> <%= ann.file.id %>
            href='<%= webindex %>omero_table/<%= ann.file.id %>/'>&nbsp;</a>
            <a class='removeFile action' id="<%= ann.id %>-file" title="Remove File"
            href='<%= webindex %>action/remove/file/<%= ann.id %>/'>&#8211</a>
            <a id="<%= ann.id %>-file" type="image" class="deleteFile action" title="Delete File"
                href="<%= webindex %>action/delete/file/<%= ann.id %>/"> &#215 </a>
<% }) %>
            data-annId="<%= ann.id %>"
            data-added-by="<% print (ann.addedBy.join(',')) %>"
      <tr title="<%= ann.ns %>">
              <%= ann.ns.slice(0, 50) %>
                Added by: <%= ann.owner.firstName %> <%= ann.owner.lastName %>
                    <%= ann.parentNames.length %> Annotations linked to:
                  <%= ann.parentNames ? (ann.parentNames.length + " objects") : ann.link.parent.name %>
                      <b><%= ann.parentNames.length %></b> identical Key-Value annotations:
                        <b><%= ann.link.parent.class.slice(0, ann.link.parent.class.length-1) %>
                            ID:</b> <%= ann.link.parent.id %><br />
                            &nbsp <%= pName %><br />
                        <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %>
                        <br /><b>Linked by:</b> <%= ann.link.owner.firstName %> <%= ann.link.owner.lastName %>
                Add annotations to <%= objCount %> objects
            <td><% print(_.escape(row['0'])) %></td>
            <td><% print(_.escape(row['1'])) %></td>
                            "<b>Owner:</b> " + data.owner + " <br />" +
                            "<b>Linked by:</b> " + data.addedBy + " <br />" +
    data-tag-id="<%= tag.id %>"
    data-added-by="<% print (tag.addedBy.join(',')) %>">
	
        <a class='tooltip tag_inner' href="?show=tag-<%= tag.id %>" target="_top">
            <%= tag.textValue %>
        <span class='removeTag' title="Remove Tag" id="<%= tag.id %>-tag"
                url='<%= webindex %>action/remove/tag/<%= tag.id %>/'>
            Can remove Tag from <b><%= tag.canRemoveCount %> object<% if(tag.canRemoveCount !== 1) {print('s')} %></b>:<br/>
                        <b><%= link.parent.class %> <%= link.parent.id %></b>
                        <% print(_.escape(link.parent.name).slice(0, 28)) %>
                        <% if (link.owner.id !== userId){
            <b>ID:</b> <%= tag.id %><br />
            <b>Owner:</b> <%= tag.owner.firstName %> <%= tag.owner.lastName %><br />
            <b>Linked by:</b> <%= tag.link.owner.firstName %> <%= tag.link.owner.lastName %><br />
            <b>Description:</b> <%= tag.description %>
    
<% }) %>
            id="image_icon-<%= img.id %>"
            data-fileset="<%= img.data.obj.filesetId %>"
            data-id="<%= img.id %>"
            <% if(img.shareId) { %> data-share="<%= img.shareId %>" <% }%>
                <a href="<%= webindex %><% if(img.shareId) {print( img.shareId + '/')} %>img_detail/<%= img.id %>/<% if (dataset) print('?dataset=' + dataset) %>" >
                 <%= img.name %>
                <span class="hidden_sort_text"> <%= img.name %> </span>
            <div class="date" valign="middle"><%= img.date %></div>
            <div class="sizeX" valign="middle"><%= img.data.obj.sizeX %></div>
            <div class="sizeY" valign="middle"><%= img.data.obj.sizeY %></div>
            <div class="sizeZ" valign="middle"><%= img.data.obj.sizeZ %></div>
<% } %>
                return "<option value='" + t.id + "'>" + t.text + "</option>";
                var tagText = usedTags[tagId].textValue;
                            html += '<li ' + cls + ' title="' + w.name + '" data-imageId="' + w.id + '">';
                    $("#targetMessage").html("File: " + chosen_file + " not valid.<br/>File must be Python script (.py)");
                "owner": r["owner"],
            case '"': return '\"';
                                    var text_string = formatShapeText(shape['textValue'].escapeHTML())
                                    txt = paper.text(textx, newY, formatShapeText(shape['textValue'].escapeHTML()))
                    return '<img style="left: ' + (x * 100) + '%; top: ' + (y * 100) + '%" title="' + ws.name + '" data-imageId="' + ws.id + '" />';
        $('#wblitz-image-name').html(tmp.imageName);
        $('#wblitz-image-description-content').html(tmp.imageDescription.replace(/\n/g, '<br />'));
        $('#wblitz-image-author').html(tmp.imageAuthor);
        $('#wblitz-image-pub').html(tmp.projectName);
                '" title="' + channels[i].label +
                '"><div class="lutBackground"></div><div class="btnLabel">'+channels[i].label+'</div></button>')
                .replace(/\$label/g, channels[i].label)
                .replace(/\$l/g, lbl)
                .replace(/\$cwl/g, channels[i].label) // Wavelength
              shapesHtml += "<td class='shape_cell shape_text' title=\""+ text +"\"><p>"+ truncate_text(text.escapeHTML()) + "</p></td>";
          roi_html += "<th class='shape_text' title=\""+ roiText +"\"><p>"+ truncate_text(roiText) +"</p></th>";
Fixed code: 5.11.0 (October 2021)
---------------------

- Security vulnerability fixes for
  [2021-SV3](https://www.openmicroscopy.org/security/advisories/2021-SV3)

                    tableRows.push('<tr><td class="link">' + rowData.label.escapeHTML() + '(id:'+ rowId +')</td>');
                        slice.label = slice.label.escapeHTML();
            var headerTxt = "<p>Move data owned by " + ownernames.join(", ").escapeHTML() + ".</p>" +
                html += g.name.escapeHTML() + "<hr></div>";
                namesList.push(u.name.escapeHTML());
            <input name='<%- obj.split("=")[0] %>' value='<%- obj.split("=")[1] %>' hidden/>
                    <%- exp.FirstName%> <%- exp.LastName %>
                            ann.value = ann[a];
                    tag.textValue = tag.textValue;
                    tag.description = tag.description;
                            summary[tagId] = {'textValue': _.escape(tag.textValue),
                html += create_tag_html(
                    tag.t, tag.d, tag.i, null, tag.s !== 0
                );
                                child.t, child.d, child.i, tag.i
                            );
        $(".tag_selection div").on('click', tag_click);
                tagset.text().escapeHTML() + "</span> tag set and select it immediately:");
            items: '[data-id]',  // Just needs an attribute that exists
            title += "<b>Owner:</b> " + owner.escapeHTML() + "<br />";
            title += "<b>Linked by:</b> " + link_owner.escapeHTML() + "<br />";
            title += "<b>Description:</b> " + description.escapeHTML() + "<br />";
            title += "<b>Tag set:</b> " + tagset.escapeHTML() + "<br />";
    var create_tag_html = function(text, description, id, parent_id,
            html += " data-description='" + description.escapeHTML() + "'";
        html += ">" + text.escapeHTML() + "</div>";
            owners[me] = owners[me] || _.unescape(my_name);
                text, description, new_tag_counter,
            var title = create_tag_title(
                description, _.unescape(my_name),
                tagset_id? all_tags[tagset_id].t : null
            );
                items: '[data-id]',  // Just needs an attribute that exists
                content: title
                        $tr = $('<tr><th><h2>' + topLevelTag.escapeHTML() + '</h2></th></tr>');
});
<div class="ann_comment_wrapper" data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
        <img src="<%= webindex %>avatar/<%- ann.owner.id %>/"
        alt="<%- ann.owner.firstName %> <%- ann.owner.lastName %>"
        title="<%- ann.owner.firstName %> <%- ann.owner.lastName %>" />
                <%- ann.owner.firstName %> <%- ann.owner.lastName %>
            </strong>

            <img class='removeComment' id="<%- ann.id %>-comment"
                url='<%= webindex %>action/remove/comment/<%- ann.id %>/'
        <div class='commentText'><%- ann.textValue %></div>
        <b>ID:</b> <%- ann.id %><br />
        <% if (ann.ns) { %><b>Namespace:</b> <%- ann.ns %><br /><% } %>
        <% if (ann.description) { %><b>Description:</b> <%- ann.description %><br /><% } %>
<tr data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
    <th><%- ann.type %></th>
            <div><% print(_.escape((ann.value + "").slice(0, 20))) %>...</div>
            <div style="display: none"><%- ann.value %></div>
            <div><%- ann.value %></div>

            <b>ID:</b> <%- ann.id %><br />
            <% if (ann.ns) { %><b>Namespace:</b> <%- ann.ns %><br /><% } %>
            <% if (ann.description) { %><b>Description:</b> <%- ann.description %><br /><% } %>
            <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %><br />
            <b>Linked to:</b> <%- ann.parent.class %> <%- ann.parent.id %>
<% }) %>
    id="file_ann-<%- ann.id %>"
    data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
        <%- ann.file.name %>
            <span>(<%- ann.file.size %>)</span>
            Can remove File from <b><%- ann.canRemoveCount %>
                        <b><%- link.parent.class %> <%- link.parent.id %></b>
            <b>Annotation ID:</b> <%- ann.id %><br />
            <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %><br />
            <b>Linked by:</b> <%- ann.link.owner.firstName %> <%- ann.link.owner.lastName %><br />
            <b>Description:</b> <%- ann.description %>
                <br/><b>Namespace:</b> <%- ann.ns %>
                <br/><b>Mimetype:</b> <%- ann.file.mimetype %>
            <br/><b>File ID:</b> <%- ann.file.id %>
            href='<%= webindex %>omero_table/<%- ann.file.id %>/'>&nbsp;</a>
            <a class='removeFile action' id="<%- ann.id %>-file" title="Remove File"
            href='<%= webindex %>action/remove/file/<%- ann.id %>/'>&#8211</a>
            <a id="<%- ann.id %>-file" type="image" class="deleteFile action" title="Delete File"
                href="<%= webindex %>action/delete/file/<%- ann.id %>/"> &#215 </a>
<% }) %>
            data-annId="<%- ann.id %>"
            data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>"
      <tr title="<%- ann.ns %>">
              <%- ann.ns.slice(0, 50) %>
                Added by: <%- ann.owner.firstName %> <%- ann.owner.lastName %>
                    <%- ann.parentNames.length %> Annotations linked to:
                  <%- ann.parentNames ? (ann.parentNames.length + " objects") : ann.link.parent.name %>
                      <b><%- ann.parentNames.length %></b> identical Key-Value annotations:
                        <b><%- ann.link.parent.class.slice(0, ann.link.parent.class.length-1) %>
                            ID:</b> <%- ann.link.parent.id %><br />
                            &nbsp <%- pName %><br />
                        <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %>
                        <br /><b>Linked by:</b> <%- ann.link.owner.firstName %> <%- ann.link.owner.lastName %>
                Add annotations to <%- objCount %> objects
            <td><%- row['0'] %></td>
            <td><%- row['1'] %></td>
                            "<b>Owner:</b> " + data.owner.escapeHTML() + " <br />" +
                            "<b>Linked by:</b> " + data.addedBy.escapeHTML() + " <br />" +
    data-tag-id="<%- tag.id %>"
    data-added-by="<% print(_.escape(tag.addedBy.join(','))) %>">

        <a class='tooltip tag_inner' href="?show=tag-<%- tag.id %>" target="_top">
            <%- tag.textValue %>
        <span class='removeTag' title="Remove Tag" id="<%- tag.id %>-tag"
                url='<%= webindex %>action/remove/tag/<%- tag.id %>/'>
            Can remove Tag from <b><%- tag.canRemoveCount %> object<% if(tag.canRemoveCount !== 1) {print('s')} %></b>:<br/>
                        <b><%- link.parent.class %> <%- link.parent.id %></b>
                        <%- link.parent.name.slice(0, 28) %>
                        <% if (link.owner.id !== userId) {
            <b>ID:</b> <%- tag.id %><br />
            <b>Owner:</b> <%- tag.owner.firstName %> <%- tag.owner.lastName %><br />
            <b>Linked by:</b> <%- tag.link.owner.firstName %> <%- tag.link.owner.lastName %><br />
            <b>Description:</b> <%- tag.description %>

<% }) %>
            id="image_icon-<%- img.id %>"
            data-fileset="<%- img.data.obj.filesetId %>"
            data-id="<%- img.id %>"
            <% if(img.shareId) { %> data-share="<%- img.shareId %>" <% }%>
                <a href="<%= webindex %><% if(img.shareId) {print( img.shareId + '/')} %>img_detail/<%- img.id %>/<% if (dataset) print('?dataset=' + dataset) %>" >
                 <%- img.name %>
                <span class="hidden_sort_text"> <%- img.name %> </span>
            <div class="date" valign="middle"><%- img.date %></div>
            <div class="sizeX" valign="middle"><%- img.data.obj.sizeX %></div>
            <div class="sizeY" valign="middle"><%- img.data.obj.sizeY %></div>
            <div class="sizeZ" valign="middle"><%- img.data.obj.sizeZ %></div>
<% } %>
                return "<option value='" + t.id + "'>" + t.text.escapeHTML() + "</option>";
                var tagText = usedTags[tagId].textValue.escapeHTML();
                            html += '<li ' + cls + ' title="' + w.name.escapeHTML() + '" data-imageId="' + w.id + '">';
                    $("#targetMessage").html("File: " + chosen_file.escapeHTML() + " not valid.<br/>File must be Python script (.py)");
from django.utils.html import escape
                "owner": escape(r["owner"]),  # May be used unsafe later
            case "`": return '&#x60;';
            case "'": return '&#x27;';
            case '"': return '&quot;';
                                    var text_string = formatShapeText(shape['textValue'])
                                    txt = paper.text(textx, newY, formatShapeText(shape['textValue']))
                    return '<img style="left: ' + (x * 100) + '%; top: ' + (y * 100) + '%" title="' + ws.name.escapeHTML() + '" data-imageId="' + ws.id + '" />';
        $('#wblitz-image-name').html(tmp.imageName.escapeHTML());
        $('#wblitz-image-description-content').html(tmp.imageDescription.escapeHTML().replace(/\n/g, '<br />'));
        $('#wblitz-image-author').html(tmp.imageAuthor.escapeHTML());
        $('#wblitz-image-pub').html(tmp.projectName.escapeHTML());
                '" title="' + channels[i].label.escapeHTML() +
                '"><div class="lutBackground"></div><div class="btnLabel">'+channels[i].label.escapeHTML()+'</div></button>')
                .replace(/\$label/g, channels[i].label.escapeHTML())
                .replace(/\$l/g, lbl.escapeHTML())
                .replace(/\$cwl/g, channels[i].label.escapeHTML()) // Wavelength
              shapesHtml += "<td class='shape_cell shape_text' title=\""+ text.escapeHTML() +"\"><p>"+ truncate_text(text.escapeHTML()) + "</p></td>";
          roi_html += "<th class='shape_text' title=\""+ roiText.escapeHTML() +"\"><p>"+ truncate_text(roiText).escapeHTML() +"</p></th>";