Repository navigation
The rebranded release: the project formerly shipped as SecurityMiddleware is now PHP Shield.
Added
- IP reputation via AbuseIPDB — one client for both communities, because abuseipdb.com and abuseipdb.ru share the API v2 contract:
ThreatIntel\AbuseIpDbProviderwithcom()/ru()constructors,ReputationCheckerwith provider fallback, shared cache, quota window and circuit breaker,IpReputationvalue object. Middleware\Shield— the decision entry point.inspect()returns a decision array (blocked,status,reason,detail,score,findings);isRequestAllowed()remains as a boolean facade.- Severity-based block lifetimes (
severity_block_multipliers): low 30 min, medium 1 h, high 4 h, critical 12 h. - Failed-attempt counters with escalation (
max_failed_attempts, default 25) andStorage\FailedAttemptStoreInterface. - Retention cleanup (
SecurityMiddleware::cleanup(),Storage\MaintenanceStoreInterface) and richer statistics (by type, top addresses). Support\Config— typed, validating configuration access; unknown mode or rule names now fail loudly.Support\SecurityHeaders::build()for testing header sets without emitting them.- Anti-CSRF tokens, login lockout, session regeneration and cookie hardening in the admin dashboard; the configuration dump is masked.
tools/db-selftest.php(real MySQL integration test) andtools/live-abuseipdb.php(live reputation check).integration/abuseipdb-ru/— reference adapter that replaces the hand-written middleware of the AbuseIPDB.RU platform with this package, plus its end-to-end test (verified: site smoke 7/7, adapter 15/15).SECURITY.md,docs/security-review.md.
Changed
ShieldFactory::create()returnsShield;createWithComponents()needs no MySQL at all.SecurityMiddlewarenow owns state only: blocks, incident journal, counters, cleanup, statistics.- Default
modeismonitor; defaulttrusted_ipsis loopback only (an allowlist entry bypasses every check). - Default
rate_limit_block_durationis 300 s, so a burst cannot lock out a shared address for an hour. - Detection values are also scanned after URL decoding,
stripslashesand NUL removal;REQUEST_URIis scanned. - Block expiries are computed by the database (
NOW(),DATE_ADD), removing a PHP/MySQL time-zone dependency. - CI matrix extended to PHP 8.5; the coding-standard step is mandatory again.
ext-curldocumented as the transport for reputation lookups.- Breaking:
BlockStoreInterface::block()takes a$severityargument;countIncidents()andgetRecentIncidents()are required. - Breaking:
SecurityMiddleware::isRequestAllowed()moved toShield. - Breaking: the
composer.jsonpackage name ispaulmann/php-shield.
Fixed
- Client IP spoofing:
X-Forwarded-Forwas read from the left, so a client behind a trusted proxy could pick its own address and, matching atrusted_ipsentry, bypass the whole shield. The chain is now read right to left. - Fatal error on binary payloads:
json_encode(..., JSON_THROW_ON_ERROR)in incident logging turned one invalid UTF-8 byte into aJsonExceptionduring threat handling. Invalid bytes are now substituted. - Unchecked
mysqli_result:DatabaseRateLimitercalledfetch_assoc()onfalse, a fatal error whenever the statement failed. - Interactive SQL injection surface in the dashboard: configuration (including password hash and Redis password) was rendered in full; it is now redacted.
- CSRF: block/unblock/logout accepted cross-site POSTs.
- PHPStan level max did not pass (50 errors); the CI job is green now and analyses
src/Adminagain. composer cs-checkalways failed — the config used a risky fixer withoutsetRiskyAllowed(true).- Schema drift:
migrations/schema.sqldisagreed with the runtime DDL (INTvsBIGINT UNSIGNED,VARCHAR(50)vs64, index names). - Command-injection backticks matched any
`text`in prose; they now require a command name.