Comprehensive account, IP, network, device, strike, restriction, appeal, and moderation enforcement tools for Laravel applications.

Maintainers

Package info

github.com/EloquentWorks/Exile

pkg:composer/eloquent-works/exile

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.1.0 2026-07-19 01:16 UTC

This package is not auto-updated.

Last update: 2026-07-28 03:10:54 UTC


README

Tests Latest Release License

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 any or strict all matching 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

  • BanIssued
  • BanRevoked
  • BanExpired
  • RestrictionIssued
  • RestrictionRevoked
  • StrikeIssued
  • WarningIssued
  • AppealSubmitted
  • AppealResolved

✅ 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:

🔐 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.