Firewall
With the Codefy Firewall, every HTTP request is monitored for SQL injection, XSS, RCE and other attacks. If a request is a threat, it is blocked.
Example Config
<?php
declare(strict_types=1);
return [
'enabled' => true,
'block' => true,
'log_payload' => false,
'alert_min_severity' => 'high',
'notifiers' => [],
'ignored_paths' => [
'/favicon.ico',
'/robots.txt',
],
'sql_injection' => [],
'xss' => [],
// Remote Code Execution
'rce' => [],
'file_traversal' => [],
// Server-Side Request Forgery
'ssrf' => [],
'scanner_path_probe' => [],
'sensitive_file_probe' => [],
'php_probe' => [],
];
How It Works
- A middleware scans every incoming HTTP request.
- The request is checked against regex patterns covering SQL injection, XSS, RCE, file traversal, SSRF, and more.
- If a threat pattern matches, the request is immediately blocked.
Verification
You can verify that the firewall is working by triggering a threat:
- Start your app
- Test A Registered Route
Append a malicious query parameter to any existing route in your app.
Examples
SQL Injection
XSS (Cross-Site Scripting)
Directory Traversal
RCE (Remote Code Execution)
Logging
If you want to log the threats, you will need to create an implementation of Codefy\Framework\Security\Firewall\ThreatLogger. Let's say that you wanted to log the threats in a database, you will need to do something similar to the following:
Create a Log Table
CREATE TABLE threat_logs (
threat_id CHAR(36) PRIMARY KEY,
ip_address VARCHAR(45) NOT NULL,
method VARCHAR(10) NOT NULL,
url TEXT NOT NULL,
user_agent TEXT NULL,
threat_type VARCHAR(64) NOT NULL,
severity VARCHAR(20) NOT NULL,
confidence_score DECIMAL(5,2) NOT NULL DEFAULT 0.00,
matched_pattern VARCHAR(255) NULL,
matched_value TEXT NULL,
request_payload LONGTEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Implement ThreatLogger
<?php
declare(strict_types=1);
namespace Application\Service;
use PDO;
use Psr\Http\Message\ServerRequestInterface;
use Qubus\ValueObjects\Identity\Ulid;
final readonly class PdoThreatLogger
{
public function __construct(
private PDO $pdo,
private bool $logPayload = true,
) {
}
public function log(ServerRequestInterface $request, ThreatMatch $match): void
{
$payload = null;
if ($this->logPayload) {
$payload = json_encode([
'query' => $request->getQueryParams(),
'body' => $request->getParsedBody(),
], JSON_THROW_ON_ERROR);
}
$stmt = $this->pdo->prepare(
'INSERT INTO threat_logs (
threat_id,
ip_address,
method,
url,
user_agent,
threat_type,
severity,
confidence_score,
matched_pattern,
matched_value,
request_payload,
created_at
) VALUES (
:threat_id,
:ip_address,
:method,
:url,
:user_agent,
:threat_type,
:severity,
:confidence_score,
:matched_pattern,
:matched_value,
:request_payload,
CURRENT_TIMESTAMP
)'
);
$stmt->execute([
'threat_id' => Ulid::generateAsString(),
'ip_address' => $this->ip($request),
'method' => $request->getMethod(),
'url' => (string) $request->getUri(),
'user_agent' => $request->getHeaderLine('User-Agent'),
'threat_type' => $match->type,
'severity' => $match->severity,
'confidence_score' => $match->confidence,
'matched_pattern' => mb_substr($match->pattern, 0, 255),
'matched_value' => $match->value,
'request_payload' => $payload,
]);
}
private function ip(ServerRequestInterface $request): string
{
$server = $request->getServerParams();
return $server['HTTP_CF_CONNECTING_IP']
?? $server['HTTP_X_FORWARDED_FOR']
?? $server['REMOTE_ADDR']
?? '0.0.0.0';
}
}
Service Provider
If you are using the skeleton app, then update the Application\Provider\FirewallServiceProvider:
<?php
declare(strict_types=1);
namespace Application\Provider;
use Application\Service\PdoThreatLogger;
use Codefy\Framework\Security\Firewall\ThreatLogger;
use Codefy\Framework\Support\CodefyServiceProvider;
class FirewallServiceProvider extends CodefyServiceProvider
{
public function register(): void
{
$this->codefy->singleton(ThreatLogger::class, function() {
return new PdoThreatLogger($this->codefy->getDbConnection()->pdo)
});
}
}
Then make sure to open ./config/firewall.php, and make sure log_payload is set to true.
Notifier
Along with logging, you can also add notifications by writing an implementation of Codefy\Framework\Security\Firewall\ThreatNotifier:
<?php
declare(strict_types=1);
namespace Application\Service;
use Psr\Http\Message\ServerRequestInterface;
use Codefy\Framework\Security\Firewall\ThreatMatch;
use Codefy\Framework\Security\Firewall\ThreatNotifier;
final readonly class SlackThreatNotifier implements ThreatNotifier
{
public function __construct(
private string $webhookUrl,
) {
}
public function notify(ServerRequestInterface $request, ThreatMatch $match): void
{
if ($this->webhookUrl === '') {
return;
}
$message = [
'text' => sprintf(
"*Firewall blocked request*\nType: `%s`\nSeverity: `%s`\nConfidence: `%s`\nURL: `%s`",
$match->type,
$match->severity,
$match->confidence,
(string) $request->getUri(),
),
];
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode($message, JSON_THROW_ON_ERROR),
'timeout' => 2,
],
]);
@file_get_contents($this->webhookUrl, false, $context);
}
}
Then you will need to add your new notifier to ./config/firewall.php:
///
use Application\Service\SlackNotifier;
///
'notifiers' => [
new SlackNotifier(env('FIREWALL_SLACK_WEBHOOK')),
],
///
When a threat occurs, a new notification will be sent to your Slack channel.