Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Xss Sanitizer Laravel Package

phlib/xss-sanitizer

PHP library for sanitizing untrusted HTML and preventing XSS. It parses user-supplied markup, strips dangerous tags, attributes and URLs, and returns safe HTML for rendering in web applications. Configurable rules suit comments, rich text and other user-generated content.

Deep Wiki
Context7

Getting Started

Install and inspect first

composer require phlib/xss-sanitizer

Before writing any app code, open the package's src/ directory and README. The public surface is small — the entry point is Phlib\XssSanitizer\Sanitizer (construct it, then call sanitize($html)). Confirm its constructor signature in src/Sanitizer.php, since it may accept a filter list or an HTMLPurifier config object depending on the tagged version. Also run composer show phlib/xss-sanitizer to see the exact version and its transitive dependency on ezyang/htmlpurifier.

Verify the runtime requirements

HTMLPurifier needs ext-dom, ext-libxml, and ext-mbstring, plus a writable cache directory. Check these on your Laravel host before you build anything on top of the package:

php -m | grep -Ei 'dom|libxml|mbstring'

First use case: sanitizing user-submitted rich text

The canonical Laravel scenario is a WYSIWYG field (post body, comment with formatting, CMS page) that you want to render as HTML. Blade's {{ }} escapes everything, so it's safe but strips all formatting; {!! !!} renders HTML but is an XSS hole if the input is untrusted. The package fits exactly in that gap:

use Phlib\XssSanitizer\Sanitizer;

$clean = (new Sanitizer())->sanitize($request->input('body'));

Store the sanitized output in the database, then render it with {!! $post->body !!}. That way every reader gets safe markup and you never have to remember which fields were run through the sanitizer. Start with one field in one model, add a feature test with a handful of known XSS vectors (<script>, <img onerror=…>, javascript: hrefs), and expand from there.

Implementation Patterns

Bind a singleton in a service provider

Do not new Sanitizer() per row or per render — HTMLPurifier's setup (loading the definition, parsing config) is the expensive part. Register it once:

// AppServiceProvider::register()
$this->app->singleton(Sanitizer::class, function () {
    return new Sanitizer(); // pass config/filters here
});

Then inject Sanitizer into controllers, form requests, or jobs. If the package exposes a filter array or HTMLPurifier config in its constructor, this is the one place the allow-list policy lives — keep it out of controllers so it can be reviewed and changed in isolation.

Sanitize at the write boundary, not the read boundary

The cleanest pattern in Laravel is a form request prepareForValidation() or a mutator/observer on the model, so dirty HTML never reaches the database:

protected function prepareForValidation(): void
{
    $this->merge([
        'body' => app(Sanitizer::class)->sanitize((string) $this->input('body')),
    ]);
}

Doing it on write means (a) you pay the cost once instead of on every request, (b) you can add a body_sanitized_at column or a queue job to re-sanitize old content when the policy tightens, and (c) you avoid the classic mistake of rendering escaped output and then "un-escaping" it later.

Custom Blade directive for the render side

To make intent obvious at the call site and prevent accidental {!! !!} on unsanitized data, register a directive:

Blade::directive('sanitized', fn ($expr) => "<?php echo app(\\Phlib\\XssSanitizer\\Sanitizer::class)->sanitize($expr); ?>");

@sanitized($post->body) is then an explicit, greppable contract, and code review can flag any bare {!! !!} as suspicious. A cheaper alternative: render {!! $post->body !!} only for fields that are guaranteed sanitized by a model cast, and document that invariant on the model.

Pipeline with filters for different trust levels

Rich-text fields and short user strings want different policies. If the package's filter mechanism allows it, define two configured instances (e.g. Sanitizer::richText() and Sanitizer::plainText()) — the plain-text variant stripping tags entirely, the rich-text one allowing a small tag/attribute set. Bind them with contextual or named bindings, or expose them as small wrapper classes so injection stays type-safe. Keep the policies in config/sanitizer.php rather than inline arrays.

Handle large documents asynchronously

Sanitizing a long article or imported RSS payload can take tens to hundreds of milliseconds — enough to blow a request budget if done inline on a bulk import. For imports, dispatch a job that sanitizes and persists, and show a "processing" state. For normal form submissions the inline cost is fine.

Test with a vector corpus, not just one example

Add a Pest/PHPUnit test that iterates over a fixed list of payloads (script tags, event handlers, javascript: and data: URLs, SVG/MathML tricks, malformed nesting) and asserts the output contains none of the dangerous substrings and does retain expected formatting (<p>, <strong>, <a href>). This is the regression net that lets you change configuration later without fear.

Gotchas and Tips

Licensing is the first decision, not the last

This package (and HTMLPurifier beneath it) is LGPL-3.0, not MIT. For a typical SaaS that's usually acceptable — you're using it as a library, not modifying and statically linking it — but if you ship a distributed PHP application or have a legal team with strong opinions about copyleft, get sign-off before it's woven through the codebase. It's much easier to swap now than after fifty call sites.

HTMLPurifier performance and caching

HTMLPurifier parses its configuration and builds the allowed-tag definition on each instantiation, which is slow. The fix is (a) the singleton above and (b) enabling its definition cache. If the package exposes the underlying HTMLPurifier config, set a cache path under storage/framework/cache/sanitizer and ensure the directory is writable — a silent failure to write the cache is a common cause of "why is this endpoint 300ms slower in production." If the package doesn't expose config, wrap it and accept the cost, or fork/patch.

Encoding and mbstring

HTMLPurifier assumes UTF-8; set the document encoding explicitly if the app handles anything else. Missing mbstring produces mangled multibyte output rather than a clean error, and it's easy to miss locally if your dev image has it and production doesn't.

It outputs HTML4-era markup

Expect HTMLPurifier to normalize/reject HTML5 constructs (<figure>, <video>, some data-* attributes). If your editor produces modern markup, allow-list the specific tags you need or accept that formatting will be flattened. Decide this deliberately — a silently dropped <figure> is a content bug the author will report as "the editor ate my image."

Sanitization is not validation

Running input through the sanitizer doesn't make it valid data — it just guarantees it's safe HTML. Keep Laravel validation (required|string|max:…) separate, and don't let a permissive sanitizer substitute for rejecting obviously abusive payloads.

Don't double-escape

The classic bug is sanitizing on write, then also running the value through e() or {{ }} on render, which displays literal <p> tags. Pick one path: sanitize-then-{!! !!}, or store raw and escape on render. Mixing them is how teams end up with a "raw HTML" escape hatch that quietly reintroduces XSS.

Attribute and URL schemes

If you customize the policy, remember the danger lives in attributes: onerror, onclick, style with expression(), and href/src with javascript: or data: URLs. Set allowed URI schemes explicitly rather than trusting tag allow-lists alone. If you allow iframes for embeds, that's a separate opt-in with its own host allow-list.

Small-package risk and the Laravel-native alternative

16 stars and a single maintainer means you should pin the version in composer.json and be prepared to take over maintenance. If you want a Laravel-first integration with config publishing and a facade out of the box, mews/purifier wraps the same HTMLPurifier engine and is the more common choice in the ecosystem. The phlib package is a thinner wrapper — fine if you want minimal surface area, but weigh that against community support. Either way, isolate the sanitizer behind your own interface (a HtmlSanitizer contract bound in a provider) so swapping engines later is a one-file change.

Extension point: custom filters and rules

If the package exposes its filter interface, custom filters are the right place for domain rules — e.g. rewriting <a> targets to rel="noopener noreferrer", or stripping class attributes that could clash with your CSS. Keep them pure and unit-test them individually; a custom filter that silently mangles output is harder to debug than a misconfigured allow-list.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle