Skip to content

ndcorder/php-shield-middleware

Repository files navigation

php-shield-middleware

A unified PSR-15 security middleware pack for modern PHP.

CI

Why?

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.

Features

  • 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

Requirements

  • PHP 8.1+
  • Any PSR-15 compatible framework (Slim 4, Mezzio, Laravel, etc.)

Installation

composer require kexxt/shield-middleware

Quick Start

use Shield\Shield;

// All defaults — security headers, CORS, and input sanitization
$stack = Shield::defaults();

// Add to your PSR-15 pipeline
$app->pipe($stack);

Custom Configuration

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);

Individual Middleware

Each middleware can be used independently:

Security Headers

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=()',
));

CSP Builder

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');

CORS Handler

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,
));

Rate Limiter

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).

Request Sanitizer

use Shield\Middleware\RequestSanitizer;
use Shield\Config\SanitizerConfig;

$middleware = new RequestSanitizer(new SanitizerConfig(
    stripTags: true,
    trimWhitespace: true,
    removeNullBytes: true,
    normalizeUnicode: true,
));

IP Filter

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
));

PHP 8 Attributes

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
{
    // ...
}

Middleware Stack Order

When using Shield::create()->build(), middleware executes in this order:

  1. IP Filter — Block disallowed IPs early
  2. Rate Limiter — Enforce rate limits
  3. CORS — Handle preflight and add CORS headers
  4. Request Sanitizer — Clean input data
  5. Security Headers — Add security response headers
  6. CSP — Add Content-Security-Policy header

Development

# Install dependencies
composer install

# Run tests
./vendor/bin/pest

# Static analysis
./vendor/bin/phpstan analyse

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (./vendor/bin/pest)
  5. Ensure PHPStan passes (./vendor/bin/phpstan analyse)
  6. Commit your changes
  7. Push and create a Pull Request

License

MIT License. See LICENSE for details.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors