phil / bot-detection-bundle
Bot detection for Symfony whose only output is a robot or human verdict: in-browser automation detectors in a Stimulus controller, a strict verdict, a verdict-to-friction policy and an aggregate robot verdict counter
Package info
github.com/parler-haut-interagir-librement/bot-detection-bundle
Type:symfony-bundle
pkg:composer/phil/bot-detection-bundle
Requires
- php: >=8.5
- symfony/config: ^8.2
- symfony/dependency-injection: ^8.2
Requires (Dev)
- phpunit/phpunit: ^13.4
- psr/log: ^3.0
- symfony/console: ^8.2
- symfony/event-dispatcher: ^8.2
- symfony/framework-bundle: ^8.2
- symfony/http-kernel: ^8.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-11 06:15:36 UTC
README
Tell a robot from a human in the browser, and let only that one word out.
phil/bot-detection-bundle produces a single output: a robot or human verdict. In the browser, a Stimulus
controller runs automation detectors locally (among them WebDriver, headless Chrome, automation globals and stack
frames, a software renderer) and lets only human or robot out. On the server, Verdict reads what the browser
submits, strictly. A FrictionPolicy chosen per surface maps it to friction, and robot verdicts go to an aggregate
counter that takes no argument.
It is for Symfony applications that want to slow automated visitors without hurting people. No identifier and no browser fingerprint is computed, sent or kept, the verdict itself is never stored, and no third party is ever called. A verdict never refuses a request: friction means one more step. Telling good crawlers from bad ones is left to a server-side crawler list.
Other packages build on the verdict. phil/analytics-bundle reads it to keep robots out of its counters, and
phil/spam-protection-bundle gives its inspection of a form as a verdict.
The bundle is a skeleton of the verdict, the friction policy and VerdictHandler, with one port for what the
application decides: where RobotVerdictCounter keeps its count. PhilBotDetectionBundle declares no service and
no configuration.
Status
| Area | State |
|---|---|
Domain\Verdict\Verdict |
Implemented and tested |
Domain\Friction\Friction, Domain\Friction\FrictionPolicy |
Implemented and tested |
Application\VerdictHandler |
Implemented and tested |
Domain\Counter\RobotVerdictCounter |
Port, implemented by the application |
Browser detectors: the Stimulus controller verdict |
Implemented, tested with Node; no third-party code included |
PhilBotDetectionBundle |
No service, no configuration |
Documentation
- Installation — requirements, installing from a repository until the bundle is on Packagist, and registering it (which changes nothing yet).
- Configuration — the bundle has no configuration key and declares no service; how to wire one policy, counter and handler per surface, and the decisions the application owns.
- The verdict — the
humanandrobotwire values, strict parsing that never throws, and why a verdict is neither an identifier nor stored. - Friction — one more step and never a refusal, the two policies for a missing verdict, and choosing between them per surface.
- Handling a verdict —
VerdictHandler, the caller's obligations, and why the counter counts robot verdicts rather than friction steps. - Browser signals — the in-browser detectors of the
verdictStimulus controller, the signals still to write, what is never collected or sent, and the open-source projects read as design references (motifs only). - Extension points — the
RobotVerdictCounterport, its invariants and example adapters. - Testing — running the tests from the monorepo or with the bundle installed alone, and testing your counter and surfaces.
- API reference — every public type by layer, with its role and state.
Quick start
composer require phil/bot-detection-bundle:^0.1@dev # from a path or VCS repository until published
use Phil\BotDetectionBundle\Application\VerdictHandler; use Phil\BotDetectionBundle\Domain\Friction\FrictionPolicy; use Phil\BotDetectionBundle\Domain\Verdict\Verdict; $handler = new VerdictHandler(FrictionPolicy::robotOrUnknown(), $signUpRobotVerdictCounter); $friction = $handler->handle(Verdict::fromSubmitted($request->request->get('verdict'))); if ($friction->adds()) { // one more step on this surface: a back-off, a stronger challenge... } // then drop the verdict: never log it, never store it
Requirements
- PHP 8.5 or later
symfony/dependency-injectionandsymfony/config8.2
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.