Problem

Symfony's HtmlSanitizerConfig ships with a default
maxInputLength of 20,000 bytes. This cap was
designed to protect public-facing web forms from denial-of-service attacks
via enormous user-submitted strings. HtmlSanitizerConfigBuilder::build()
never calls withMaxInputLength(), so every sanitizer instance
silently inherits this 20 kB limit.

When the HTML fragment passed to the sanitizer exceeds 20,000 bytes,
Symfony executes:

$input = substr($input, 0, $this->config->getMaxInputLength());

This is a raw byte truncation of the input string before any
parsing occurs. The truncated string is then handed to PHP's DOMDocument,
which parses the broken markup, auto-closes all open tags, and serializes a
structurally valid but content-incomplete HTML fragment. No warning is logged.
No exception is thrown. The pipeline accepts the truncated output and continues.

Steps to reproduce

  1. Configure a migration using html_processor with a
    container selector that returns a large HTML section (e.g. a
    page wrapper containing navigation, description, subjects, and a multi-row
    editions table). The sanitizer runs at Step 5, before minification at
    Step 7, so the cap applies to the raw extracted HTML — which can be
    significantly larger than the final minified output.
  2. Enable a sanitizer option such as allowStaticElements: true.
  3. Run the migration against a URL that returns a large page.
  4. Compare the processed-html-*.html debug output file with the
    original HTML fetched from the URL.

Expected result

The sanitized output contains all content that the sanitizer's allowlist permits.

Actual result

The output is silently truncated at the 20,000-byte mark. In the observed case
the truncation occurred inside an HTML attribute value
(alt="Cover of: Die Mars-Chronike" instead of
"Die Mars-Chroniken"), followed by DOMDocument auto-closing all open
tags. Several table rows present in the input were absent from the output. No
warning was logged. The migration reported success.

Root cause

HtmlSanitizerConfig::$maxInputLength defaults to
20_000 (Symfony source, line 102). HtmlSanitizer::sanitizeFor()
applies this cap via substr() before parsing:

// Prevent DOS attack induced by extremely long HTML strings
if (-1 !== $this->config->getMaxInputLength() && strlen($input) > $this->config->getMaxInputLength()) {
    $input = substr($input, 0, $this->config->getMaxInputLength());
}

HtmlSanitizerConfigBuilder::build() never overrides this default.
Since html_processor is a server-side content pipeline — not a
web-form input sanitizer — the DOS protection is inappropriate: content is already
scoped by the container step before reaching the sanitizer, and
the source URLs are administrator-configured, not anonymous-user-supplied.

Fix

In HtmlSanitizerConfigBuilder::build(), initialize the config with
the cap disabled:

// Symfony's default maxInputLength is 20,000 bytes — designed for web-form
// DOS protection, not server-side content pipelines. Disable it. Callers can
// re-enable via the maxInputLength sanitizer option.
$config = (new HtmlSanitizerConfig())->withMaxInputLength(-1);

Additionally, expose maxInputLength as a first-class sanitizer
option so callers operating in untrusted contexts can set an explicit cap:

sanitizer:
  allowStaticElements: true
  maxInputLength: 65536   # bytes; -1 = no cap (module default)

Impact

  • Content is silently dropped from the pipeline output with no log entry and
    no exception. AI migration pipelines receive and process truncated HTML
    believing it to be complete.
  • The bug is invisible at the migration level: the migration reports success,
    field values are populated, and nodes are created — but from incomplete
    source data.
  • Truncation is non-deterministic: it depends on the byte size of the live
    page fragment at fetch time, making it difficult to reproduce against static
    fixtures.

Environment

  • html_processor: ^2.0 (2.0.0-rc1)
  • Drupal: ^10 || ^11
  • PHP: 8.2+
  • Symfony HtmlSanitizer: ^7.0 (HtmlSanitizerConfig::$maxInputLength = 20_000 at line 102)
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

webbywe created an issue. See original summary.

  • webbywe committed 3fc358d5 on 1.0.x
    Issue #3607368: Fix HtmlSanitizerConfigBuilder silently truncating input...
webbywe’s picture

Status: Active » Fixed

Merged. Needed for an RC2.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

webbywe’s picture

Status: Fixed » Closed (fixed)