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

Filter Laravel Package

joomla/filter

Joomla's Filter package provides input and output sanitization utilities, letting you validate and clean user data and build URL-safe strings for secure PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require joomla/filter "~3.0.7"
    

    For PHP 8.3+ projects, use ~4.0 and confirm the 4.x line contains the equivalent XSS fixes shipped in 3.0.7 before relying on it.

    Security note: 3.0.7 patches two XSS filter bypasses (whitespace in HTML data: URIs, and an HTML5 entity-decode mismatch). If you are pinned below 3.0.7, upgrade before doing anything else — this is a security patch, not a feature release.

  2. First Use Case: Sanitize user input in a Laravel controller or request handler:

    use Joomla\Filter\InputFilter;
    
    $cleanInput = InputFilter::clean(
        $userInput,
        InputFilter::ONLY_ALLOW_DEFINED_TAGS,
        ['p', 'b', 'i', 'a']
    );
    
  3. Where to Look First:

    • InputFilter for HTML sanitization.
    • OutputFilter for URL/string sanitization.
    • Tests for edge-case examples (e.g., XSS evasion).
    • The 3.0.6...3.0.7 diff — the exact payload shapes now blocked make excellent regression-test fixtures.

Implementation Patterns

Usage Patterns

  1. Request Sanitization Pipeline:

    // In a Laravel Form Request or middleware
    public function sanitize($input, array $allowedTags = ['p', 'a'])
    {
        return InputFilter::clean(
            $input,
            InputFilter::ONLY_ALLOW_DEFINED_TAGS,
            $allowedTags
        );
    }
    
  2. Service Provider Integration:

    // app/Providers/AppServiceProvider.php
    public function register()
    {
        $this->app->singleton('filter', function () {
            return new InputFilter();
        });
    }
    

    Inject via constructor:

    public function __construct(private InputFilter $filter) {}
    
  3. Dynamic Whitelists:

    // Allow tags based on user role
    $allowedTags = auth()->user()->role === 'admin' ? ['div', 'span'] : ['p', 'b'];
    $cleanHtml = $this->filter->clean($input, InputFilter::ONLY_ALLOW_DEFINED_TAGS, $allowedTags);
    
  4. Attribute-Level Control:

    // Allow only href in <a> tags
    $cleanHtml = InputFilter::clean(
        $input,
        InputFilter::ONLY_ALLOW_DEFINED_TAGS | InputFilter::ONLY_ALLOW_DEFINED_ATTRIBUTES,
        ['a'],
        ['href']
    );
    
  5. Defense in Depth (recommended after 3.0.7): Treat the filter as one layer, not the only one. Because 3.0.7 fixed evasion bugs — meaning prior versions silently let payloads through — assume future ones are possible and pair the filter with:

    // Escape on output by default; only unescape what you filtered
    {!! $post->rendered_html !!}   {{-- filtered at write time --}}
    {{ $post->title }}             {{-- plain escaped --}}
    
    // A CSP header is the cheap backstop
    $response->headers->set(
        'Content-Security-Policy',
        "default-src 'self'; script-src 'self'"
    );
    

Workflows

  1. Form Handling:

    • Use in validate() or a dedicated sanitizing step of your Form Request. (Laravel does not call a sanitize() method automatically — call it explicitly via prepareForValidation() or in the controller.)
    • Example:
      public function rules()
      {
          return ['content' => 'required|string'];
      }
      
      protected function prepareForValidation(): void
      {
          $this->merge([
              'content' => InputFilter::clean(
                  $this->input('content'),
                  InputFilter::ONLY_ALLOW_DEFINED_TAGS,
                  ['p', 'b']
              ),
          ]);
      }
      
  2. API Payloads:

    • Sanitize JSON inputs before processing:
      $data = json_decode($request->getContent(), true);
      $data['description'] = InputFilter::clean($data['description'], ...);
      
  3. CMS Content:

    • Integrate with Laravel Nova’s DetailPanel or custom fields:
      // Nova Field
      public function resolve($resource, $attribute)
      {
          return InputFilter::clean($resource->$attribute, ...);
      }
      

Integration Tips

  1. Laravel Validation Rules: Extend Laravel’s validation with custom rules:

    use Joomla\Filter\InputFilter;
    
    class Sanitize extends FormRequest
    {
        public function rules()
        {
            return ['bio' => ['required', new SanitizeRule(['p', 'a'])]];
        }
    }
    
    class SanitizeRule implements ValidationRule
    {
        public function __construct(private array $allowedTags) {}
    
        public function validate(string $attribute, mixed $value, Closure $fail): void
        {
            $clean = InputFilter::clean($value, InputFilter::ONLY_ALLOW_DEFINED_TAGS, $this->allowedTags);
    
            if ($clean === false) {
                $fail("The {$attribute} contains disallowed markup.");
            }
        }
    }
    

    Prefer rejecting dangerous input over silently stripping it for fields where loss of content is surprising — silent stripping plus an unescaped render path is how bypasses become incidents.

  2. Blade Directives: Create a custom Blade directive for sanitized output:

    // app/Providers/BladeServiceProvider.php
    Blade::directive('sanitize', function ($expression) {
        return "<?php echo InputFilter::clean({$expression}, InputFilter::ONLY_ALLOW_DEFINED_TAGS, ['p', 'b']); ?>";
    });
    

    Usage:

    {!! sanitize($user->bio) !!}
    

    Be aware this puts sanitization at render time on every request — no caching, and a bug here is a live XSS. Filter on write, store both raw and cleaned copies, and render the stored cleaned copy.

  3. Event Listeners: Sanitize data on model events (e.g., saving):

    public function saving(Post $post)
    {
        $post->content = InputFilter::clean(
            $post->content,
            InputFilter::ONLY_ALLOW_DEFINED_TAGS,
            ['h1', 'p', 'ul', 'li']
        );
    }
    

    Note: rows written before the 3.0.7 upgrade were sanitized with the buggy filter. Store-time filtering does not retroactively clean historical data — see the backfill gotcha below.

Workflows (Security Patch Rollout)

  1. Upgrade:
    composer require joomla/filter "~3.0.7" --with-dependencies
    composer show joomla/filter   # verify the resolved version
    
  2. Confirm the constraint isn't held back:
    composer why-not joomla/filter 3.0.7
    
  3. Re-run your sanitizer regression suite against known payloads (below).
  4. Backfill any stored HTML written under 3.0.6 or earlier — a queued job that re-runs the filter over affected columns is cheap insurance:
    Post::query()->where('updated_at', '<', $cutover)->chunkById(200, function ($posts) {
        foreach ($posts as $post) {
            $post->content = InputFilter::clean($post->content, InputFilter::ONLY_ALLOW_DEFINED_TAGS, ['p', 'a', 'ul', 'li']);
            $post->saveQuietly();
        }
    });
    

Gotchas and Tips

Pitfalls

  1. Security Patch, Not a Feature Release:

    • 3.0.7 fixes two XSS filter bypasses: whitespace characters inside HTML data: URIs, and an HTML5 entity-decode mismatch.
    • Both are bypasses, i.e. payloads that previously passed sanitization unchanged. Anything you filtered with ≤3.0.6 is potentially tainted.
    • Fix: Pin ~3.0.7 (or the equivalent 4.x release) and backfill stored content.
  2. Whitespace-in-data: URI Bypass:

    • Browsers tolerate whitespace inside URIs (java\tscript:, data:text/html;base64,... with embedded newlines). Pre-3.0.7 the filter's URI check could be defeated by inserting whitespace.
    • Fix: 3.0.7 normalizes before checking. Don't reimplement URI checks yourself; also strip data:/javascript: schemes at the attribute level by not whitelisting src for untrusted authors.
  3. HTML5 Entity-Decode Mismatch Bypass:

    • PHP's decoder and the browser's HTML5 tokenizer disagree on certain entities (notably ones that decode to <, >, : or &). Pre-3.0.7 a payload could look harmless to the filter and dangerous to the browser.
    • Fix: 3.0.7 aligns the decode logic. Regression-test with entity-encoded payloads, not just literal ones.
  4. PHP Version Mismatch:

    • v3.x: Requires PHP 8.1+.
    • v4.x: Requires PHP 8.3+.
    • Fix: Use ~3.0.7 for older Laravel projects (e.g., Laravel 9).
  5. False Positives in Sanitization:

    • Aggressive filtering may break legitimate HTML (e.g., <img src="..."> if src isn’t whitelisted).
    • Fix: Test with real-world HTML snippets (e.g., from TinyMCE or CKEditor outputs).
  6. Nested Tag Issues:

    • Older versions (<3.0.2) had case-sensitivity bugs in stripImages/stripIframes.
    • Fix: Upgrade to ~3.0.7 or later.
  7. Attribute Filtering Quirks:

    • ONLY_ALLOW_DEFINED_ATTRIBUTES can strip valid attributes if not configured carefully.
    • Fix: Explicitly whitelist attributes (e.g., ['href', 'title'] for <a> tags).
  8. Performance Overhead:

    • Heavy filtering (e.g., large HTML blocks) may slow down requests.
    • Fix: Filter on write, not on read; cache sanitized results.

Debugging

  1. Unexpected Output:

    • Use InputFilter::clean() with InputFilter::RETURN_ERRORS to inspect failures:
      $result = InputFilter::clean($input, InputFilter::RETURN_ERRORS, ['p']);
      if ($result === false) {
          // inspect the filter's error state
      }
      
  2. XSS Evasion Bypasses:

    • Test with payloads like:
      <script>alert(1)</script>
      <img src=x onerror=alert(1)>
      <a href="javascript:alert(1)">Click</a>
      
    • Add the 3.0.7 regression cases to your fixtures:
      <!-- whitespace inside URI scheme / data URI -->
      <a href="java&#9;script:alert(1)">x</a>
      <a href="java script:alert(1)">x</a>
      <img src="data:text/html;base64,
      PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">
      <iframe src="data:text/html,
      <script>alert(1)</script>"></iframe>
      
      <!-- entity-decode mismatch -->
      <a href="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;alert(1)">x</a>
      <a href="&colon;javascript&colon;alert(1)">x</a>
      <svg><a xlink:href="&#x6a;avascript:alert(1)">x</a></svg>
      
    • Fix: Assert the output is inert (no script, no javascript:/data: scheme after decode), not that the input string appears in the output.
  3. Whitelist Misconfigurations:

    • Dump the resolved configuration rather than assuming defaults:
      $filter = new InputFilter();
      dump($filter->getAllowedTags(), $filter->getAllowedAttributes());
      
  4. Verifying You're Actually Patched:

    php -r '$l = json_decode(file_get_contents("composer.lock"), true);
    foreach ($l["packages"] as $p) if ($p["name"] === "joomla/filter") echo $p["version"], PHP_EOL;'
    

    Then grep the vendor source for the whitespace-normalization change in the URI validation path — if it isn't there, you're on a stale build even if the constraint looks right.

Config Quirks

  1. Static vs. Instance Methods:

    • Static methods (e.g., InputFilter::clean()) use global config.
    • Instance methods allow per-object customization:
      $filter = new InputFilter();
      $filter->setAllowedTags(['div', 'span']);
      $clean = $filter->clean($input);
      
    • Security-relevant behavior lives in the shared global config path; if you override config per-instance, make sure the static path (used by any legacy call sites) stays equally strict.
  2. OutputFilter Dependencies:

    • OutputFilter::stringURLSafe() requires joomla/language package.
    • Fix: Install via Composer if needed:
      composer require joomla/language
      
  3. Legacy Constant Names:

    • Older code may use deprecated constants (e.g., TAGS_WHITELIST).
    • Fix: Update to new names (e.g., ONLY_ALLOW_DEFINED_TAGS).

Extension Points

  1. Custom Filters: Extend InputFilter to add domain-specific rules:

    class CustomFilter extends InputFilter
    {
        public function cleanCustom($input)
        {
            return $this->clean($input, InputFilter::ONLY_ALLOW_DEFINED_TAGS, ['custom']);
        }
    }
    

    If you override URI or entity handling, you now own that security surface — 3.0.7's fixes are exactly the kind of thing a well-meaning local override can silently reintroduce. Prefer subclassing for additions, not for reimplementing validation.

  2. Event-Driven Sanitization: Use Laravel events to trigger sanitization:

    // In a service provider
    event(new SanitizeEvent($input));
    

    Handle with a listener:

    public function handle(SanitizeEvent $event)
    {
        $event->setSanitized(InputFilter::clean($event->input, ...));
    }
    
  3. Testing Helpers: Create a small assertion helper so every feature test uses the same payload corpus, including the 3.0.7 cases:

    trait AssertsSanitizedHtml
    {
        public function assertSanitized(string $input, array $allowedTags = ['p', 'a']): void
        {
            $out = \Joomla\Filter\InputFilter::clean(
                $input,
                \Joomla\Filter\InputFilter::ONLY_ALLOW_DEFINED_TAGS,
                $allowedTags
            );
    
            $this->assertStringNotContainsStringIgnoringCase('script', (string) $out);
            $this->assertDoesNotMatchRegularExpression(
                '/(javascript|vbscript|data)\s*:/i',
                html_entity_decode((string) $out, ENT_QUOTES | ENT_HTML5)
            );
        }
    }
    

    Decoding entities before asserting is the point — a bypass that survives the filter usually only becomes visible after the browser-style decode.

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.
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names