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 `
`;