typo3/html-sanitizer
A standalone PHP HTML sanitization component from TYPO3, usable in any PHP project. Declarative Behavior settings define allowed tags, attributes, and values; visitors modify or remove nodes; builders combine presets into ready-to-use Sanitizer instances for safe HTML output.
Architecture fit. Standalone, framework-agnostic PHP component distributed via Composer under MIT. Despite the TYPO3 namespace, the README explicitly declares CMS-agnosticism and the package has no dependency on TYPO3 core — it is a leaf library with only a PSR-3 logging dependency and ext-dom. The layering maps cleanly onto application boundaries: Behavior is policy, Visitor implementations are transformations, Builder is a preset factory, Sanitizer is the stateless engine. In a Laravel codebase that maps directly to config file → container-bound builder → singleton service. Critically, it is a parse-transform-serialize pipeline over a DOM, not a regex scrubber, which is the correct architecture for XSS defense.
Integration feasibility. High. composer require typo3/html-sanitizer plus roughly 30–50 lines of glue (service provider binding, optional Blade directive, optional Eloquent cast) is a working integration. No database, queue, or external service involvement. CommonBuilder ships a reasonable default allow-list, so a team can get to a safe baseline without hand-authoring policy. The cost is that no Laravel wrapper exists — we own and must test the glue ourselves.
Technical risk.
[SECURITY] fixes: comment-end-bang bypass, CDATA and raw-text element XSS, processing-instruction handling, namespace attribute encoding, and a raw-text bypass under ALLOW_INSECURE_RAW_TEXT. This is double-edged — it demonstrates active maintenance, but a pinned-and-forgotten version inherits known vulnerabilities. Patch discipline is non-negotiable.Behavior and visitors mandatory in the constructor and removed CommonBuilder->srcsetAttr; v2.3.1 reverted that removal. Treat minor bumps as needing test runs, not silent updates.Behavior is immutable (withFlags/withTags return new instances) but Behavior\Tag and Behavior\Attr are mutable. Sharing and then mutating a tag or attr object after building a preset is a real defect class, especially under Octane.ALLOW_CUSTOM_ELEMENTS, ALLOW_INSECURE_RAW_TEXT, and naive value regexes (the README's #^https?://# is unanchored) can silently weaken policy. The library cannot save a team from a permissive allow-list.Key questions.
symfony/html-sanitizer is a lower-risk, larger-community alternative.Behavior policy artifact — security, platform, or product?Stack fit. Compatible with Laravel 9–12 on PHP 8.1–8.4; the 2.x line has CI coverage through PHP 8.5. MIT licensing is clean for closed-source commercial use. No dependency conflicts beyond PSR-3. We supply the missing framework glue: a config/html-sanitizer.php holding the allow-list, an HtmlSanitizerServiceProvider binding the Sanitizer singleton via a custom Builder, and a Blade directive or HtmlString cast for unescaped rendering.
Alternatives weighed: mews/purifier (HTMLPurifier) is mature but config-driven, slow, and has no custom-element handlers or encode-rather-than-delete semantics. Symfony's sanitizer follows the W3C spec with a larger community but is less expressive for the "disarm and keep" behavior this package offers.
Migration path.
Behavior from evidence rather than assumption.Builder, and build a golden-file regression suite seeded from the oliverhader/html-sanitizer-demo XSS payload corpus.{!! !!} on user input, strip_tags, or an older purifier) and delete its config.Compatibility. Construct the Sanitizer once and bind it as a singleton — it is stateless after construction. Keep closures out of cached config so Behavior is reproducible under config:cache. Under Octane, never mutate Tag/Attr objects at request time.
Sequencing. Week 1: corpus inventory, policy draft, test corpus. Week 2: shadow mode and diff instrumentation. Week 3: enforce on new writes. Week 4: backfill and retire legacy. Roughly one engineer for 2–3 weeks including security review.
Maintenance. Small code footprint, but a real lifecycle obligation. Watch GitHub releases and the TYPO3 security advisory channel; configure Dependabot/Renovate with the package ungrouped so security patches land as individual, reviewable PRs. Expect 2–5 releases a year, at least one security-relevant. Pin with ~2.3.0 and treat patch bumps as high-priority, test-covered changes. The Behavior policy becomes a living config artifact co-owned by security and product — every newly allowed tag is a security decision, not a formatting one.
Support. Community-only; no vendor SLA. Escalation is the TYPO3 Security Team or GitHub private advisories. Acceptable for internal and standard commercial use; a genuine gap if the product sits under a customer security SLA requiring vendor-backed support. In that case, budget for internal ownership via fork, or select a vendor-backed alternative.
Scaling. Stateless and horizontally scalable; cost is per-document CPU in the PHP worker with no I/O. Cache the singleton and never rebuild Behavior per request. The scaling hazard is unbounded input: a multi-megabyte document expands into a large DOM. Enforce an input size cap (e.g. 256 KB) in the validation layer before calling the sanitizer, and run backfills in chunked jobs with explicit memory limits. At high write volume, sanitize on write rather than on every read.
Failure modes.
Behavior or unanchored regex lets payloads through. Mitigated by the XSS payload regression corpus in CI.Ramp-up. The API itself is under a day for a mid-level PHP developer; the README example is close to a working integration. The real effort is the policy: 2–4 days to author, security-review, and validate an allow-list against a real corpus, plus ongoing review whenever content types change. No specialist knowledge is required beyond standard XSS literacy — and that literacy, not the library, is the gating skill for safe operation.
How can I help you explore Laravel packages today?