A unified PSR-15 security middleware pack for modern PHP.
Individual PSR-15 security middleware packages on Packagist are mostly written for PHP 7.x, poorly maintained, and inconsistent in API design. Developers assembling a security middleware stack must find, evaluate, and configure 5-6 separate packages.
Shield provides a single, modern PSR-15 middleware pack with consistent API, sensible defaults, and full customization via PHP 8 attributes.
- Security Headers — HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy
- CSP Builder — Fluent Content-Security-Policy builder with nonce generation and report-uri support
- CORS Handler — Origin whitelisting, credential support, preflight caching, method/header configuration
- Rate Limiter — Token bucket algorithm, configurable per-route/IP/user, backed by PSR-16 cache
- Request Sanitizer — Strips XSS payloads, normalizes Unicode, trims whitespace, removes null bytes
- IP Filter — Allow/deny lists with individual IPs and CIDR range support
- PHP 8.1+
- Any PSR-15 compatible framework (Slim 4, Mezzio, Laravel, etc.)
composer require kexxt/shield-middlewareuse Shield\Shield;
// All defaults — security headers, CORS, and input sanitization
$stack = Shield::defaults();
// Add to your PSR-15 pipeline
$app->pipe($stack);use Shield\Shield;
$stack = Shield::create()
->securityHeaders(hsts: true, frameOptions: 'DENY')
->cors(origins: ['https://app.example.com'])
->rateLimit(maxRequests: 100, windowSeconds: 60, cache: $psrCache)
->sanitize(stripTags: true, trimWhitespace: true)
->ipFilter(allow: ['10.0.0.0/8'])
->build();
$app->pipe($stack);Each middleware can be used independently:
use Shield\Middleware\SecurityHeaders;
use Shield\Config\SecurityHeadersConfig;
$middleware = new SecurityHeaders(new SecurityHeadersConfig(
hsts: true,
hstsMaxAge: 31536000,
frameOptions: 'DENY',
referrerPolicy: 'strict-origin-when-cross-origin',
permissionsPolicy: 'camera=(), microphone=(), geolocation=()',
));use Shield\Middleware\ContentSecurityPolicy;
use Shield\Config\CspConfig;
$csp = CspConfig::create()
->defaultSrc(["'self'"])
->scriptSrc(["'self'", 'https://cdn.example.com'])
->styleSrc(["'self'", "'unsafe-inline'"])
->withNonce()
->reportTo('https://example.com/csp-report');
$middleware = new ContentSecurityPolicy($csp);
// Access nonce in your templates via request attribute:
$nonce = $request->getAttribute('csp-nonce');use Shield\Middleware\Cors;
use Shield\Config\CorsConfig;
$middleware = new Cors(new CorsConfig(
allowedOrigins: ['https://app.example.com', 'https://admin.example.com'],
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
allowCredentials: true,
maxAge: 86400,
));use Shield\Middleware\RateLimiter;
use Shield\Config\RateLimiterConfig;
$middleware = new RateLimiter(
cache: $psrCache, // Any PSR-16 CacheInterface
config: new RateLimiterConfig(
maxRequests: 100,
windowSeconds: 60,
keyResolver: fn($request) => 'user_' . $request->getAttribute('user_id'),
),
);Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After (on 429).
use Shield\Middleware\RequestSanitizer;
use Shield\Config\SanitizerConfig;
$middleware = new RequestSanitizer(new SanitizerConfig(
stripTags: true,
trimWhitespace: true,
removeNullBytes: true,
normalizeUnicode: true,
));use Shield\Middleware\IpFilter;
use Shield\Config\IpFilterConfig;
$middleware = new IpFilter(new IpFilterConfig(
allow: ['10.0.0.0/8', '172.16.0.0/12'],
deny: ['10.0.0.1'], // Deny takes precedence over allow
));Per-route configuration via attributes:
use Shield\Attribute\RateLimit;
use Shield\Attribute\CorsOrigins;
use Shield\Attribute\ShieldRoute;
#[RateLimit(maxRequests: 10, windowSeconds: 60)]
#[CorsOrigins('https://app.example.com')]
public function sensitiveEndpoint(): Response
{
// ...
}
#[ShieldRoute(rateLimit: 50, corsOrigins: ['https://app.example.com'], ipAllow: ['10.0.0.0/8'])]
public function adminEndpoint(): Response
{
// ...
}When using Shield::create()->build(), middleware executes in this order:
- IP Filter — Block disallowed IPs early
- Rate Limiter — Enforce rate limits
- CORS — Handle preflight and add CORS headers
- Request Sanitizer — Clean input data
- Security Headers — Add security response headers
- CSP — Add Content-Security-Policy header
# Install dependencies
composer install
# Run tests
./vendor/bin/pest
# Static analysis
./vendor/bin/phpstan analyse- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Write tests for your changes
- Ensure all tests pass (
./vendor/bin/pest) - Ensure PHPStan passes (
./vendor/bin/phpstan analyse) - Commit your changes
- Push and create a Pull Request
MIT License. See LICENSE for details.