From 3eb928bc72d0fa2ea94198839dcc3c12a003c776 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 3 Jul 2026 09:56:36 -0400 Subject: [PATCH] Escape HTML in username, VM display name and diagnostics output --- ui/src/config/section/compute.js | 3 ++- ui/src/config/section/infra/routers.js | 3 ++- ui/src/config/section/infra/systemVms.js | 3 ++- ui/src/config/section/network.js | 3 ++- ui/src/config/section/user.js | 3 ++- ui/src/utils/util.js | 9 +++++++++ 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 3b483c19b74..9940c118efe 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -18,6 +18,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' import { isZoneCreated } from '@/utils/zone' +import { escapeHtml } from '@/utils/util' export default { name: 'compute', @@ -377,7 +378,7 @@ export default { show: (record) => { return ['Stopped'].includes(record.state) && record.passwordenabled }, response: (result) => { return { - message: result.virtualmachine && result.virtualmachine.password ? `The password of VM ${result.virtualmachine.displayname} is ${result.virtualmachine.password}` : null, + message: result.virtualmachine && result.virtualmachine.password ? `The password of VM ${escapeHtml(result.virtualmachine.displayname)} is ${result.virtualmachine.password}` : null, copybuttontext: result.virtualmachine.password ? 'label.copy.password' : null, copytext: result.virtualmachine.password ? result.virtualmachine.password : null } diff --git a/ui/src/config/section/infra/routers.js b/ui/src/config/section/infra/routers.js index e52bcd7a829..6079f4a44f2 100644 --- a/ui/src/config/section/infra/routers.js +++ b/ui/src/config/section/infra/routers.js @@ -17,6 +17,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' +import { escapeHtml } from '@/utils/util' export default { name: 'router', @@ -226,7 +227,7 @@ export default { options: ['ping', 'ping6', 'traceroute', 'traceroute6', 'arping'] } }, - response: (result) => { return result && result.diagnostics ? `Output:
${result.diagnostics.stdout}
Error: ${result.diagnostics.stderr}
Exit Code: ${result.diagnostics.exitcode}` : 'Invalid response' } + response: (result) => { return result && result.diagnostics ? `Output:
${escapeHtml(result.diagnostics.stdout)}
Error: ${escapeHtml(result.diagnostics.stderr)}
Exit Code: ${result.diagnostics.exitcode}` : 'Invalid response' } }, { api: 'getDiagnosticsData', diff --git a/ui/src/config/section/infra/systemVms.js b/ui/src/config/section/infra/systemVms.js index 6e135ccdd36..9a9910a6610 100644 --- a/ui/src/config/section/infra/systemVms.js +++ b/ui/src/config/section/infra/systemVms.js @@ -17,6 +17,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' +import { escapeHtml } from '@/utils/util' export default { name: 'systemvm', @@ -145,7 +146,7 @@ export default { options: ['ping', 'traceroute', 'arping'] } }, - response: (result) => { return result && result.diagnostics ? `Output:
${result.diagnostics.stdout}
Error: ${result.diagnostics.stderr}
Exit Code: ${result.diagnostics.exitcode}` : 'Invalid response' } + response: (result) => { return result && result.diagnostics ? `Output:
${escapeHtml(result.diagnostics.stdout)}
Error: ${escapeHtml(result.diagnostics.stderr)}
Exit Code: ${result.diagnostics.exitcode}` : 'Invalid response' } }, { api: 'getDiagnosticsData', diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index fbc044ff500..408df8147cd 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -20,6 +20,7 @@ import store from '@/store' import tungsten from '@/assets/icons/tungsten.svg?inline' import { isAdmin } from '@/role' import { isZoneCreated } from '@/utils/zone' +import { escapeHtml } from '@/utils/util' export default { name: 'network', @@ -681,7 +682,7 @@ export default { show: (record) => { return ['Stopped'].includes(record.state) && record.passwordenabled }, response: (result) => { return { - message: result.virtualmachine && result.virtualmachine.password ? `The password of VM ${result.virtualmachine.displayname} is ${result.virtualmachine.password}` : null, + message: result.virtualmachine && result.virtualmachine.password ? `The password of VM ${escapeHtml(result.virtualmachine.displayname)} is ${result.virtualmachine.password}` : null, copybuttontext: result.virtualmachine.password ? 'label.copy.password' : null, copytext: result.virtualmachine.password ? result.virtualmachine.password : null } diff --git a/ui/src/config/section/user.js b/ui/src/config/section/user.js index d26901aecca..4c6f198a990 100644 --- a/ui/src/config/section/user.js +++ b/ui/src/config/section/user.js @@ -17,6 +17,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' +import { escapeHtml } from '@/utils/util' export default { name: 'accountuser', @@ -111,7 +112,7 @@ export default { api: 'lockUser', icon: 'LockOutlined', label: 'label.action.lock.user', - message: (record) => ['message.lock.user', { user: record.username }], + message: (record) => ['message.lock.user', { user: escapeHtml(record.username) }], successMessage: (record) => ['message.lock.user.success', { user: record.username }], dataView: true, popup: true, diff --git a/ui/src/utils/util.js b/ui/src/utils/util.js index 8773f07446e..d64f3a13764 100644 --- a/ui/src/utils/util.js +++ b/ui/src/utils/util.js @@ -64,6 +64,15 @@ export function removeLoadingAnimate (id = '', timeout = 1500) { }, timeout) } +export function escapeHtml (value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + export function sanitizeReverse (value) { return value .replace(/&/g, '&')