Weave Code
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

Html Sanitizer Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require symfony/html-sanitizer
    

    For Laravel, ensure compatibility with your Symfony components version (e.g., symfony/http-foundation).

  2. Basic sanitization:

    use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
    use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
    
    $config = (new HtmlSanitizerConfig())->allowSafeElements();
    $sanitizer = new HtmlSanitizer($config);
    
    $cleanHtml = $sanitizer->sanitize($userInput);
    
  3. 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]);
    
  4. Where to look first:

    • README.md: For core configuration methods (allowElement(), blockElement()).
    • HtmlSanitizerConfig class: Reference for all available rules.
    • sanitizeFor() method: For context-specific sanitization (e.g., <head>, <textarea>).

Implementation Patterns

Workflows

  1. 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);
        });
    }
    
  2. 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
    
  3. Attribute-Level Control:

    • Allow specific attributes:
      $config->allowElement('img', ['src', 'alt', 'width', 'height']);
      
    • Force security headers:
      $config->forceAttribute('a', 'rel', 'noopener noreferrer');
      $config->forceAttribute('iframe', 'sandbox', 'allow-same-origin');
      
  4. URL Sanitization: Restrict links/media to trusted domains:

    $config
        ->allowLinkSchemes(['https', 'mailto'])
        ->allowLinkHosts(['trusted.com', '*.example.org'])
        ->allowMediaSchemes(['https'])
        ->allowMediaHosts(['cdn.trusted.com']);
    
  5. 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());
    
  6. 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')),
            ]);
        }
    }
    

Integration Tips

  • 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();
    

Gotchas and Tips

Pitfalls

  1. False Sense of Security:

    • Myth: "Allowing safeElements() is enough."
      • Reality: safeElements() still allows CSS injection (e.g., <style>, style attributes). Use allowStaticElements() for stricter control.
    • Fix: Explicitly block dangerous attributes:
      $config->dropAttribute('style', '*');
      
  2. Context Misuse:

    • Myth: "All contexts are the same."
      • Reality: sanitizeFor('div') and sanitizeFor('head') behave differently. Misusing contexts can lead to XSS (e.g., allowing <script> in <div>).
    • Fix: Always specify the correct context:
      // Wrong: Treats as body (may allow scripts)
      $sanitizer->sanitize($html);
      
      // Correct: Treats as text (safe for <title>)
      $sanitizer->sanitizeFor('title', $html);
      
  3. URL Sanitization Overrides:

    • Myth: "Forcing HTTPS is enough."
      • Reality: Relative URLs (/path) or javascript: schemes may slip through.
    • Fix: Combine rules:
      $config
          ->forceHttpsUrls()
          ->allowRelativeLinks() // Only if intentional
          ->dropAttribute('href', ['javascript:', 'data:']);
      
  4. Attribute Sanitizer Conflicts:

    • Myth: "Custom sanitizers don’t interfere."
      • Reality: Multiple AttributeSanitizerInterface implementations may override each other unpredictably.
    • Fix: Register custom sanitizers last or use priority queues.
  5. Performance with Large HTML:

    • Myth: "Sanitization is negligible."
      • Reality: Parsing malformed HTML (e.g., nested <div> tags) can be slow.
    • Fix: Pre-sanitize with a lightweight validator or use PHP 8.4’s native parser:
      if (strlen($html) > 10000) {
          // Fallback to a simpler config for large inputs
          $config->allowSafeElements()->dropAttribute('style', '*');
      }
      

Debugging

  1. Unexpected Drops:

    • Issue: Elements/attributes disappear without explanation.
    • Debug: Check the config order (later rules override earlier ones). Use 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']);
      
  2. CSS/JS Injection:

    • Issue: Sanitized output still contains <style> or <script>.
    • Debug: Ensure safeElements() or staticElements() is not used. Explicitly block:
      $config->dropElement(['style', 'script', 'iframe']);
      
  3. URL Validation Failures:

    • Issue: Valid URLs are rejected (e.g., https://sub.example.com).
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport