eloquent-works / exile
Comprehensive account, IP, network, device, strike, restriction, appeal, and moderation enforcement tools for Laravel applications.
Requires
- php: ^8.2
- illuminate/bus: ^11.15|^12.0|^13.0
- illuminate/console: ^11.15|^12.0|^13.0
- illuminate/contracts: ^11.15|^12.0|^13.0
- illuminate/database: ^11.15|^12.0|^13.0
- illuminate/encryption: ^11.15|^12.0|^13.0
- illuminate/events: ^11.15|^12.0|^13.0
- illuminate/filesystem: ^11.15|^12.0|^13.0
- illuminate/http: ^11.15|^12.0|^13.0
- illuminate/notifications: ^11.15|^12.0|^13.0
- illuminate/queue: ^11.15|^12.0|^13.0
- illuminate/routing: ^11.15|^12.0|^13.0
- illuminate/support: ^11.15|^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3.10
- laravel/pint: ^1.18
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^11.0
This package is not auto-updated.
Last update: 2026-07-28 03:10:54 UTC
README
Comprehensive moderation-enforcement tools for Laravel applications.
Laravel Exile supports account, IP, CIDR network, and device bans; temporary and permanent enforcement; warnings and strike escalation; login, posting, read-only, and shadow restrictions; appeals, evidence, moderator tracking, audit history, events, notifications, middleware, and scheduled maintenance.
$user->ban( reason: 'Repeated harassment', expiresAt: now()->addDays(7), moderator: $moderator, ); $user->strike( reason: 'Spam', points: 3, ); $user->restrict( RestrictionType::Posting, reason: 'Posting cooldown', );
📋 Supported Versions
| Package version | PHP | Laravel / Illuminate |
|---|---|---|
| Current | ^8.2 |
^12.0 || ^13.0 |
Composer automatically resolves compatible Laravel and Illuminate versions for the consuming application.
🚀 Installation
composer require eloquent-works/exile php artisan exile:install --migrate
Publish the customizable notification templates during installation:
php artisan exile:install --migrate --views
Add the Bannable trait to the account model:
<?php namespace App\Models; use EloquentWorks\Exile\Traits\Bannable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Bannable; }
Configure a dedicated hash key:
EXILE_HASH_KEY=base64:replace-with-a-dedicated-random-key
Do not rotate this key casually. Existing IP and device hashes will no longer match after rotation.
✨ Features
- Account, exact-IP, CIDR network, device, and combined bans
- Configurable
anyor strictallmatching for combined bans - Temporary and permanent enforcement
- Transactional enforcement writes and after-commit side effects
- Login, posting, read-only, and shadow restrictions
- Warnings, strike points, and automatic escalation
- Appeals with approval, denial, withdrawal, and automatic revocation
- Evidence uploads with SHA-256 integrity checksums
- Moderator attribution, internal notes, metadata, and audit history
- Queued ban notifications with replaceable notification classes
- Publishable Markdown mail templates
- Middleware, expiration processing, and retention pruning
- Configurable models, table names, categories, and aliases
🛡️ Protect Routes
Block active account, IP, network, and device bans:
use Illuminate\Support\Facades\Route; Route::middleware(['auth', 'exile'])->group(function (): void { Route::get('/dashboard', DashboardController::class); });
Block a restricted action:
Route::post('/posts', StorePostController::class) ->middleware(['auth', 'exile', 'exile.allowed:posting']);
Mark shadow-restricted requests without rejecting them:
Route::post('/comments', StoreCommentController::class) ->middleware(['auth', 'exile.shadow']); $shadowed = (bool) request() ->attributes ->get('exile.shadowed', false);
👤 Account Bans
$ban = $user->ban( reason: 'Repeated harassment', expiresAt: now()->addDays(7), moderator: $moderator, category: 'harassment', internalNotes: 'Case EX-1042', metadata: [ 'case_number' => 'EX-1042', ], ); $user->isBanned();
🌐 IP, Network, and Device Bans
use EloquentWorks\Exile\Facades\Exile; Exile::banIp( '203.0.113.10', reason: 'Automated abuse', ); Exile::banNetwork( '203.0.113.0/24', reason: 'Abusive network', ); Exile::banDevice( 'application-device-token', reason: 'Ban evasion', ); Exile::banAccountAndIp( account: $user, ipAddress: request()->ip(), reason: 'Ban evasion', );
Register a device observation without storing the raw token:
$user->registerDeviceFingerprint( fingerprint: $request->header('X-Device-Fingerprint'), ipAddress: $request->ip(), label: 'Chrome on Windows', );
⚠️ Warnings and Strikes
use EloquentWorks\Exile\Enums\WarningSeverity; $user->warn( reason: 'Please review the community rules.', severity: WarningSeverity::High, ); $user->strike( reason: 'Spam', points: 3, category: 'spam', ); $user->activeStrikePoints();
Escalation thresholds are configured in config/exile.php.
🚧 Restrictions
use EloquentWorks\Exile\Enums\RestrictionType; $user->restrict( RestrictionType::Posting, reason: 'Posting cooldown', expiresAt: now()->addDay(), ); $user->restrict(RestrictionType::ReadOnly); $user->restrict(RestrictionType::Login); $user->restrict(RestrictionType::Shadow); $user->isRestricted(RestrictionType::Posting); $user->isShadowBanned();
⚖️ Appeals
use EloquentWorks\Exile\Enums\AppealStatus; use EloquentWorks\Exile\Facades\Exile; $appeal = Exile::submitAppeal( $ban, $user, 'I believe this enforcement was issued in error.', ); Exile::resolveAppeal( $appeal, AppealStatus::Approved, $reviewer, 'Appeal accepted.', );
Approving an appeal revokes the related ban.
📎 Evidence
Attach an existing stored file:
$evidence = Exile::attachEvidence( subject: $ban, disk: 'private', path: 'moderation/case-1042/report.pdf', originalName: 'report.pdf', uploadedBy: $moderator, );
Or store an uploaded file through Exile:
$evidence = Exile::storeEvidence( $ban, $request->file('evidence'), $moderator, );
♻️ Revocation
Exile::revokeBan($ban, $moderator); Exile::revokeRestriction($restriction, $moderator); Exile::revokeStrike($strike, $moderator);
Records remain available as moderation history until explicitly pruned.
⏱️ Commands
php artisan exile:expire php artisan exile:prune
Pruning is disabled by default. Enable it in configuration or explicitly force a retention period:
php artisan exile:prune --force --days=365
🔔 Notifications
Notifications are disabled by default. Enable them in config/exile.php:
'notifications' => [ 'enabled' => true, 'channels' => ['mail'], ],
The affected model must support Laravel notifications. Database notifications also require Laravel's notifications table.
📣 Events
BanIssuedBanRevokedBanExpiredRestrictionIssuedRestrictionRevokedStrikeIssuedWarningIssuedAppealSubmittedAppealResolved
✅ Quality Checks
Run the complete quality pipeline:
composer quality
Or run each check separately:
composer format
composer analyse
composer test
Validate Composer metadata before publishing:
composer validate --strict
composer analyse must complete with zero PHPStan errors. Do not disable valid findings globally merely to make the command pass. Prefer correcting native types, generic PHPDoc annotations, impossible comparisons, redundant instanceof conditions, and iterable value types.
See Testing and Quality and PHPStan Remediation.
📚 Documentation
Full documentation is available in the docs directory:
- Documentation index
- Installation
- Configuration
- Architecture
- Bans
- IP, Network, and Device Enforcement
- Restrictions
- Warnings, Strikes, and Escalation
- Appeals
- Evidence
- Middleware
- Events, Notifications, and Audit History
- Commands and Scheduling
- Customization
- Security
- Testing and Quality
- PHPStan Remediation
- Release Checklist
🔐 Security
Keep EXILE_HASH_KEY private. Exile uses keyed HMAC hashes for IP and device matching. Human-readable IP addresses and CIDR ranges are encrypted at rest using Laravel's encryption system.
Do not use device fingerprints as a sole identity signal. Treat them as one moderation indicator alongside account history, IP context, and human review.
Security vulnerabilities should be reported privately according to SECURITY.md, not through a public issue.
🤝 Contributing
See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
🙏 Credits
Built by Eloquent Works.
📄 License
Laravel Exile is open-source software licensed under the MIT License.