From dc02c63058b15927c113dc70e16d0095850e1134 Mon Sep 17 00:00:00 2001 From: Alex Sorafumo Date: Wed, 30 Sep 2026 11:02:13 +1000 Subject: [PATCH 01/10] fix(ui): escape user names in confirm modal and editor HTML Confirm modal content is rendered with innerHTML, so resource names from the API could inject markup. Add an escapeHtml helper and use it where callers build HTML strings. Escape uploaded file names before SunEditor insertHTML, and destroy the editor on re-init and destroy. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/app/admin/build-list.component.ts | 5 +++-- src/app/admin/edge.component.ts | 4 ++-- src/app/admin/extensions.component.ts | 3 ++- src/app/admin/resource-imports.component.ts | 3 ++- .../admin/signage-ai/signage-ai.component.ts | 5 +++-- src/app/admin/staff-api.component.ts | 3 ++- src/app/admin/storage/storage.component.ts | 5 +++-- src/app/admin/upload-library.component.ts | 3 ++- src/app/common/general.ts | 17 ++++++++++++++ src/app/common/item.service.ts | 5 +++-- src/app/domains/domain-state.service.ts | 3 ++- src/app/groups/group-state.service.ts | 5 +++-- src/app/systems/system-state.service.ts | 6 ++--- src/app/triggers/trigger-state.service.ts | 3 ++- .../rich-text-input.component.ts | 22 +++++++++++++------ src/app/ui/metadata-display.component.ts | 3 ++- src/app/users/users-state.service.ts | 7 ++++-- src/app/zones/zones-state.service.ts | 6 ++--- src/tests/common/general.spec.ts | 14 ++++++++++++ src/tests/users/user-lifecycle.spec.ts | 5 ++++- 20 files changed, 92 insertions(+), 35 deletions(-) diff --git a/src/app/admin/build-list.component.ts b/src/app/admin/build-list.component.ts index da1942124..0d311b8cb 100644 --- a/src/app/admin/build-list.component.ts +++ b/src/app/admin/build-list.component.ts @@ -5,6 +5,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { del, get } from '@placeos/ts-client'; import { toQueryString } from '../common/api'; +import { escapeHtml } from '../common/general'; import { i18n } from '../common/locale.service'; import { notifyError, notifySuccess } from '../common/notifications'; import { openConfirmModal } from '../overlays/confirm-modal.component'; @@ -159,8 +160,8 @@ export class PlaceBuildListComponent implements OnInit { { title: i18n('ADMIN.BUILD_LIST_REMOVE'), content: i18n('ADMIN.BUILD_LIST_REMOVE_MSG', { - driver: i.driver, - repo: i.repo, + driver: escapeHtml(i.driver), + repo: escapeHtml(i.repo), }), icon: { type: 'icon', content: 'delete' }, }, diff --git a/src/app/admin/edge.component.ts b/src/app/admin/edge.component.ts index 404b1f78a..f0dabe5ae 100644 --- a/src/app/admin/edge.component.ts +++ b/src/app/admin/edge.component.ts @@ -11,7 +11,7 @@ import { removeEdge, retrieveEdgeToken, } from '@placeos/ts-client'; -import { copyToClipboard } from '../common/general'; +import { copyToClipboard, escapeHtml } from '../common/general'; import { notifyError, notifyInfo, @@ -228,7 +228,7 @@ export class PlaceEdgeComponent implements OnInit { const details = await openConfirmModal( { title: 'Remove edge?', - content: `Remove ${i.name}?
You or your users may lose access to some data.`, + content: `Remove ${escapeHtml(i.name)}?
You or your users may lose access to some data.`, icon: { type: 'icon', content: 'delete' }, }, this._dialog, diff --git a/src/app/admin/extensions.component.ts b/src/app/admin/extensions.component.ts index e4868ef7f..916737364 100644 --- a/src/app/admin/extensions.component.ts +++ b/src/app/admin/extensions.component.ts @@ -7,6 +7,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatSelectModule } from '@angular/material/select'; import { MatTooltipModule } from '@angular/material/tooltip'; import { PlaceDomain, updateDomain } from '@placeos/ts-client'; +import { escapeHtml } from '../common/general'; import { notifyError } from '../common/notifications'; import { waitForEvent } from '../common/signals'; import { ApplicationIcon, DialogEvent } from '../common/types'; @@ -256,7 +257,7 @@ export class PlaceExtensionsComponent implements OnInit { { data: { title: 'Remove extension', - content: `Are you sure you want to remove the extension "${item.name}" from ${item.type}?`, + content: `Are you sure you want to remove the extension "${escapeHtml(item.name)}" from ${escapeHtml(item.type)}?`, icon: { content: 'delete' }, }, }, diff --git a/src/app/admin/resource-imports.component.ts b/src/app/admin/resource-imports.component.ts index 45f7a8010..2c13cff86 100644 --- a/src/app/admin/resource-imports.component.ts +++ b/src/app/admin/resource-imports.component.ts @@ -8,6 +8,7 @@ import { MatSelectModule } from '@angular/material/select'; import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterModule } from '@angular/router'; import { addSystem, query, querySystemsWithEmails } from '@placeos/ts-client'; +import { escapeHtml } from '../common/general'; import { i18n } from '../common/locale.service'; import { notifySuccess, notifyWarn } from '../common/notifications'; import { openConfirmModal } from '../overlays/confirm-modal.component'; @@ -215,7 +216,7 @@ export class ResourceImportsComponent implements OnInit { count: missing.length, })}

`, icon: { type: 'icon', content: 'publish' }, diff --git a/src/app/admin/signage-ai/signage-ai.component.ts b/src/app/admin/signage-ai/signage-ai.component.ts index 17e67f6bf..2669df37e 100644 --- a/src/app/admin/signage-ai/signage-ai.component.ts +++ b/src/app/admin/signage-ai/signage-ai.component.ts @@ -3,6 +3,7 @@ import { MatRippleModule } from '@angular/material/core'; import { MatDialog } from '@angular/material/dialog'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { escapeHtml } from '../../common/general'; import { i18n } from '../../common/locale.service'; import { notifyError, notifySuccess } from '../../common/notifications'; import { openConfirmModal } from '../../overlays/confirm-modal.component'; @@ -284,7 +285,7 @@ export class SignageAIComponent implements OnInit { { title: i18n('ADMIN.AI_PROVIDER_TEST_TITLE'), content: i18n('ADMIN.AI_PROVIDER_TEST_MSG', { - name: item.name, + name: escapeHtml(item.name), }), icon: { content: 'bolt' }, }, @@ -314,7 +315,7 @@ export class SignageAIComponent implements OnInit { { title: i18n('ADMIN.AI_PROVIDER_REMOVE_TITLE'), content: i18n('ADMIN.AI_PROVIDER_REMOVE_MSG', { - name: item.name, + name: escapeHtml(item.name), }), icon: { content: 'delete_forever' }, }, diff --git a/src/app/admin/staff-api.component.ts b/src/app/admin/staff-api.component.ts index 544275828..ea96c8952 100644 --- a/src/app/admin/staff-api.component.ts +++ b/src/app/admin/staff-api.component.ts @@ -9,6 +9,7 @@ import { MatSelectModule } from '@angular/material/select'; import { MatTooltipModule } from '@angular/material/tooltip'; import { del, get, PlaceDomain } from '@placeos/ts-client'; import { addDays, getUnixTime, startOfDay } from 'date-fns'; +import { escapeHtml } from '../common/general'; import { notifyError, notifySuccess } from '../common/notifications'; import { HashMap } from '../common/types'; import { openConfirmModal } from '../overlays/confirm-modal.component'; @@ -247,7 +248,7 @@ export class PlaceStaffAPIComponent implements OnInit { const details = await openConfirmModal( { title: 'Remove tenant?', - content: `Remove ${tenant.name} from this domain?
+ content: `Remove ${escapeHtml(tenant.name)} from this domain?

This will remove all related: