fix(front-end): fix security
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / prettier-autofix (pull_request) Successful in 8s
Build and Deploy / test-frontend (push) Successful in 1m5s
PR Checks / security-sast (pull_request) Successful in 36s
PR Checks / test-backend (pull_request) Successful in 27s
Build and Deploy / build-and-push (push) Successful in 23s
PR Checks / test-frontend (pull_request) Successful in 1m1s
Build and Deploy / deploy (push) Successful in 9s

This commit is contained in:
2026-03-11 11:02:47 +01:00
parent 052ade3e91
commit 78b719d3c2
2 changed files with 43 additions and 13 deletions

View File

@@ -1836,17 +1836,18 @@ export class AdminShopComponent implements OnInit, OnDestroy {
if (!editor) {
return;
}
const currentHtml = this.serializeNodeChildren(editor);
const currentLanguage = this.activeContentLanguage;
if (sanitize) {
const normalized = this.normalizeRichTextStorageValue(editor.innerHTML);
const normalized = this.normalizeRichTextStorageValue(currentHtml);
const safeHtml = normalized ?? '';
this.productForm.descriptions[currentLanguage] = safeHtml;
if (editor.innerHTML !== safeHtml) {
editor.innerHTML = safeHtml;
if (currentHtml !== safeHtml) {
this.replaceElementContentFromHtml(editor, safeHtml);
}
return;
}
this.productForm.descriptions[currentLanguage] = editor.innerHTML;
this.productForm.descriptions[currentLanguage] = currentHtml;
}
private renderActiveDescriptionInEditor(): void {
@@ -1856,8 +1857,8 @@ export class AdminShopComponent implements OnInit, OnDestroy {
}
const html =
this.productForm.descriptions[this.activeContentLanguage] ?? '';
if (editor.innerHTML !== html) {
editor.innerHTML = html;
if (this.serializeNodeChildren(editor) !== html) {
this.replaceElementContentFromHtml(editor, html);
}
}
@@ -1926,7 +1927,7 @@ export class AdminShopComponent implements OnInit, OnDestroy {
}
}
return outputBody.innerHTML;
return this.serializeNodeChildren(outputBody);
}
private sanitizeRichTextNode(
@@ -2016,9 +2017,35 @@ export class AdminShopComponent implements OnInit, OnDestroy {
}
private extractTextFromHtml(value: string): string {
const container = document.createElement('div');
container.innerHTML = value;
return container.textContent ?? '';
const parser = new DOMParser();
const parsed = parser.parseFromString(`<body>${value}</body>`, 'text/html');
return parsed.body.textContent ?? '';
}
private serializeNodeChildren(node: Node): string {
const serializer = new XMLSerializer();
let html = '';
for (const child of Array.from(node.childNodes)) {
html += serializer.serializeToString(child);
}
return html;
}
private replaceElementContentFromHtml(
element: HTMLElement,
html: string,
): void {
if (!html) {
element.replaceChildren();
return;
}
const parser = new DOMParser();
const parsed = parser.parseFromString(`<body>${html}</body>`, 'text/html');
const nodes = Array.from(parsed.body.childNodes).map((child) =>
document.importNode(child, true),
);
element.replaceChildren(...nodes);
}
private escapeHtml(value: string): string {

View File

@@ -671,9 +671,12 @@ export class ProductDetailComponent {
if (!this.containsHtmlMarkup(normalized)) {
return normalized;
}
const container = document.createElement('div');
container.innerHTML = normalized;
const text = (container.textContent ?? '').replace(/\u00a0/g, ' ').trim();
const parser = new DOMParser();
const parsed = parser.parseFromString(
`<body>${normalized}</body>`,
'text/html',
);
const text = (parsed.body.textContent ?? '').replace(/\u00a0/g, ' ').trim();
return text || null;
}