From 8b02229b528ae2f3e1980774ddafc17cd893deb3 Mon Sep 17 00:00:00 2001 From: Vishal Sindham <37668661+vishalsindham@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:36:53 +0530 Subject: [PATCH] fix(Table_Multiselect_Field): Mitigate reflected XSS due to search (#33443) * fix(Table_Multiselect Field): Mitigate reflected XSS due to search feature of table multiselect * fix(Table_Multiselect_Field): Making changes where required * fix(Table_Multiselect_Field): Making changes where required --- frappe/public/js/frappe/form/controls/link.js | 8 +++- .../frappe/form/controls/table_multiselect.js | 46 +++++++++++-------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 41b89f1453..6e5f0d84e2 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -224,7 +224,8 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat d.label = d.value; } - let _label = me.get_translated(d.label); + // Sanitize label and description before using them to build HTML + let _label = frappe.utils.escape_html(me.get_translated(d.label)); let html = d.html || "" + _label + ""; if ( d.description && @@ -232,7 +233,10 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat // because it will not visible otherwise (me.is_title_link() || d.value !== d.description) ) { - html += '
' + __(d.description) + ""; + html += + '
' + + __(frappe.utils.escape_html(d.description)) + + ""; } return $(`
`) .on("click", (event) => { diff --git a/frappe/public/js/frappe/form/controls/table_multiselect.js b/frappe/public/js/frappe/form/controls/table_multiselect.js index da55378d5e..9a962f82b4 100644 --- a/frappe/public/js/frappe/form/controls/table_multiselect.js +++ b/frappe/public/js/frappe/form/controls/table_multiselect.js @@ -83,26 +83,33 @@ frappe.ui.form.ControlTableMultiSelect = class ControlTableMultiSelect extends ( const link_field = this.get_link_field(); if (value) { - if (this.frm) { - const new_row = frappe.model.add_child( - this.frm.doc, - this.df.options, - this.df.fieldname - ); - new_row[link_field.fieldname] = value; - this.rows = this.frm.doc[this.df.fieldname]; + // Trim the value to remove spaces or only if space is only input + value = value.trim(); - this.frm.script_manager.trigger( - `${this.df.fieldname}_add`, - this.df.options, - new_row.name - ); - } else { - this.rows.push({ - [link_field.fieldname]: value, - }); + // Only create a pill if the value is a real item from the autocomplete list. + // This prevents creating a pill from raw text when the user clicks away. + if (this.awesomplete.get_item(value)) { + if (this.frm) { + const new_row = frappe.model.add_child( + this.frm.doc, + this.df.options, + this.df.fieldname + ); + new_row[link_field.fieldname] = value; + this.rows = this.frm.doc[this.df.fieldname]; + + this.frm.script_manager.trigger( + `${this.df.fieldname}_add`, + this.df.options, + new_row.name + ); + } else { + this.rows.push({ + [link_field.fieldname]: value, + }); + } + frappe.utils.add_link_title(link_field.options, value, label); } - frappe.utils.add_link_title(link_field.options, value, label); } this._rows_list = this.rows.map((row) => row[link_field.fieldname]); return this.rows; @@ -165,9 +172,10 @@ frappe.ui.form.ControlTableMultiSelect = class ControlTableMultiSelect extends ( const link_field = this.get_link_field(); const encoded_value = encodeURIComponent(value); const pill_name = frappe.utils.get_link_title(link_field.options, value) || value; + return ` `;