psr15-guard
symfony-guard is the official Symfony adapter for
guard-core-php, the PHP port of the
guard-core security engine. It wraps your HttpKernelInterface kernel with the
full engine pipeline: penetration detection, rate limiting, IP banning, and
verdict responses translated back to Symfony-native responses.
All security logic lives in the engine; this package is a thin shim that
translates a HttpFoundation Request into a GuardRequest, runs the engine,
and translates the block verdict back to a Response when one arrives.
Installation
composer require rennf93/psr15-guard
Requires PHP 8.2 or later. Until rennf93/guard-core-php has a Packagist
distribution, point composer at its repository and allow dev stability:
{
"minimum-stability": "dev",
"prefer-stable": true,
"repositories": [
{ "type": "vcs", "url": "https://github.com/rennf93/guard-core-php" }
]
}
Quick start
use RenzoFranceschini\GuardCore\Config\SecurityConfig;
use RenzoFranceschini\GuardCore\Engine\GuardEngine;
use RenzoFranceschini\GuardCoreSymfony\GuardMiddleware;
$kernel = new GuardMiddleware(
new App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']),
new GuardEngine(new SecurityConfig(
enableRedis: true,
redisPrefix: 'guard_core:',
rateLimit: 100,
rateLimitWindow: 60,
))
);
// public/index.php
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Blocked requests get the engine's GuardResponse translated exactly (status,
body, headers). Passing requests continue down the stack untouched.
What the middleware handles
- Client identity:
$request->getClientIp()(Symfony trusted-proxy resolution applies), with the engine's owntrustedProxiesavailable as an alternative; pick one side to do the resolving - Headers: joined into the engine's case-insensitive
HeaderBag - Body: the first 256 KiB are shown to the engine
(
SymfonyGuardRequest::MAX_BODY_BYTES) - Main request only: sub-requests pass straight into the wrapped kernel, so fragments and forwards are not double-counted by rate limits
- Fail-closed: engine throws become
500with a fixed, non-leaky message - Statelessness: the middleware holds no mutable state; PHP shared-nothing applies (per request under FPM, per worker under FrankenPHP/RoadRunner/Octane)
See Usage for the full adapter surface and Configuration for engine tuning. Runnable apps live in the examples directory.