SecurityInputFilter is the request-level input guard for
high-confidence XSS detection. It inspects GET and POST values before
controller logic runs, canonicalizes user input to reduce encoding bypasses,
and blocks the request when a known dangerous pattern is detected.
Input Security Guard because the
filter is not only about sanitizing forms. It is a request gate that checks
user-controlled input before normal application logic continues.
The filter is globally registered in app/Config/Filters.php in
the before chain, which means it runs for normal incoming web
requests unless the route is explicitly excluded.
'SecurityInputFilter' => SecurityInputFilter::class,
'before' => [
'SecurityInputFilter' => [
'except' => [
'/client/notification/create',
'/ticket/crud_mail_template/*',
'test_mail',
'leads/sendMail',
'ticket/reply'
]
],
]
The filter only reads:
$request->getGet()$request->getPost()
It does not inspect uploaded file contents. File uploads are handled by the
separate GlobalPostFileUploadGuard filter.
The filter uses a focused list of high-confidence XSS patterns to reduce false positives while still blocking obvious injection attempts.
| Category | Examples from the filter |
|---|---|
| Script tags | <script, </script> |
| JavaScript execution schemes | javascript:, vbscript:, data:text/html |
| Inline event handlers | onclick=, onerror=, onload= |
| Dangerous HTML tags | <iframe, <object, <embed, <applet, <img |
| SVG / MathML vectors | <svg, <math |
| Meta refresh payloads | <meta http-equiv="refresh" |
| Injected src/href handlers | HTML tags using src=javascript: or href=data: |
Before pattern matching, the filter canonicalizes each value:
Helps catch encoded payloads that would otherwise bypass naive matching.
Turns entity-encoded payloads into their real characters before detection.
Removes null bytes and other control characters from the evaluation string.
Reduces noise before regex evaluation.
private function canonicalize(string $value): string
{
$value = urldecode($value);
$value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
return preg_replace('/[\x00-\x1F\x7F]/u', '', $value);
}
Array inputs are converted to JSON first, then canonicalized as a string.
When a pattern matches, the filter logs security metadata and immediately
returns a JSON 403 response:
| Logged field | Purpose |
|---|---|
ip | Source IP address |
method | Request method |
uri | Current request URL |
field | Input field name |
attack | Static marker XSS_PATTERN |
length | Canonicalized payload length |
hash | SHA-256 hash of the canonicalized payload |
The raw input value is not logged directly. The filter logs intent metadata and a hash instead.
{
"status": 403,
"error": "Forbidden",
"message": "Malicious input detected"
}
Some routes are explicitly excluded from the global security-input filter:
| Excluded route | Why developers should care |
|---|---|
/client/notification/create |
Global input blocking does not run here. |
/ticket/crud_mail_template/* |
Template-editing paths often need richer content and should be handled deliberately. |
test_mail |
Bypassed globally. |
leads/sendMail |
Bypassed globally. |
ticket/reply |
Bypassed globally. |
When building a new form, endpoint, or feature that accepts user input, follow this checklist:
If your route is not in the exception list, the filter already evaluates GET and POST fields before controller code runs.
Business validation, field-level validation, and output escaping are still required.
If a feature legitimately accepts formatted HTML, do not silently fight the filter. Design a safe path for that route and document why it needs special handling.
If you exclude a route in Filters.php, add compensating server-side sanitization or allowlist logic in the receiving code.
Because the filter canonicalizes input, test URL-encoded and HTML-entity-encoded payloads in addition to plain strings.
The filter logs a structured critical event named SECURITY_BLOCKED_REQUEST with a payload hash and field name.
| Pitfall | Why it happens |
|---|---|
| A rich text feature keeps returning 403 | The submitted markup matches one of the high-confidence XSS patterns, and the route is still under the global filter. |
| Input looks harmless in raw form but still gets blocked | The canonicalization step decoded the payload into a dangerous form before matching. |
| A developer adds an exception without extra protection | The route bypasses the global blocker and now depends entirely on downstream validation. |
| Files are assumed to be covered here | Uploaded file contents are handled by the separate file-upload guard, not this filter. |
SecurityInputFilter as the first request-level XSS tripwire.
Keep it on by default, make exceptions rarely, and document every exception
with the safer validation path that replaces it.