shugoi / shugoi-php
Shugoi anti-abuse protection — PHP/Laravel middleware
Requires
- php: >=8.2
- 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: ^9.0
- phpunit/phpunit: ^11.0
Suggests
- nyholm/psr7: Required for Laravel integration (PSR-7 HTTP message implementation)
This package is auto-updated.
Last update: 2026-07-28 12:32:33 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 is stored server-side with a signed single-use token; the client receives an eval bootcode skeleton. Guards run, and if the browser is legitimate,
rd()fetches the real HTML via/__shugoi/render - 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 eval bootcode is obfuscated (function renaming, line shuffling, XOR string encryption, decoder injection) before Unicode encoding
- Block page — Full neobrutalist shield page with Alex Brush font, favicon + brand images, pink h2, noise SVG background, countdown timer for rate limits. Consistent ASCII 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), Artisan commands (shugoi:setup,shugoi:check) - Localization — Full FR/EN support for block pages and error messages
Requirements
- PHP 8.2+
- Laravel 11+ (for Laravel integration)
- 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
Done. All HTTP requests are now protected.
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' => '80a320ee3904...', 'allowlist' => ['/api', '/__shugoi'], 'autoInject' => true, 'csp' => true, 'verifyBots' => false, ]); $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); $core = new Core($config, $api, $configCache); $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('GET', '/', getallheaders(), file_get_contents('php://input'), '1.1', $_SERVER); $handler = new class($uri) 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
When using the PHP built-in server (new process per request), the HtmlStore must use a SHARED disk path so tokens persist across requests:
$htmlStore = new HtmlStore('/tmp/shugoi-render-shared');
For Laravel with a persistent process (Octane, Swoole), the in-memory store works.
How it works
Request flow
- Request arrives → Middleware evaluates the request (UA, headers, IP, rate limits)
- If blocked → Returns
BLOCKED BY SHUGOI(text for bots) or shield page (HTML for browsers) - If allowed → Injects guard scripts into the HTML response
- Split-render → Original HTML is stored with a signed token; the client receives an eval bootcode skeleton
- Client boots → Guards run fingerprinting (Tor, VM, headless, anti-detect checks)
- If guards pass →
rd()fetches/__shugoi/render?token=...to get the real HTML - If guards detect issue →
__sg_showBlock()displays a neobrutalist card with block reason
Content Replacement Check
When detectionFlags.enableContentReplacementCheck is:
true(default) — If the render token is invalid/expired, the block card "Remplacement de contenu client détecté" is shownfalse— The render endpoint skips token validation and returns any available stored HTML
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 skeleton/eval injection |
multiProcess |
bool | false |
Disk-based HTML storage for PM2 |
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 which returns stored HTML by token. This route MUST be excluded from the Shugoi middleware to avoid recursion.
For Laravel, we already handle this in the ServiceProvider. For standalone, add the route before the middleware.
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