Merge pull request #14027 from mtsymbarov-del/fix/rule-chain-vulnerability

Fixed XSS vulnerabilities in the Rule node
This commit is contained in:
Igor Kulikov 2025-09-25 17:00:00 +03:00 committed by GitHub
commit 6b4636ac48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 17 additions and 3 deletions

View File

@ -30,6 +30,7 @@ import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { parseHttpErrorMessage } from '@core/utils'; import { parseHttpErrorMessage } from '@core/utils';
import { getInterceptorConfig } from './interceptor.util'; import { getInterceptorConfig } from './interceptor.util';
import { DomSanitizer } from '@angular/platform-browser';
const tmpHeaders = {}; const tmpHeaders = {};
@ -46,6 +47,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
private dialogService: DialogService, private dialogService: DialogService,
private translate: TranslateService, private translate: TranslateService,
private authService: AuthService, private authService: AuthService,
private sanitizer: DomSanitizer
) {} ) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
@ -129,7 +131,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
} }
if (unhandled && !ignoreErrors) { if (unhandled && !ignoreErrors) {
const errorMessageWithTimeout = parseHttpErrorMessage(errorResponse, this.translate, req.responseType); const errorMessageWithTimeout = parseHttpErrorMessage(errorResponse, this.translate, req.responseType, this.sanitizer);
this.showError(errorMessageWithTimeout.message, errorMessageWithTimeout.timeout); this.showError(errorMessageWithTimeout.message, errorMessageWithTimeout.timeout);
} }
return throwError(() => errorResponse); return throwError(() => errorResponse);

View File

@ -31,6 +31,8 @@ import {
isNotEmptyTbFunction, isNotEmptyTbFunction,
TbFunction TbFunction
} from '@shared/models/js-function.models'; } from '@shared/models/js-function.models';
import { DomSanitizer } from '@angular/platform-browser';
import { SecurityContext } from '@angular/core';
const varsRegex = /\${([^}]*)}/g; const varsRegex = /\${([^}]*)}/g;
@ -809,7 +811,7 @@ export function getEntityDetailsPageURL(id: string, entityType: EntityType): str
} }
export function parseHttpErrorMessage(errorResponse: HttpErrorResponse, export function parseHttpErrorMessage(errorResponse: HttpErrorResponse,
translate: TranslateService, responseType?: string): {message: string; timeout: number} { translate: TranslateService, responseType?: string, sanitizer?:DomSanitizer): {message: string; timeout: number} {
let error = null; let error = null;
let errorMessage: string; let errorMessage: string;
let timeout = 0; let timeout = 0;
@ -837,6 +839,9 @@ export function parseHttpErrorMessage(errorResponse: HttpErrorResponse,
errorText += errorKey ? translate.instant(errorKey) : errorResponse.statusText; errorText += errorKey ? translate.instant(errorKey) : errorResponse.statusText;
errorMessage = errorText; errorMessage = errorText;
} }
if(sanitizer) {
errorMessage = sanitizer.sanitize(SecurityContext.HTML,errorMessage);
}
return {message: errorMessage, timeout}; return {message: errorMessage, timeout};
} }

View File

@ -26,6 +26,7 @@ import {
OnInit, OnInit,
QueryList, QueryList,
Renderer2, Renderer2,
SecurityContext,
SkipSelf, SkipSelf,
ViewChild, ViewChild,
ViewChildren, ViewChildren,
@ -97,6 +98,7 @@ import { HttpStatusCode } from '@angular/common/http';
import { TbContextMenuEvent } from '@shared/models/jquery-event.models'; import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
import { EntityDebugSettings } from '@shared/models/entity.models'; import { EntityDebugSettings } from '@shared/models/entity.models';
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
import { DomSanitizer } from '@angular/platform-browser';
@Component({ @Component({
selector: 'tb-rulechain-page', selector: 'tb-rulechain-page',
@ -273,6 +275,7 @@ export class RuleChainPageComponent extends PageComponent
private renderer: Renderer2, private renderer: Renderer2,
private viewContainerRef: ViewContainerRef, private viewContainerRef: ViewContainerRef,
private changeDetector: ChangeDetectorRef, private changeDetector: ChangeDetectorRef,
private sanitizer:DomSanitizer,
public dialog: MatDialog, public dialog: MatDialog,
public dialogService: DialogService, public dialogService: DialogService,
public fb: FormBuilder) { public fb: FormBuilder) {
@ -1360,9 +1363,13 @@ export class RuleChainPageComponent extends PageComponent
name = node.name; name = node.name;
desc = this.translate.instant(ruleNodeTypeDescriptors.get(node.component.type).name) + ' - ' + node.component.name; desc = this.translate.instant(ruleNodeTypeDescriptors.get(node.component.type).name) + ' - ' + node.component.name;
if (node.additionalInfo) { if (node.additionalInfo) {
details = node.additionalInfo.description; details = this.sanitizer.sanitize(SecurityContext.HTML, node.additionalInfo.description);
} }
} }
name = this.sanitizer.sanitize(SecurityContext.HTML, name);
desc = this.sanitizer.sanitize(SecurityContext.HTML, desc);
let tooltipContent = '<div class="tb-rule-node-tooltip">' + let tooltipContent = '<div class="tb-rule-node-tooltip">' +
'<div id="tb-node-content">' + '<div id="tb-node-content">' +
'<div class="tb-node-title">' + name + '</div>' + '<div class="tb-node-title">' + name + '</div>' +