flowd / phirewall
A PHP Firewall and rate limiter based on PSR-7 and PSR-15 middleware (safelists, blocklists, throttles, fail2ban)
Installs: 1 018
Dependents: 1
Suggesters: 0
Security: 0
Stars: 2
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/flowd/phirewall
Requires
- php: >=8.2
- psr/event-dispatcher: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^1.1 || ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/simple-cache: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.89
- infection/infection: ^0.29
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^11.5
- rector/rector: ^1.2
Suggests
- ext-apcu: Optional: use ApcuCache for fast in-process counters (enable with apc.enable_cli=1 for CLI)
- predis/predis: Optional: use RedisCache for distributed counters
This package is not auto-updated.
Last update: 2026-02-05 00:16:12 UTC
README
Protect your PHP application from brute force, DDoS, SQL injection, XSS, and bot attacks with a single middleware.
Phirewall is a PSR-15 middleware that provides comprehensive application-layer protection. It's lightweight, framework-agnostic, and easy to configure.
Why Phirewall?
- Simple Setup - Add protection in minutes with sensible defaults
- Multiple Attack Vectors - Rate limiting, brute force protection, OWASP rules, bot detection
- Framework Agnostic - Works with any PSR-15 compatible framework (Laravel, Symfony, Slim, Mezzio, etc.)
- Production Ready - Redis support for multi-server deployments
- Observable - PSR-14 events for logging, metrics, and alerting
Quick Start
composer require flowd/phirewall
use Flowd\Phirewall\Config; use Flowd\Phirewall\Middleware; use Flowd\Phirewall\KeyExtractors; use Flowd\Phirewall\Store\InMemoryCache; // Create the firewall $config = new Config(new InMemoryCache()); // Allow health checks to bypass all rules $config->safelist('health', fn($req) => $req->getUri()->getPath() === '/health'); // Block common scanner paths $config->blocklist('scanners', fn($req) => str_starts_with($req->getUri()->getPath(), '/wp-admin')); // Rate limit: 100 requests per minute per IP $config->throttle('api', limit: 100, period: 60, key: KeyExtractors::ip()); // Ban IP after 5 failed logins in 5 minutes $config->fail2ban('login', threshold: 5, period: 300, ban: 3600, filter: fn($req) => $req->getHeaderLine('X-Login-Failed') === '1', key: KeyExtractors::ip() ); // Add to your middleware stack $middleware = new Middleware($config);
Add the middleware to your PSR-15 pipeline. All requests will be evaluated against your rules before reaching your application.
Try It Now
Run one of the included examples to see Phirewall in action:
# Basic setup demo php examples/01-basic-setup.php # See brute force protection php examples/02-brute-force-protection.php # Test SQL injection blocking php examples/04-sql-injection-blocking.php # Full production setup php examples/08-comprehensive-protection.php
Examples
The examples/ folder contains 15 runnable examples:
| # | Example | Description |
|---|---|---|
| 01 | basic-setup | Minimal configuration to get started |
| 02 | brute-force-protection | Fail2Ban-style login protection |
| 03 | api-rate-limiting | Tiered rate limits for APIs |
| 04 | sql-injection-blocking | OWASP-style SQLi detection |
| 05 | xss-prevention | Cross-Site Scripting protection |
| 06 | bot-detection | Scanner and malicious bot blocking |
| 07 | ip-blocklist | File-backed IP/CIDR blocklists |
| 08 | comprehensive-protection | Production-ready multi-layer setup |
| 09 | observability-monolog | Event logging with Monolog |
| 10 | observability-opentelemetry | Distributed tracing with OpenTelemetry |
| 11 | redis-storage | Redis backend for multi-server deployments |
| 12 | apache-htaccess | Apache .htaccess IP blocking |
| 13 | benchmarks | Storage backend performance comparison |
| 14 | owasp-crs-files | Loading OWASP CRS rules from files |
| 15 | in-memory-pattern-backend | Configuration-based CIDR/IP blocklists |
Features
Protection Layers
| Feature | Description |
|---|---|
| Safelists | Bypass all checks for trusted requests (health checks, internal IPs) |
| Blocklists | Immediately deny suspicious requests (403) |
| Throttling | Rate limit by IP, user, API key, or custom key (429) |
| Fail2Ban | Auto-ban after repeated failures |
| OWASP CRS | SQL injection, XSS, and PHP injection detection |
| Pattern Backends | File/Redis-backed blocklists with IP, CIDR, path, and header patterns |
Observability
- PSR-14 Events -
SafelistMatched,BlocklistMatched,ThrottleExceeded,Fail2BanBanned - Diagnostics Counters - Per-rule statistics for monitoring
- Standard Headers -
X-RateLimit-*,Retry-After,X-Phirewall-*
Storage Backends
| Backend | Use Case |
|---|---|
InMemoryCache |
Development, testing, single requests |
ApcuCache |
Single-server production |
RedisCache |
Multi-server production |
Documentation
For detailed documentation, see the docs/ folder:
- Getting Started - Step-by-step setup guide
- Common Attacks - Protection recipes for 10+ attack types
- Configuration - Complete API reference
- Storage Backends - Redis, APCu, and custom backends
- Pattern Backends - IP, CIDR, path, and header blocklists
- OWASP CRS - Loading and customizing rules
- Observability - Events, logging, and monitoring
- Infrastructure Adapters - Apache .htaccess integration
Installation
composer require flowd/phirewall
Optional Dependencies
# For Redis-backed distributed counters (multi-server) composer require predis/predis # For Monolog logging integration composer require monolog/monolog
APCu: Enable the PHP extension and set apc.enable_cli=1 for CLI testing.
Response Headers
When a request is blocked:
| Header | Description |
|---|---|
X-Phirewall |
Block type: blocklist, throttle, fail2ban |
X-Phirewall-Matched |
Rule name that triggered |
Retry-After |
Seconds until rate limit resets (429 only) |
Enable $config->enableRateLimitHeaders() for standard X-RateLimit-* headers.
Client IP Behind Proxies
When behind load balancers or CDNs, use TrustedProxyResolver:
use Flowd\Phirewall\Http\TrustedProxyResolver; use Flowd\Phirewall\KeyExtractors; $resolver = new TrustedProxyResolver([ '10.0.0.0/8', // Internal network '172.16.0.0/12', // Docker ]); $config->throttle('api', limit: 100, period: 60, key: KeyExtractors::clientIp($resolver) );
Custom Responses
Customize blocked responses while keeping standard headers:
$config->blocklistedResponse(function (string $rule, string $type, $req) { return new Response(403, ['Content-Type' => 'application/json'], json_encode(['error' => 'Blocked', 'rule' => $rule]) ); }); $config->throttledResponse(function (string $rule, int $retryAfter, $req) { return new Response(429, ['Content-Type' => 'application/json'], json_encode(['error' => 'Rate limited', 'retry_after' => $retryAfter]) ); });
OWASP Core Rule Set
Load OWASP-style rules for SQL injection, XSS, and more:
use Flowd\Phirewall\Owasp\SecRuleLoader; $rules = SecRuleLoader::fromString(<<<'CRS' SecRule ARGS "@rx (?i)\bunion\b.*\bselect\b" "id:942100,phase:2,deny,msg:'SQLi'" SecRule ARGS "@rx (?i)<script[^>]*>" "id:941100,phase:2,deny,msg:'XSS'" CRS); $config->owaspBlocklist('owasp', $rules);
Or load from files:
$rules = SecRuleLoader::fromDirectory('/path/to/crs-rules');
See 04-sql-injection-blocking.php and 05-xss-prevention.php for complete examples.
Real-World Recipes
API Rate Limiting
// Global limit $config->throttle('global', limit: 1000, period: 60, key: KeyExtractors::ip()); // Burst detection $config->throttle('burst', limit: 30, period: 5, key: KeyExtractors::ip()); // Higher limits for authenticated users $config->throttle('user', limit: 5000, period: 60, key: KeyExtractors::header('X-User-Id'));
Login Protection
// Throttle login attempts $config->throttle('login', limit: 10, period: 60, key: function($req) { return $req->getUri()->getPath() === '/login' ? $req->getServerParams()['REMOTE_ADDR'] : null; }); // Ban after failures $config->fail2ban('login-ban', threshold: 5, period: 300, ban: 3600, filter: fn($req) => $req->getHeaderLine('X-Login-Failed') === '1', key: KeyExtractors::ip() );
Bot Detection
$scanners = ['sqlmap', 'nikto', 'nmap', 'burp', 'dirbuster']; $config->blocklist('scanners', function($req) use ($scanners) { $ua = strtolower($req->getHeaderLine('User-Agent')); foreach ($scanners as $scanner) { if (str_contains($ua, $scanner)) return true; } return false; });
Development
# Run tests composer test # Fix code style composer fix # Mutation testing composer test:mutation
Sponsors
This project received funding from TYPO3 Association through its Community Budget program.
License
Dual licensed under LGPL-3.0-or-later and proprietary. See LICENSE for details.