Open Closed

HTML Injection on error popups #10875


User avatar
0
Edwintoh created

For angular project using abp, is there any way to prevent html injection in validation error popup? in any case the error contains html content

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

2 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • In ABP Angular, validation errors (and other HTTP errors) are typically shown via the theme’s HTTP error handler (uses ConfirmationService.error(...) internally). If your server returns an error message that contains HTML, the safest approach is to override the HTTP error handler and force the message to be treated as plain text (i.e., never bind it as HTML / never pass a “safeHtml” object).
    • Concretely: register your own HTTP_ERROR_HANDLER and sanitize/escape the message before showing it.
    // error.handler.ts
    import { Injector } from '@angular/core';
    import { HttpErrorResponse } from '@angular/common/http';
    import { throwError } from 'rxjs';
    import { ConfirmationService } from '@abp/ng.theme.shared';
    
    // Minimal HTML escaping (renders tags as text)
    function escapeHtml(input: any): string {
      const s = String(input ?? '');
      return s
        .replace(/&/g, '&')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    }
    
    export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
      // Example: specifically handle 400 validation errors
      if (httpError?.status === 400 && httpError.error?.error?.validationErrors?.length) {
        const confirmationService = injector.get(ConfirmationService);
    
        const rawMessage = httpError.error.error.validationErrors[0].message;
        const rawTitle = httpError.error.error.message;
    
        return confirmationService.error(
          escapeHtml(rawMessage),   // prevent HTML injection
          escapeHtml(rawTitle),     // prevent HTML injection
          { hideCancelBtn: true, yesText: 'AbpAccount::Close' }
        );
      }
    
      return throwError(() => httpError);
    }
    
    // app.module.ts
    import { HTTP_ERROR_HANDLER } from '@abp/ng.theme.shared';
    import { handleHttpErrors } from './error.handler';
    
    @NgModule({
      // ...
      providers: [
        { provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors },
      ],
    })
    export class AppModule {}
    

    This ensures that even if the backend returns <b>...</b> / <script>...</script> inside a validation message, it will be displayed as text, not executed/interpreted as HTML.

    Links (verified):

    • https://abp.io/support/questions/5171
    • https://github.com/abpframework/abp/blob/360a3395aa0e44fb77574ea7eac745e8ba94b82e/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts#L19

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    The confirmation popup binds the title and the message with [innerHTML], so HTML that comes inside an error message is rendered as markup. Angular sanitizes it first, so script tags and event handler attributes are dropped and no script runs, but plain tags like <b>, <img> or <a href> still render.

    If you want the text shown as-is, replace ConfirmationService (and ToasterService if you also show toasts) and escape the strings before they reach the template:

    // text-only-messages.service.ts
    import { Injectable } from '@angular/core';
    import { LocalizationParam } from '@abp/ng.core';
    import { Confirmation, ConfirmationService, Toaster, ToasterService } from '@abp/ng.theme.shared';
    import { Observable } from 'rxjs';
    
    export function escapeHtml(value: string): string {
      return value
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    }
    
    function escapeParam(param: LocalizationParam): LocalizationParam {
      return typeof param === 'string' ? escapeHtml(param) : param;
    }
    
    @Injectable({ providedIn: 'root' })
    export class TextOnlyConfirmationService extends ConfirmationService {
      override show(
        message: LocalizationParam,
        title: LocalizationParam,
        severity?: Confirmation.Severity,
        options = {} as Partial<Confirmation.Options>,
      ): Observable<Confirmation.Status> {
        return super.show(escapeParam(message), escapeParam(title), severity, {
          ...options,
          messageLocalizationParams: options.messageLocalizationParams?.map(escapeHtml),
          titleLocalizationParams: options.titleLocalizationParams?.map(escapeHtml),
        });
      }
    }
    
    @Injectable({ providedIn: 'root' })
    export class TextOnlyToasterService extends ToasterService {
      // the toast title is bound with {{ }}, only the message goes through innerHTML
      override show(
        message: LocalizationParam,
        title?: LocalizationParam,
        severity: Toaster.Severity = 'neutral',
        options = {} as Partial<Toaster.ToastOptions>,
      ): Toaster.ToasterId {
        return super.show(escapeParam(message), title, severity, {
          ...options,
          messageLocalizationParams: options.messageLocalizationParams?.map(escapeHtml),
        });
      }
    }
    

    Then register them in your app providers (app.config.ts, or app.module.ts if you are still on modules):

    import { ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
    import { TextOnlyConfirmationService, TextOnlyToasterService } from './text-only-messages.service';
    
    providers: [
      { provide: ConfirmationService, useExisting: TextOnlyConfirmationService },
      { provide: ToasterService, useExisting: TextOnlyToasterService },
      // ...
    ];
    

    Localization keys and {0} parameters keep working. One thing to keep in mind: after this change, HTML that your own code passes to a confirmation or a toast on purpose is shown as text too.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 28, 2026, 11:44
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.