Search by

emacom / bot-shield-core

emacom

Framework-agnostic core of BotShield: filter-count challenge with Cloudflare Turnstile and signed cookies

Package info

github.com/emacom/bot-shield-core

pkg:composer/emacom/bot-shield-core

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-09-23 11:11 UTC

This package is auto-updated.

Last update: 2026-09-23 19:04:18 UTC


README

Framework-agnostic BotShield core: challenge decision, Turnstile verification, signed cookie and bot verification via rDNS. The platform (Magento, WooCommerce, PrestaShop) provides the port implementations and the integration point.

Requirements

  • PHP 8.2–8.5
  • psr/log, psr/simple-cache, psr/clock

Ports

Interface Contract
Port\Config store settings, getHmacSecret() derived from the platform key
Port\Request header, client address behind trusted proxies, URI with query string
Port\CookieJar cookie read and write with Path=/, HttpOnly, Secure, SameSite=Lax
Port\HttpClient form POST, body only for status 200, null in every other case
Port\FilterCounter codes of filters active in the request, no empty values
Psr\SimpleCache\CacheInterface rDNS result cache and circuit breaker counter
Psr\Clock\ClockInterface cookie signature timestamp
Psr\Log\LoggerInterface botshield_* events with context

Classes

Class Role
ChallengeGuard evaluate($context) returns true when the request must get the challenge
ChallengePage widget page HTML, cache-blocking HEADERS, translatable TEXTS
ChallengeResponse redirect() for pages, ajax() for scripts, afterVerification() for the verification endpoint
ChallengeVerification Turnstile token → cookie, result as VerificationResult
Resolver\AjaxDetector X-Requested-With, Sec-Fetch-Dest: empty, Accept: application/json
Turnstile\Verifier token verification via siteverify, fail-open on network error
Verification\Cookie read, validation and issuing of the ts_verified cookie
Verification\Bot X-Verified-Bot header and rDNS with forward confirmation, DEFAULT_BOT_DOMAINS
Resolver\ClientIp client address, /24 or /64 binding prefix, private address detection
CookieSigner, ReturnUrl, Verification\IpRange HMAC signature, safe return URL, CIDR matching

AJAX

Filters reloaded via AJAX get 403 instead of 302. The challenge opens as a modal on the current page.

AJAX request → ChallengeGuard::evaluate() === true, AjaxDetector::isAjax() === true
            ← ChallengeResponse::ajax(): 403, X-BotShield-Challenge: 1, {"challenge": {url, siteKey, verifyUrl, texts}}
assets/challenge-interceptor.js (wrapped fetch + XMLHttpRequest)
            → Turnstile modal → POST cf-turnstile-response, Accept: application/json
            ← ChallengeResponse::afterVerification(json: true): {"ok": true} + Set-Cookie ts_verified
            → location.reload()
Platform responsibility Detail
include assets/challenge-interceptor.js on listings no configuration, data arrives in the 403 response
build url with r pointing to the page not the AJAX endpoint, e.g. without from-xhr in PrestaShop
add platform AJAX markers to AjaxDetector::isAjax() from-xhr parameter, admin-ajax action
send Http\Response through the platform response object status, headers, body

Page reload restores filters only when the filter plugin stores them in the URL via pushState.

Tests

composer install
vendor/bin/phpunit
vendor/bin/phpstan analyse
node --test tests/js/*.test.js

Verified on PHP 8.2.1 and 8.5: 184 tests, phpstan level 8 with no errors. Node 22.22: 9 interceptor tests. The Turnstile modal has no automated test.