diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js index 8ed92e6b948de10afe88243331183a4217a545bb..656c851aa3d3666c55682b106243a11f53c08f98 100644 --- a/app/assets/javascripts/users_select/index.js +++ b/app/assets/javascripts/users_select/index.js @@ -210,7 +210,7 @@ function UsersSelect(currentUser, els, options = {}) { return axios.put(issueURL, data).then(({ data }) => { let user = {}; - let tooltipTitle = user.name; + let tooltipTitle; $dropdown.trigger('loaded.gl.dropdown'); $loading.addClass('gl-display-none'); if (data.assignee) { @@ -806,7 +806,9 @@ UsersSelect.prototype.renderRow = function ( ${ username - ? `${username}` + ? `${escape( + username, + )}` : '' } ${this.renderApprovalRules(elsClassName, user.applicable_approval_rules)} diff --git a/spec/frontend/users_select/index_spec.js b/spec/frontend/users_select/index_spec.js index 0d2aae7894499319fbaa5851b132ba94340fd314..3757e63c4f96274a6a40d175d0fe51865d602721 100644 --- a/spec/frontend/users_select/index_spec.js +++ b/spec/frontend/users_select/index_spec.js @@ -108,4 +108,39 @@ describe('~/users_select/index', () => { }); }); }); + + describe('XSS', () => { + const escaped = '><script>alert(1)</script>'; + const issuableType = 'merge_request'; + const user = { + availability: 'not_set', + can_merge: true, + name: 'name', + }; + const selected = true; + const username = 'username'; + const img = ''; + const elsClassName = 'elsclass'; + + it.each` + prop | val | element + ${'username'} | ${'>'} | ${'.dropdown-menu-user-username'} + ${'name'} | ${'>'} | ${'.dropdown-menu-user-full-name'} + `('properly escapes the $prop $val', ({ prop, val, element }) => { + const u = prop === 'username' ? val : username; + const n = prop === 'name' ? val : user.name; + const row = UsersSelect.prototype.renderRow( + issuableType, + { ...user, name: n }, + selected, + u, + img, + elsClassName, + ); + const fragment = document.createRange().createContextualFragment(row); + const output = fragment.querySelector(element).innerHTML.trim(); + + expect(output).toBe(escaped); + }); + }); });