Bugfix Issue 1432 rolesView DeleteCell Onclick Breakout Stored XSS - sebiboga/testlink-upgraded GitHub Wiki
Issue 1432 β rolesView.html: delete-cell onclick attribute breakout β stored-XSS via role description
Issue: #1432
Branch: fix/issue-1432
Status: VERIFIED-FIXED (2026-09-12)
The Role Management grid (gui/templates/usermanagement/rolesView.html) rendered the
delete action as an inline attribute handler that interpolated the role name raw:
onclick="confirmDelete(4, '<role name with quotes escaped>')"Only single quotes were escaped (r.name.replace(/'/g, "\\'") at rolesView.html:203).
A role whose name/description contains a double quote (e.g.
"><img src=x onerror=location.hash=\RVXSS`>x) closes the onclickattribute; the trailing><img ...>is then parsed by the HTML parser as a **real element** inside the actions
, and its onerror` fires when the image fails to load β on first
render, no click needed.
Note the Role Management screen is a special case of the #1406/#1430 family: the roles
table has no name column (SHOW COLUMNS FROM roles β id/description/notes),
so the displayed role "name" is the roles.description column (getDisplayName());
the description is both the label and the XSS vector.


- Plant a payload into a role description:
UPDATE roles SET description='"><img src=x onerror=location.hash=RVXSS>x' WHERE id=4;(or create the role via the UI β the BFF stores it verbatim). - Log in admin/admin at http://localhost:8082.
- Open
gui/templates/usermanagement/rolesView.html?tproject_id=0&tplan_id=0. - The grid renders:
document.querySelectorAll('img')returns["<img src=\"x\" onerror=\"location.hash=RVXSS\">"]andlocation.hashbecomes#RVXSSβ measured live on this fixture (page URL carried the#RVXSSfragment).
-
api/roles/index.phproleToJSON()returns'name' => $r->getDisplayName(), i.e. theroles.descriptionvalue, served verbatim. -
gui/templates/usermanagement/rolesView.html:203:'<i class="fa fa-trash action-btn danger" title="Delete" onclick="confirmDelete(' + r.id + ', \'' + r.name.replace(/'/g, "\\'") + '\')"></i>'β only'is neutralized;",<,>survive, so a"in the name breaks the attribute and<img onerror=β¦>becomes live markup (= stored-XSS sink, executes at grid render). - Why it breaks NOW (regression source): the #1406 fix
(
fix/issue-1406-usersview-xss, ESLented the role name column cell here atrolesView.html:208) but left the delete-cell attribute interpolation raw. The same screen's siblingusersView.htmlwas fixed by moving the name out of the inline attribute entirely (deleteUser(id)resolves the login fromallItems,usersView.html:841-847). - Blast radius: only
rolesView.html:203on this screen; other usermanagement screens pass only numeric ids inonclick=handlers (verified by grep). Admin-gated (mgt_users + ability to plant a role description) β priority minor, same doctrine as #1406/#1430.
- Added a module-level
allItemsarray;loadRoles()now storesr.items. - Delete cell passes only the id:
onclick="confirmDelete(<id>)". -
confirmDelete(id, name)βconfirmDelete(id); the role name is resolved fromallItemsby id at click time and rendered through the existingesc()into the modal message β text context only, no attribute context, no breakout possible.
Method chosen on purpose: identical to the #1406/#1430 family so the whole
usermanagement area converges on one safe pattern. Alternative rejected: escaping
"/</> into the attribute would still leave a JS-string-in-attribute structure
(fragile; the id-only pattern removes the class of bug, not just this instance).
Regression suite Regression β Issue #1432 in tmp/TLU_Test_Cases.md (10 cases), all
PASS:
- 1432.1 pre-fix control β injected
<img>present,location.hash=#RVXSS; - 1432.2 post-fix grid β
img_count0, hash empty, payload shown as escaped text; - 1432.3 delete icon =
onclick="confirmDelete(4)"(id only); - 1432.4 confirm modal on payload role β entity-escaped name, no element created;
- 1432.5 cancel flow; 1432.6 full createβlistβdelete of a payload-named role via UI;
- 1432.7 system roles keep no trash icon; 1432.8 edit flow regression;
- 1432.9 Event Viewer clean (audit only, no ERROR/WARNING);
- 1432.10
node --checkon extracted inline script + grep sweep. Result: 10/10 PASS. JS syntax check green; no i18n bundles touched (no user-facing strings added/changed β fix is markup/JS only).
-
gui/templates/usermanagement/rolesView.html(+8/β2) -
docs/screenshots/issue-1432-roles-xss-repro.png(pre-fix) /issue-1432-roles-postfix-grid.png -
tmp/TLU_Test_Cases.md(suite commit)