symfony/html-sanitizer
Object-oriented HTML sanitizer for PHP/Symfony. Safely cleans untrusted HTML for DOM insertion with configurable allow/block/drop rules, attribute policies, forced values, and URL controls (HTTPS and scheme/host allowlists). Removes scripts and unsafe behaviors.
Install the package:
composer require symfony/html-sanitizer
For Laravel, ensure compatibility with your Symfony components version (e.g., symfony/http-foundation).
Basic sanitization:
use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
$config = (new HtmlSanitizerConfig())->allowSafeElements();
$sanitizer = new HtmlSanitizer($config);
$cleanHtml = $sanitizer->sanitize($userInput);
First use case: Sanitize user-generated content in a Laravel Blade template or API response:
// In a controller
$comment = $sanitizer->sanitize($request->input('comment'));
return response()->json(['comment' => $comment]);
Where to look first:
allowElement(), blockElement()).HtmlSanitizerConfig class: Reference for all available rules.sanitizeFor() method: For context-specific sanitization (e.g., <head>, <textarea>).Dynamic Configuration:
Use dependency injection to create reusable sanitizer instances for different contexts (e.g., CommentSanitizer, AdminDashboardSanitizer):
// Laravel Service Provider
public function register()
{
$this->app->singleton('comment.sanitizer', function () {
$config = (new HtmlSanitizerConfig())
->allowSafeElements()
->allowElement('a', ['href', 'title'])
->forceHttpsUrls();
return new HtmlSanitizer($config);
});
}
Context-Aware Sanitization:
Sanitize differently based on where HTML is rendered (e.g., <div> vs. <title>):
$sanitizer->sanitizeFor('div', $html); // Allows body tags
$sanitizer->sanitizeFor('title', $html); // Encodes as text
$sanitizer->sanitizeFor('textarea', $html); // Encodes as text
Attribute-Level Control:
$config->allowElement('img', ['src', 'alt', 'width', 'height']);
$config->forceAttribute('a', 'rel', 'noopener noreferrer');
$config->forceAttribute('iframe', 'sandbox', 'allow-same-origin');
URL Sanitization: Restrict links/media to trusted domains:
$config
->allowLinkSchemes(['https', 'mailto'])
->allowLinkHosts(['trusted.com', '*.example.org'])
->allowMediaSchemes(['https'])
->allowMediaHosts(['cdn.trusted.com']);
Custom Attribute Sanitizers:
Extend AttributeSanitizerInterface for domain-specific rules (e.g., validate data-* attributes):
use Symfony\Component\HtmlSanitizer\AttributeSanitizerInterface;
class CustomAttributeSanitizer implements AttributeSanitizerInterface
{
public function sanitizeAttribute(string $name, string $value, string $element): string
{
if (str_starts_with($name, 'data-')) {
return preg_replace('/[^a-z0-9\-_]/i', '', $value);
}
return $value;
}
}
$config->withAttributeSanitizer(new CustomAttributeSanitizer());
Laravel Form Request Integration: Sanitize HTML in form requests:
use Illuminate\Foundation\Http\FormRequest;
class StoreCommentRequest extends FormRequest
{
public function sanitizeHtml()
{
$config = (new HtmlSanitizerConfig())->allowSafeElements();
$sanitizer = new HtmlSanitizer($config);
$this->merge([
'comment' => $sanitizer->sanitize($this->input('comment')),
]);
}
}
Laravel Blade Directives: Create a custom Blade directive for sanitization:
// app/Providers/BladeServiceProvider.php
Blade::directive('sanitize', function ($expression) {
$sanitizer = app('sanitizer'); // Injected instance
return "<?php echo {$sanitizer}->sanitize({$expression}); ?>";
});
Usage:
{!! sanitize($userComment) !!}
API Responses: Use middleware to sanitize HTML in JSON responses:
// app/Http/Middleware/SanitizeHtml.php
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->isJson()) {
$data = $response->json();
$sanitizer = app('sanitizer');
array_walk_recursive($data, function (&$value) use ($sanitizer) {
if (is_string($value)) {
$value = $sanitizer->sanitize($value);
}
});
$response->setJson($data);
}
return $response;
}
Database Storage: Store sanitized HTML in the database to ensure consistency:
$sanitized = $sanitizer->sanitize($rawHtml);
$model->fill(['content' => $sanitized])->save();
False Sense of Security:
safeElements() is enough."
safeElements() still allows CSS injection (e.g., <style>, style attributes). Use allowStaticElements() for stricter control.$config->dropAttribute('style', '*');
Context Misuse:
sanitizeFor('div') and sanitizeFor('head') behave differently. Misusing contexts can lead to XSS (e.g., allowing <script> in <div>).// Wrong: Treats as body (may allow scripts)
$sanitizer->sanitize($html);
// Correct: Treats as text (safe for <title>)
$sanitizer->sanitizeFor('title', $html);
URL Sanitization Overrides:
/path) or javascript: schemes may slip through.$config
->forceHttpsUrls()
->allowRelativeLinks() // Only if intentional
->dropAttribute('href', ['javascript:', 'data:']);
Attribute Sanitizer Conflicts:
AttributeSanitizerInterface implementations may override each other unpredictably.Performance with Large HTML:
<div> tags) can be slow.if (strlen($html) > 10000) {
// Fallback to a simpler config for large inputs
$config->allowSafeElements()->dropAttribute('style', '*');
}
Unexpected Drops:
allowElement() before dropElement():
// Wrong: Drops 'div' before allowing it
$config->dropElement('div')->allowElement('div');
// Correct: Allow first, then fine-tune
$config->allowElement('div')->dropAttribute('onclick', ['div']);
CSS/JS Injection:
<style> or <script>.safeElements() or staticElements() is not used. Explicitly block:
$config->dropElement(['style', 'script', 'iframe']);
URL Validation Failures:
https://sub.example.com).How can I help you explore Laravel packages today?