Search by

wobqqq / nova-aegis-input-sanitizer

wobqqq

Aegis module for Laravel Nova: blocks XSS, template injection, command injection and path traversal payloads before they reach the application

v1.1.0 2026-10-01 19:58 UTC

README

CI Packagist PHP PHPStan License: MIT

Input Sanitizer is a module of Aegis, the security suite for Laravel Nova. It refuses a request whose query string, body, headers or URL carry an XSS, template injection, command injection or path traversal payload before it reaches your application, and is configured from the Aegis → Settings page.

🚀 Features

  • Seven kinds of payload, one editable regular expression each: XSS, encoded XSS, command injection, path traversal, template injection ({{ }}, {% %}, {!! !!}), null bytes and CSV injection.
  • The whole request is scored: the query string, the form body, JSON bodies (optional), the input names, the headers and the URL segments, each URL-decoded up to three times and HTML-entity-decoded. Each match adds one; the request is refused once the score reaches the threshold.
  • 400 with your page: any Blade view, or the built-in one; a JSON client gets a JSON message.
  • Safe with a broken pattern: a pattern that does not compile is refused when it is saved and skipped if an older row still holds it; a pattern that gives up on a long input (PCRE backtrack limit) is no match. Neither ever turns into a 500.
  • Nova keeps working: Nova's own requests (rich-text fields send HTML on purpose) are not scanned unless you turn it on, and the Aegis settings are never scanned, so a pattern that is too broad can always be fixed.
  • Exclusions: input names or dotted paths (content, post.body) and headers that are never scanned. Cookie and Accept are never scanned.
  • Cheap on every request: the settings are cached (no database query per request) and cleared as soon as they are saved.
  • Logs without values: a blocked request is logged with the IP, the method and where each match was (query.q (xss)), never the value.
  • Dashboard and checks: a status line on the Aegis dashboard, and a check that warns about a saved pattern that is skipped or a page that is gone (also in php artisan aegis:check).

📦 Requirements

  • PHP 8.4 or higher
  • Laravel 12 or 13
  • Laravel Nova 5
  • Aegis 1.1 or later (installed with the module)

📥 Installation

1. Install the package

composer require wobqqq/nova-aegis-input-sanitizer

The service provider is discovered automatically.

2. Run the migrations

php artisan migrate

This creates the Aegis settings table if the core is new to the application; the module adds no table of its own.

3. Set up Aegis (once per application)

If Aegis is new to the application, register its tool and define the viewAegis gate as the Aegis README describes. Skip this step if you already use another Aegis module.

4. Turn it on in Nova

Open Aegis → Settings → Input Sanitizer in Nova, review the patterns and the excluded inputs, switch Scan the requests on and save. Nothing is blocked until you do.

5. Customise the blocked-request page (optional)

Publish the built-in page, or name any view of your own in the settings:

php artisan vendor:publish --tag=aegis-input-sanitizer-views

⚙️ Configuration

Everything is set in the Input Sanitizer section of the Aegis settings:

Setting Default
Scan the requests off Nothing is scanned until it is on.
Block at score 1 Refuse a request once this many pattern matches are found in it (1 to 1000).
Page shown to a blocked request aegis-input-sanitizer::blocked A Blade view name; it must exist when saved, and the built-in page is used if it is gone later.
Scan JSON request bodies off Off, JSON bodies pass unscanned (their query string, headers and URL are still scanned).
Scan Nova requests too off Nova's routes (nova.path, nova-api, nova-vendor). The Aegis settings API is never scanned.
Log blocked requests on A warning in the default log channel, without values.
Patterns one per kind A PCRE pattern with its delimiters (~<script~i), up to 1000 characters; leave one empty to turn that kind off.
Inputs never scanned none Input names or dotted paths, case-insensitive, up to 150.
Headers never scanned none Header names, case-insensitive, up to 150. Cookie and Accept always.

The settings are cached in the store named by the core's aegis.cache_store (AEGIS_CACHE_STORE), the default store otherwise.

🆘 Recovery commands

If a pattern that is too broad blocks your site or your API, turn the module off from the console. The other settings are kept:

php artisan aegis:input-sanitizer:disable

The Aegis page in Nova stays reachable while the module is on (unless you enabled Scan Nova requests too and a pattern blocks Nova itself), so you can also fix the pattern there. php artisan aegis:check reports a saved pattern that is skipped.

⚠️ Good to know

  • Defence in depth, not a firewall. It stops common payloads, not every attack. Keep validating input and escaping output in your own code.
  • Not scanned: uploaded files, cookies, JSON bodies unless enabled, and Nova unless enabled. With JSON scanning off, a client can send its payload as JSON: turn it on once your APIs accept no HTML.
  • Headers are visitor input. Every header a client sends is scanned, including User-Agent and Referer: a command-line client whose user agent starts with curl/ matches the default command injection pattern. Exclude a header (user-agent) rather than weakening a pattern.
  • Proxies add headers. A load balancer, a CDN or a proxy adds its own headers (X-Forwarded-*, CF-*, Forwarded, …) to every request. They are scanned like any other: if one of them carries characters the patterns flag, exclude it by name. Remember that any of these headers can also be sent, forged, by the client itself.
  • The logged IP is $request->ip(). Behind a proxy or a load balancer it is the proxy's address unless the proxy is configured as a trusted proxy in Laravel (trustProxies); never trust forwarded headers from an untrusted source.
  • Rich-text editors send HTML on purpose. Outside Nova, add their input names to Inputs never scanned, or every save is refused.
  • A loose threshold is a weaker filter. A threshold above 1 lets a request through with fewer matches; each pattern counts once per value.
  • Long-running workers (Octane, queues) read the settings again on every request; a save clears the cache for every server sharing the cache store.

⬆️ Upgrading

See CHANGELOG.md.

🔒 Security

Please report a vulnerability privately, as described in SECURITY.md.

🛠️ Development

The toolchain runs in Docker, the host needs nothing but docker and make. The module is developed against the core's checkout in the sibling directory ../nova-aegis (a Composer path repository; the container mounts the parent directory). No Nova license is needed: development and CI run on a test double of Nova in stubs/nova (installed as laravel/nova from a path repository, never shipped). Applications still install the real Nova.

make install        # composer install
make code.fix       # composer normalize, Rector, PHP CS Fixer
make code.check     # composer validate/audit, php -l, PHP CS Fixer, Rector, PHPStan (level max)
make test.coverage  # Pest with coverage (90 % minimum)
make ready          # everything above
make test.nova      # optional: the PHP suite on the real Nova

make test.nova copies the repository to a temporary directory, installs the real laravel/nova from nova.laravel.com there and runs Pest; it needs your own Nova license in auth.json (gitignored), and NOVA_VERSION=5.9.3 make test.nova picks a release your license may download. The working copy, its vendor/ and composer.lock are left untouched.