Średnia

ApostropheCMS sanitize-html: unbounded recursion when re-sanitizing the fallback of a discarded iframe (stack overflow, nestingLimit not enforced)

  • Ujawniono: 21.10.2026
  • Aktualizacja: 22.10.2026 18:07

Summary

Since 2.18.0, when an <iframe> is not allowed and disallowedTagsMode is 'discard' (the defaults), its raw-text content is re-sanitized by a recursive call to sanitizeHtml(text, options) (the change from #5564, fixing #5550). The recursion has no bound and starts each nested call with a fresh state. Two consequences, both absent from 2.17.7 and 2.17.4:

  1. A few kilobytes of unclosed <iframe> tags throw RangeError: Maximum call stack size exceeded with the default options. Any application that sanitizes untrusted HTML with 2.18.0 can be made to throw on a small crafted input.
  2. nestingLimit is not enforced across a discarded iframe: each nested iframe restarts the depth count at zero, so the output can be nested arbitrarily deep regardless of the configured limit.

Affected versions

  • 2.18.0 (the current latest), and the main branch at the time of writing (the recursive call is still in packages/sanitize-html/index.js, in the ontext handler, branch tag === 'iframe' && !tagAllowed(tag) && options.disallowedTagsMode === 'discard').
  • Not affected: 2.17.7 and 2.17.4 (tested with the same inputs below).

Tested with Node.js v26.7.0.

Reproduction 1: stack overflow with the default options

js
const sanitizeHtml = require('sanitize-html');

// with 2.18.0:
sanitizeHtml('<iframe>'.repeat(1000));  // 8,000 chars: returns ''
sanitizeHtml('<iframe>'.repeat(2000));  // 16,000 chars: throws RangeError: Maximum call stack size exceeded
sanitizeHtml('<iframe>'.repeat(20000)); // 160,000 chars: throws RangeError: Maximum call stack size exceeded

Results:

input 2.17.4 2.17.7 2.18.0
'<iframe>'.repeat(1000) ok ok ok
'<iframe>'.repeat(2000) ok ok RangeError
'<iframe>'.repeat(20000) ok ok RangeError

The threshold depends on the remaining stack, so it is lower when sanitizeHtml is called from an already deep call stack (for example inside a server-side rendering pass).

Reproduction 2: nestingLimit is bypassed

js
const sanitizeHtml = require('sanitize-html');

const input = ('<iframe>' + '<b>'.repeat(3)).repeat(50) + 'x';
const out = sanitizeHtml(input, { allowedTags: ['b'], nestingLimit: 3 });
// maximum nesting depth of <b> in `out`:
//   2.17.4: 2    2.17.7: 0 (fallback escaped as text)    2.18.0: 150

Without an iframe, the same options cap the output at three nested <b> in all three versions. With k repetitions of '<iframe>' + '<b>'.repeat(3), 2.18.0 returns 3 * k nested <b> (measured for k = 1, 5, 50: depth 3, 15, 150).

Cause

In 2.18.0 the ontext handler does:

js
} else if (
  tag === 'iframe' &&
  !tagAllowed(tag) &&
  options.disallowedTagsMode === 'discard'
) {
  result += sanitizeHtml(text, options);
}

Because htmlparser2 treats <iframe> as a raw-text element, the text of an unclosed iframe is the whole remainder of the input; re-parsing it can meet another unclosed <iframe>, and so on, one stack frame (and one full re-parse) per iframe. The nested call also starts with its own depth, so tags inside the fallback are not counted against nestingLimit together with the enclosing tags.

Possible directions

Not a patch, only what would close both effects: bound the recursion (or process the fallback without recursing), and carry the current depth into the nested sanitization so that nestingLimit applies to the whole output.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka npm sanitize-html >= 2.18.0, < 2.18.1 2.18.1

Odnośniki

Ocena CVSS 5.3/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Słabość (CWE)
CWE-674
Platforma
npm
Paczka
sanitize-html
Ujawniono
21.10.2026
Aktualizacja
22.10.2026 18:07

Źródła i licencje

OSV / GitHub Advisory Database - GHSA-c9rx-x947-fp3h - CC BY 4.0

Historia podatności: sanitize-html

Wszystkie opisane podatności tej paczki (Paczka npm), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (17) w archiwum
Wszystkich wpisów
17
Pierwsza → ostatnia
11.2018 10.2026
Bez poprawki
0
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 1
  • Wysoka 1
  • Średnia 14
  • Niska 1

Ujawnienia w latach

  1. 2018: 2
  2. 2019: 0
  3. 2020: 1
  4. 2021: 2
  5. 2022: 1
  6. 2023: 0
  7. 2024: 1
  8. 2025: 1
  9. 2026: 9
Istotność:
  1. ApostropheCMS sanitize-html: unbounded recursion when re-sanitizing the fallback of a discarded iframe (stack overflow, nestingLimit not enforced) ten wpis
    Średnia · 5.3 CVE-2026-113650 >= 2.18.0, < 2.18.1 2.18.1
  2. Krytyczna · 9.3 CVE-2026-44990 >= 2.17.3, < 2.17.4 2.17.4
  3. Wysoka · 7.5 CVE-2022-25887 < 2.7.1 2.7.1

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.