shugoi / shugoi-php
Shugoi anti-abuse protection — PHP/Laravel middleware
Requires
- php: >=8.2
- amphp/websocket-server: ^4.0
- guzzlehttp/guzzle: ^7.0
- nyholm/psr7: ^1.8
- psr/http-message: ^2.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
Suggests
- ext-openswoole: Required only for the optional shugoi:websocket render sidecar
- nyholm/psr7: Required for Laravel integration (PSR-7 HTTP message implementation)
Provides
None
Conflicts
None
Replaces
None
- dev-master
- v0.5.0
- v0.4.91
- v0.4.12
- 0.4.11
- v0.4.10
- v0.4.9
- v0.4.8
- v0.4.7
- 0.4.6
- 0.4.5
- 0.4.0
- 0.3.0
- dev-agent/release-php-0.4.6
- dev-main
- dev-fix/php-clock-false-positive-20261002
- dev-fix/laravel-finalized-html-20261001
- dev-fix/php-guard-workers-20261001
- dev-fix/php-api-alignment-20261001
- dev-fix/php-websocket-pow-20260930
This package is auto-updated.
Last update: 2026-10-07 12:49:31 UTC
README
Shugoi is a full-featured anti-abuse protection layer for PHP applications. It combines edge-level request blocking, client-side browser fingerprinting via Web Workers, split-render technology with single-use tokens, code obfuscation, and multi-layered detection to protect your web apps from bots, scrapers, automated attacks, and fraud.
Features
- Edge blocking — Headless browser detection (curl, wget, python, puppeteer, etc.), fake browser detection (missing Sec-Fetch headers), rate limiting with configurable thresholds
- Client-side fingerprinting — Tor Browser detection, VM/machine detection, anti-detect browser detection, headless Chrome/Puppeteer detection via Web Worker
- Whitelist — machineId-based whitelist managed from the Shugoi dashboard, bypasses all client-side checks
- Split-render - Original HTML stays server-side behind an exact, single-use signed token. Browser detection exchanges use WebSocket; a signed grant releases HTML through same-origin HTTP by default, or through the optional WebSocket render sidecar.
- WebSocket proof of work - A separate PHP CLI gateway issues and verifies the preflight challenge. The browser receives a signed receipt bound to its IP and user agent; PHP consumes it once before setting its admission cookie.
- Crawler metadata isolation — Recognized crawler user agents receive only an escaped document containing the original page's title, description, OpenGraph/Twitter tags and canonical link. The protected page body is never returned to this branch; FCrDNS remains required for any trusted-bot access decision.
- Content replacement detection — If the render endpoint is called with an invalid/consumed token and
enableContentReplacementCheckis enabled, a block card "Remplacement de contenu client détecté" is shown with full neobrutalist styling - CSP injection — Automatic Content-Security-Policy header with proper origins for scripts, fonts, images; optionally extensible via
extraDirectives - Bot verification — Reverse DNS (FCrDNS) verification for Googlebot, Bingbot, YandexBot, Applebot, etc.
- Obfuscation — The bootstrap is obfuscated (function renaming, line shuffling, XOR string encryption and decoder injection) without the retired invisible-eval wrapper
- Block page — Shugoi-styled shield page with browser-matched background palettes, brand images, Reggae One display font, and a countdown for rate limits. Consistent plain-text block page for non-browser clients
- Multi-process support — Shared disk-based HTML token storage for PHP built-in server, Laravel Octane, or any multi-worker setup
- PSR-15 middleware — Framework-agnostic, compatible with any PSR-15 implementation
- Laravel integration — ServiceProvider with auto-wiring, HTTP middleware wrapper, Facade, Blade directives (
@shugoiHead,@shugoiBody), and Artisan commands (shugoi:setup,shugoi:check,shugoi:websocket) - Localization — Full FR/EN support for block pages and error messages
Requirements
- PHP 8.2+
- Laravel 11+ (for Laravel integration)
- OpenSwoole PHP extension (only when using the optional render WebSocket sidecar)
- Guzzle 7+
- PSR-15 compatible middleware (for standalone usage)
Installation
composer require shugoi/shugoi-php
Quick Start (Laravel)
composer require shugoi/shugoi-php
Add to .env:
SHUGOI_SITE_KEY=sg_sk_live_xxxx SHUGOI_SECRET=your_site_secret
Add to bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) { $middleware->append(\Shugoi\Laravel\ShugoiMiddleware::class); })
Verify:
php artisan shugoi:setup
Configure and start the PHP WebSocket gateway, including the Nginx upgrade route and matching environment. The protected browser flow requires this service; PHP-FPM alone does not accept WebSocket upgrades.
Laravel uses this same package and PoW gateway; no separate Laravel SDK is needed. Follow the Laravel deployment notes for middleware order, private storage, cached configuration and gateway secrets.
The same-origin render WebSocket is an optional transport. Enable it with
SHUGOI_BROWSER_TRANSPORT=websocket, SHUGOI_RENDER_STORE=disk,
SHUGOI_RENDER_STORE_PATH, and SHUGOI_PUBLIC_ORIGIN, then run
php artisan shugoi:websocket behind a TLS reverse proxy. The
Laravel WebSocket demo includes a
configuration example.
Demo
A complete demo script is available in demo/setup.sh:
cd demo && bash setup.sh
This creates a fresh Laravel project with Shugoi pre-configured.
Quick Start (PSR-15 standalone)
use Shugoi\Config; use Shugoi\Core; use Shugoi\ApiClient; use Shugoi\ConfigCache; use Shugoi\GuardCache; use Shugoi\HtmlStore; use Shugoi\CspBuilder; use Shugoi\GuardInjector; use Shugoi\TokenSigner; use Shugoi\Middleware; use Nyholm\Psr7\ServerRequest; use Nyholm\Psr7\Response; $config = new Config([ 'siteKey' => 'sg_sk_live_xxx', 'secret' => getenv('SHUGOI_SECRET'), 'allowlist' => ['/api'], 'autoInject' => true, 'csp' => true, 'verifyBots' => true, ]); $api = new ApiClient($config); $configCache = new ConfigCache($api); $guardCache = new GuardCache($api); $htmlStore = new HtmlStore('/tmp/shugoi-render-shared'); $tokenSigner = new TokenSigner($config); $cspBuilder = new CspBuilder($config); $pow = new \Shugoi\Pow($config); $core = new Core($config, $api, $pow, $configCache, new \Shugoi\BotVerifier()); $injector = new GuardInjector($config, $tokenSigner, $htmlStore, $guardCache, $configCache); $middleware = new Middleware( config: $config, core: $core, api: $api, configCache: $configCache, guardCache: $guardCache, htmlStore: $htmlStore, cspBuilder: $cspBuilder, injector: $injector, tokenSigner: $tokenSigner, ); $request = new ServerRequest($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], getallheaders(), file_get_contents('php://input'), '1.1', $_SERVER); $request = $request->withQueryParams($_GET)->withCookieParams($_COOKIE); $handler = new class implements \Psr\Http\Server\RequestHandlerInterface { public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface { return new Response(200, ['Content-Type' => 'text/html'], '<html><body>OK</body></html>'); } }; $response = $middleware->process($request, $handler);
Important: Shared disk store
With PHP-FPM, the built-in server, or multiple workers, HtmlStore must use a private shared disk path so tokens survive across requests:
$htmlStore = new HtmlStore('/tmp/shugoi-render-shared');
Laravel defaults to a site-scoped storage directory. In-memory storage is only suitable when all requests reach the same persistent instance. See the gateway setup for the shared receipt directory and trusted proxy configuration.
How it works
Request flow
- The middleware blocks configured non-browser user agents and applies rate limits.
- A browser without a valid admission cookie receives a verification page, without protected content.
- The browser obtains and solves a challenge over
/__sg_challenge/ws. No raw proof is accepted over HTTP. - The gateway returns a signed, short-lived receipt. The application consumes it atomically and redirects to the clean URL with an HttpOnly admission cookie.
- The application stores its HTML and sends a bootstrap containing the Shugoi guard and WebSocket transport hooks.
- The guard obtains its signed WLC decision and render grant over the Shugoi API WebSocket.
- The same-origin render endpoint verifies token, tenant, grant and expiry, then consumes the exact HTML once.
Invalid, expired and consumed render tokens are always refused, including when enableContentReplacementCheck is disabled. This flag never permits a fallback to another stored page. A user-agent filter or PoW alone cannot prove that a client is human; the signed render decision remains required.
Configuration reference
| Option | Type | Default | Description |
|---|---|---|---|
siteKey |
string | required | Your Shugoi site key |
secret |
string | - | Site secret for key validation |
signingSecret |
string | secret |
HMAC secret for token signing |
allowlist |
string[] | ['/api', '/legal'] |
Paths bypassing protection |
headlessPatterns |
RegExp[] | curl, wget, python, ... | UA patterns to block |
botWhitelist |
RegExp[] | Googlebot, Bingbot, ... | Legit bots exempt from blocking |
baseUrl |
string | https://shugoi.com/api/v1 |
API base URL |
internalUrl |
string | baseUrl |
Internal URL for server-to-server calls |
autoInject |
bool | true |
Auto-inject guard scripts |
csp |
bool | true |
Enable CSP header |
splitRender |
bool | true |
Enable guarded bootstrap injection |
multiProcess |
bool | false |
Shared HTML storage (Laravel defaults to true) |
browserTransport |
string | http |
Render transport (http or optional websocket) |
renderStore |
string | memory |
Render-store mode; WebSocket render needs disk |
renderStorePath |
string | site-scoped in Laravel | Private shared HTML directory |
websocketBind |
string | 127.0.0.1 |
Local bind address for the render sidecar |
websocketPort |
int | 8787 |
Local port for the render sidecar |
publicOrigin |
string | - | Exact public HTTPS origin accepted by the render sidecar |
powWebSocketUrl |
string | /__sg_challenge/ws |
Public PoW gateway URL |
powReceiptStorePath |
string | site-scoped temporary directory | Private shared consumed-receipt directory |
verifyBots |
bool | true |
Reverse DNS bot verification |
debug |
bool | false |
Console logs |
restrictedAccess |
bool | false |
Show restricted block page |
blockStatus |
int | 403 |
HTTP status for blocks |
locale |
string | - | Block page language (fr/en) |
extraDirectives |
array | - | Additional CSP directives |
blockPage |
callable | - | Custom block page HTML |
Internal route
The middleware serves /__shugoi/render itself and returns stored HTML only after token, grant, site and expiry validation. Laravel uses the same RenderService through its adapter/controller; do not exclude this route from the Shugoi middleware.
For Laravel, the ServiceProvider and adapter handle this route. For standalone PSR-15 usage, keep the middleware in the request chain so it can handle the route before the application handler.
Testing
$ curl http://localhost:3100/ +---------------------------------------------+ | BLOCKED BY SHUGOI | +---------------------------------------------+ | Bots, scrapers and headless clients | | are blocked by Shugoi protection. | | | | Use a standard browser to access | | this site. | | | | - contact: support@shugoi.com - | +---------------------------------------------+
All automated requests (curl, wget, python, etc.) are blocked. Only legitimate browsers with proper fingerprint signals pass through the protection.
Architecture
Request → Middleware → Core::evaluate()
├── Blocked → BLOCK_PAGE text or shield HTML + CSP
└── Allowed → GuardInjector → store HTML → skeleton (eval bootcode)
└── Browser executes skeleton → guards → rd() → render endpoint → HTML
License
MIT