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
- Configure a migration using
html_processorwith a
containerselector 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. - Enable a sanitizer option such as
allowStaticElements: true. - Run the migration against a URL that returns a large page.
- Compare the
processed-html-*.htmldebug 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_000at line 102)
Issue fork html_processor-3607368
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
Comment #4
webbywe commentedMerged. Needed for an RC2.
Comment #6
webbywe commented