6c1b7e8cb87b181f3220efc0ed86dbb1ceea713e braney Thu Apr 16 10:36:16 2026 -0700 hgTracks: harden Change Track Color dialog against XSS and invalid input, refs #20460 In response to findings from the nightly code review: inject hub-controlled shortLabel via .text() instead of HTML concatenation, validate the free-text color input with a hex regex before writing to the cart, and fold the duplicated Apply/Ok handlers into a shared closure. diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index aa2e4624c7f..20c825d4e0e 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -2761,87 +2761,91 @@ // seen in FireFox 3.X, which occurred b/c FF doesn't actually fetch the image until // the menu is being shown. return ""; }, showColorPicker: function (trackName) { // Show a small dialog with a spectrum color picker for changing track color var rec = hgTracks.trackDb[trackName]; if (!rec || !rec.defaultColor) return; var currentColor = (rec.colorOverrideOn && rec.colorOverride) ? rec.colorOverride : rec.defaultColor; var dialogId = "trackColorDialog"; $("#" + dialogId).remove(); - $("body").append( - "
" + - "

Pick a new color for " + rec.shortLabel + ":

" + - "" + + // Build with static template + data injected via .text()/.val()/.prop() so that + // hub-provided shortLabel cannot inject HTML. + var $dlg = $("
").attr("id", dialogId).html( + "

Pick a new color for :

" + + "" + " " + - "

" + - "
"); + "

"); + $dlg.find("p b").text(rec.shortLabel); + $dlg.find("#trackColorText").val(currentColor); + $dlg.find("#trackColorOn").prop("checked", !!rec.colorOverrideOn); + $("body").append($dlg); + var hexColorRe = /^#[0-9a-fA-F]{6}$/; $("#trackColorPicker").spectrum({ color: currentColor, showPalette: true, showSelectionPalette: true, showInitial: true, showInput: true, preferredFormat: "hex", localStorageKey: "genomebrowser", hideAfterPaletteSelect: true, change: function(color) { $("#trackColorText").val(color.toHexString()); $("#trackColorOn").prop("checked", true); } }); $("#trackColorText").on("change", function() { - $("#trackColorPicker").spectrum("set", $(this).val()); + var val = $(this).val(); + if (!hexColorRe.test(val)) + return; + $("#trackColorPicker").spectrum("set", val); $("#trackColorOn").prop("checked", true); }); - $("#" + dialogId).dialog({ - modal: true, - title: "Change Track Color", - closeOnEscape: true, - resizable: false, - minWidth: 400, - buttons: { - "Apply": function() { + var applyColor = function() { var color = $("#trackColorText").val(); + if (!hexColorRe.test(color)) { + warn("Invalid color '" + color + "'. Expected hex format like #1a2b3c."); + return false; + } var isOn = $("#trackColorOn").is(":checked") ? "1" : "0"; rec.colorOverride = color; rec.colorOverrideOn = (isOn === "1"); cart.setVars( [trackName + ".colorOverride", trackName + ".colorOverrideOn"], [color, isOn], null, false); imageV2.requestImgUpdate(trackName, trackName + ".colorOverride=" + encodeURIComponent(color) + "&" + trackName + ".colorOverrideOn=" + isOn); - }, + return true; + }; + $("#" + dialogId).dialog({ + modal: true, + title: "Change Track Color", + closeOnEscape: true, + resizable: false, + minWidth: 400, + buttons: { + "Apply": function() { applyColor(); }, "Ok": function() { - var color = $("#trackColorText").val(); - var isOn = $("#trackColorOn").is(":checked") ? "1" : "0"; - rec.colorOverride = color; - rec.colorOverrideOn = (isOn === "1"); - cart.setVars( - [trackName + ".colorOverride", trackName + ".colorOverrideOn"], - [color, isOn], null, false); - imageV2.requestImgUpdate(trackName, - trackName + ".colorOverride=" + encodeURIComponent(color) + - "&" + trackName + ".colorOverrideOn=" + isOn); + if (applyColor()) $(this).dialog("close"); } }, close: function() { $("#trackColorPicker").spectrum("destroy"); $(this).remove(); } }); }, // CGIs now use HTML tags, e.g. "Transcript: ENST00000297261.7
Strand:" mouseOverToLabel: function(title) { if (title.search(/Transcript: ?<[/]b>/) !== -1) { title = title.split("
")[0].split("
")[1];