Bug 42471: (follow-up) prevent XSS in suggestion status

The custom/authorized-value status branch concatenated row.status
directly into a class="..." attribute string. escape_str() only
escapes &, <, > (not quotes), so a status code containing a double
quote could still break out of the attribute.

Build the status <span> via DOM APIs instead, the same way the
reason field was fixed: fixed statuses get a hardcoded class, and
the free-form status code goes into a data-status attribute via
dataset, which the DOM escapes safely regardless of content.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 7ec92d492576215296bbee42669bcf6ed81c7626)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Martin Renvoize 2026-07-07 15:45:45 +01:00 committed by Lucas Gass
parent b28831147d
commit e99c6d3d62
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
1 changed files with 28 additions and 9 deletions

View File

@ -1431,15 +1431,34 @@
searchable: true,
orderable: true,
render: function (data, type, row, meta) {
let node = "";
if (row.status === "ASKED") node += '<span class="status asked">%s</span>'.format(escape_str(_("Pending")));
else if (row.status === "ACCEPTED") node += '<span class="status accepted">%s</span>'.format(escape_str(_("Accepted")));
else if (row.status === "ORDERED") node += '<span class="status ordered">%s</span>'.format(escape_str(_("Ordered")));
else if (row.status === "REJECTED") node += '<span class="status rejected">%s</span>'.format(escape_str(_("Rejected")));
else if (row.status === "CHECKED") node += '<span class="status checked">%s</span>'.format(escape_str(_("Checked")));
else if (row.status === "AVAILABLE") node += '<span class="status available">%s</span>'.format(escape_str(_("Available")));
else if (row._strings.status.str) node += '<span class="status ' + escape_str(row.status) + '">%s</span>'.format(escape_str(row._strings.status.str));
else node += '<span class="status unknown">%s</span>'.format(escape_str(_("Status unknown")));
const status_el = document.createElement("span");
status_el.classList.add("status");
if (row.status === "ASKED") {
status_el.classList.add("asked");
status_el.textContent = _("Pending");
} else if (row.status === "ACCEPTED") {
status_el.classList.add("accepted");
status_el.textContent = _("Accepted");
} else if (row.status === "ORDERED") {
status_el.classList.add("ordered");
status_el.textContent = _("Ordered");
} else if (row.status === "REJECTED") {
status_el.classList.add("rejected");
status_el.textContent = _("Rejected");
} else if (row.status === "CHECKED") {
status_el.classList.add("checked");
status_el.textContent = _("Checked");
} else if (row.status === "AVAILABLE") {
status_el.classList.add("available");
status_el.textContent = _("Available");
} else if (row._strings.status.str) {
status_el.dataset.status = row.status;
status_el.textContent = row._strings.status.str;
} else {
status_el.classList.add("unknown");
status_el.textContent = _("Status unknown");
}
let node = status_el.outerHTML;
if (row.reason) {
const reason_el = document.createElement("div");
reason_el.classList.add("reason");