ahmedmerza/logscope-guard

IP blocking and cross-environment blacklist sync for LogScope

Maintainers

Package info

github.com/AhmedMerza/laravel-logscope-guard

Homepage

pkg:composer/ahmedmerza/logscope-guard

Fund package maintenance!

AhmedMerza

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-04-06 18:40 UTC

This package is auto-updated.

Last update: 2026-04-06 18:57:50 UTC


README

License PHP Version

IP blocking and cross-environment blacklist sync for LogScope.

A malicious IP hits staging. You block it from the LogScope UI. Every other environment syncs within minutes — automatically.

Quick Start

composer require ahmedmerza/logscope-guard
php artisan guard:install

That's it. A Block IP button now appears in the LogScope detail panel whenever a log entry has an IP address.

How It Works

Admin blocks IP in LogScope UI (staging)
    │
    ├─► DB row created + Redis hash rebuilt → staging protected immediately
    │
    └─► Queued job pushes block to master env
            │
            └─► Every other env pulls from master via guard:sync (every 5 min)
                    └─► Redis rebuilt → all environments protected

Every incoming request is checked against a Redis Hash before any middleware, session, auth, or route runs. No DB hit per request.

Table of Contents

📋 Requirements

📦 Installation

composer require ahmedmerza/logscope-guard
php artisan guard:install

The install command publishes the config and runs the migration. Add these to your .env:

GUARD_ENABLED=true
GUARD_NEVER_BLOCK_IPS=127.0.0.1,::1,your.own.ip

Important: Add your own IP to GUARD_NEVER_BLOCK_IPS before enabling. You cannot be blocked by an IP on this list — it is checked before any block operation and before Redis.

⚙️ Configuration

# Master switch
GUARD_ENABLED=true

# IPs that can never be blocked (comma-separated) — prevents self-lockout
GUARD_NEVER_BLOCK_IPS=127.0.0.1,::1

# Redis connection to use for the blacklist hash
GUARD_REDIS_CONNECTION=default

# Cross-environment sync
GUARD_MASTER_URL=https://your-master-app.com
GUARD_SYNC_SECRET=a-long-random-secret

# Auto-block engine (disabled by default)
GUARD_AUTO_BLOCK_ENABLED=false
GUARD_AUTO_BLOCK_DURATION=60

# Webhook notification on every block (optional — useful for n8n, Slack, WhatsApp)
GUARD_WEBHOOK_URL=
GUARD_NOTIFICATION_QUEUE=default

# Dedicated log channel for Guard events (sync failures, auto-block skips, etc.)
GUARD_LOG_CHANNEL=stack

# Automatic cleanup of expired temporary blocks (runs daily)
GUARD_CLEANUP_ENABLED=true

Block Response

By default, blocked IPs receive a plain 403 Access denied. response. To redirect instead:

// config/logscope-guard.php
'block_response' => [
    'status'   => 403,
    'message'  => 'Access denied.',
    'redirect' => null, // Set a URL to redirect instead
],

🌐 Cross-Environment Sync

Guard supports a master/satellite topology. One environment (production) is the master. Others (staging, alpha) pull from it.

Setup

On every environment (master + satellites), add to .env:

GUARD_MASTER_URL=https://your-production-app.com
GUARD_SYNC_SECRET=same-secret-on-all-environments

On the master app, expose two routes that satellites call:

// routes/web.php (or api.php) — protect with HMAC middleware
Route::get('/guard/api/blacklist', fn () => response()->json([
    'data' => \LogScopeGuard\Models\BlacklistedIp::active()->get(),
]));

Route::post('/guard/api/block', function (Request $request) {
    app(\LogScopeGuard\Services\BlacklistService::class)->block(
        $request->input('ip'),
        $request->only(['reason', 'source_env', 'expires_at', 'blocked_by'])
    );
    return response()->json(['ok' => true]);
});

On satellites, schedule the sync command:

// Laravel 11+ (routes/console.php)
Schedule::command('guard:sync')->everyFiveMinutes();

// Laravel 10 (app/Console/Kernel.php)
$schedule->command('guard:sync')->everyFiveMinutes();

How Push + Pull Work Together

Direction Trigger Speed
Push (satellite → master) Every BlacklistService::block() call Immediate (queued job)
Pull (master → satellites) guard:sync schedule Every 5 min (configurable)

Block on staging → staging protected instantly → master updated asynchronously → production/alpha pull it within 5 minutes.

🤖 Auto-Block Rules

Automatically block IPs based on log patterns. Disabled by default.

GUARD_AUTO_BLOCK_ENABLED=true
GUARD_AUTO_BLOCK_DURATION=60  # minutes

Define rules in config/logscope-guard.php:

'auto_block' => [
    'enabled'                => env('GUARD_AUTO_BLOCK_ENABLED', false),
    'block_duration_minutes' => 60,
    'rules' => [
        // Block IPs that generate 50+ errors in 5 minutes
        [
            'level'            => 'error',
            'message_contains' => null,
            'count'            => 50,
            'window_minutes'   => 5,
        ],
        // Block IPs that hit 404 more than 100 times in 10 minutes
        [
            'level'            => 'warning',
            'message_contains' => '404',
            'count'            => 100,
            'window_minutes'   => 10,
        ],
    ],
],

Rules run every minute via the scheduler. Add the scheduler to your server if not already running:

* * * * * cd /your-app && php artisan schedule:run >> /dev/null 2>&1

Note: IPs in GUARD_NEVER_BLOCK_IPS are never auto-blocked, even if they match a rule.

🔧 Artisan Commands

# First-time setup (publish config + run migration)
php artisan guard:install

# Pull blacklist from master and rebuild local Redis cache
php artisan guard:sync

# Delete expired temporary blocks and rebuild the Redis cache
# Runs automatically every day — set GUARD_CLEANUP_ENABLED=false to manage manually
# Permanent blocks (no expiry) are never touched
php artisan guard:cleanup

🔒 Security Notes

Trusted proxies: Guard uses $request->ip() — the same method LogScope uses. If your app is behind a load balancer or proxy, configure Laravel's trusted proxies correctly so the real client IP is resolved, not the proxy IP.

HMAC signatures: All sync requests are signed with GUARD_SYNC_SECRET using hash_hmac('sha256', ...). Use a long, random secret and keep it identical across environments.

Redis TTL: The blacklist Redis hash has a 24-hour TTL as a safety net. If Redis is flushed, the cache rebuilds from DB automatically on the next request boot.

🤝 Contributing

Contributions are welcome. Please open an issue or submit a pull request on GitHub.

📄 License

MIT License. See LICENSE for details.