PHPackages                             ahmedmerza/logscope-guard - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. ahmedmerza/logscope-guard

ActiveLibrary[Security](/categories/security)

ahmedmerza/logscope-guard
=========================

Active blocking and cross-server coordination at the edge of your Laravel app — IPs (with bots and exploit paths in v1.0).

v0.1.0(3mo ago)00[1 PRs](https://github.com/AhmedMerza/laravel-watchtower/pulls)MITPHPPHP ^8.2CI passing

Since Apr 6Pushed 1w agoCompare

[ Source](https://github.com/AhmedMerza/laravel-watchtower)[ Packagist](https://packagist.org/packages/ahmedmerza/logscope-guard)[ Docs](https://github.com/ahmedmerza/laravel-logscope-guard)[ GitHub Sponsors](https://github.com/AhmedMerza)[ RSS](/packages/ahmedmerza-logscope-guard/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (14)Versions (6)Used By (0)

Watchtower for Laravel
======================

[](#watchtower-for-laravel)

[![License](https://camo.githubusercontent.com/0b0d4b23fdf582859a6ada677b20a1234b1d29989720f22620ffd1de6e39a6d1/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f41686d65644d65727a612f6c61726176656c2d7761746368746f7765723f7374796c653d666c61742d737175617265)](LICENSE.md)[![PHP Version](https://camo.githubusercontent.com/72e717bf3589ed4c4cc0cfc19d2e0931732b47b19de67287feb458aaf12e109d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e322d626c75653f7374796c653d666c61742d737175617265)](https://php.net)

> **Active blocking and cross-server coordination at the edge of your Laravel app** — block a bad actor in one environment and every other environment sees the block within minutes. No Cloudflare, no AWS WAF, no infrastructure changes. Block over a JSON API standalone, or one-click from any log entry when LogScope is installed.

> **Status — heading to v1.0.** Core IP blocking, cross-environment push/pull sync, the cache abstraction (works on **any** Laravel cache driver — Redis is no longer required), and the opt-in auto-block engine (with `block` / `warn` / `disabled` modes) are all in place and tested. LogScope is fully optional — a dev/suggest dependency you install only if you want one-click blocking from the log detail panel.
>
> Still landing before `v1.0.0`: a **built-in authorization path for standalone installs** (today you wrap the routes in your own auth — see [Standalone](#standalone-no-logscope)) and a **standalone management UI** (today the standalone interface is the JSON API below; LogScope users get the in-panel Block-IP button).

Quick Start
-----------

[](#quick-start)

> **Not on Packagist yet.** Until the first tagged release is published, install from the GitHub repo. Add it as a VCS repository in your app's `composer.json`:
>
> ```
> "repositories": [
>     { "type": "vcs", "url": "https://github.com/AhmedMerza/laravel-watchtower" }
> ]
> ```
>
>
>
> then run `composer require ahmedmerza/laravel-watchtower:dev-main`. The commands below assume the package is installed.

**With LogScope:**

```
composer require ahmedmerza/laravel-watchtower
php artisan watchtower:install
```

A **Block IP** button now appears in your LogScope detail panel whenever a log entry has an IP address.

**Standalone (no LogScope):**

```
composer require ahmedmerza/laravel-watchtower
php artisan watchtower:install
```

A JSON management API mounts at `/watchtower/api/...` (configurable via `WATCHTOWER_ROUTE_PREFIX`) — `POST /api/block`, `DELETE /api/block/{ip}`, `GET /api/status/{ip}`, `GET /api/blocks`. There is no standalone HTML UI yet (that's coming before v1.0 — see the status note above); standalone, you drive blocks through this API. Until v1.1 ships proper standalone auth, wrap the routes in your own auth middleware via `config/watchtower.php` → `routes.middleware` (e.g. `['web', 'auth']` plus a Gate check), or set `WATCHTOWER_ROUTES_ENABLED=false` to disable them entirely.

---

How It Works
------------

[](#how-it-works)

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

```

Every incoming request is checked against Laravel's cache (Redis, Memcached, file, database — your choice via `WATCHTOWER_CACHE_STORE`) before any middleware, session, auth, or route runs. No DB hit per request.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#-requirements)
- [Installation](#-installation)
- [Configuration](#%EF%B8%8F-configuration)
- [Cross-Environment Sync](#-cross-environment-sync)
- [Auto-Block Rules](#-auto-block-rules)
- [Artisan Commands](#-artisan-commands)
- [Security Notes](#-security-notes)
- [License](#-license)

---

📋 Requirements
--------------

[](#-requirements)

- PHP 8.2+
- Laravel 11+
- A configured Laravel cache store (any driver — redis, memcached, file, database, array). Redis is recommended for production.
- [ahmedmerza/logscope](https://github.com/AhmedMerza/laravel-logscope) &gt;= 1.5.2 *(optional — only needed if you want the in-detail-panel Block-IP button)*

---

📦 Installation
--------------

[](#-installation)

```
composer require ahmedmerza/laravel-watchtower
php artisan watchtower:install
```

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

```
WATCHTOWER_ENABLED=true
WATCHTOWER_NEVER_BLOCK_IPS=127.0.0.1,::1,your.own.ip
```

> **Important:** Add your own IP to `WATCHTOWER_NEVER_BLOCK_IPS` before enabling. You cannot be blocked by an IP on this list — it is checked before any block operation, before the cache, and before the DB.

---

⚙️ Configuration
----------------

[](#️-configuration)

```
# Master switch
WATCHTOWER_ENABLED=true

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

# Cache store for the blocklist. Blank = your app's default cache store.
# Any Laravel driver works: redis, memcached, file, database, array, dynamodb.
WATCHTOWER_CACHE_STORE=

# Management routes (standalone mode)
WATCHTOWER_ROUTES_ENABLED=true
WATCHTOWER_ROUTE_PREFIX=watchtower
# WATCHTOWER_ROUTE_DOMAIN=admin.example.com

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

# Auto-block engine (disabled by default)
WATCHTOWER_AUTO_BLOCK_ENABLED=false
WATCHTOWER_AUTO_BLOCK_MODE=block   # block | warn | disabled
WATCHTOWER_AUTO_BLOCK_DURATION=60

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

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

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

### Block Response

[](#block-response)

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

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

---

🌐 Cross-Environment Sync
------------------------

[](#-cross-environment-sync)

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

### Setup

[](#setup)

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

```
WATCHTOWER_MASTER_URL=https://your-production-app.com
WATCHTOWER_SYNC_SECRET=same-secret-on-all-environments
```

**On the master app**, expose two routes that satellites call. Path and HMAC header names must match what the satellites send (see `SyncCommand` and `PushBlockToMaster` for the exact wire format):

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

Route::post('/watchtower/api/block', function (Request $request) {
    app(\Watchtower\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:

```
// routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('watchtower:sync')->everyFiveMinutes();
```

### How Push + Pull Work Together

[](#how-push--pull-work-together)

DirectionTriggerSpeed**Push** (satellite → master)Every `BlacklistService::block()` callImmediate (queued job)**Pull** (master → satellites)`watchtower:sync` scheduleEvery 5 min (configurable)Block on staging → staging protected instantly → master updated asynchronously → production/alpha pull it within 5 minutes.

---

🤖 Auto-Block Rules
------------------

[](#-auto-block-rules)

Automatically block IPs based on log patterns. Disabled by default, and ships with an **empty `rules` array** — you opt in by defining rules yourself.

> ⚠️ **Tune carefully or lock real users out.** An overly broad rule can block legitimate traffic across every environment. Start each new rule in `warn` mode (below), validate it against real traffic, then flip it to `block`.

```
WATCHTOWER_AUTO_BLOCK_ENABLED=true
WATCHTOWER_AUTO_BLOCK_MODE=block   # block | warn | disabled (global default)
WATCHTOWER_AUTO_BLOCK_DURATION=60  # minutes
```

**Modes** (global default, overridable per rule):

ModeBehaviour`block`Actually block matching IPs (production behaviour).`warn`Match the rule and emit a structured `would_have_blocked: true` log entry on the configured log channel — but **do not** block. Use this to validate a rule against live traffic before trusting it.`disabled`Skip the rule entirely. A per-rule kill switch without deleting the definition.Define rules in `config/watchtower.php`:

```
'auto_block' => [
    'enabled'                => env('WATCHTOWER_AUTO_BLOCK_ENABLED', false),
    'mode'                   => env('WATCHTOWER_AUTO_BLOCK_MODE', 'block'),
    'block_duration_minutes' => 60,
    'rules' => [
        // Block IPs that generate 50+ errors in 5 minutes
        [
            'level'            => 'error',
            'message_contains' => null,
            'count'            => 50,
            'window_minutes'   => 5,
        ],
        // Same rule, but only warn while you tune it (per-rule mode override)
        [
            'level'            => 'warning',
            'message_contains' => '404',
            'count'            => 100,
            'window_minutes'   => 10,
            'mode'             => 'warn',
        ],
    ],
],
```

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 `WATCHTOWER_NEVER_BLOCK_IPS` are never auto-blocked, even if they match a rule.

---

🔧 Artisan Commands
------------------

[](#-artisan-commands)

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

# Pull blacklist from master and rebuild the local cache
php artisan watchtower:sync

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

---

🔒 Security Notes
----------------

[](#-security-notes)

**Trusted proxies:** Watchtower 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 `WATCHTOWER_SYNC_SECRET` using `hash_hmac('sha256', ...)`. Use a long, random secret and keep it identical across environments.

**Cache TTL:** Each per-IP cache entry carries a 24-hour TTL (configurable via `cache.ttl_hours`) as a safety net. The cache is explicitly rebuilt on every block/unblock and on `watchtower:sync`; if the store is flushed, it warms from the DB automatically on the next request boot.

---

🤝 Contributing
--------------

[](#-contributing)

Contributions are welcome. Please open an issue or submit a pull request on [GitHub](https://github.com/AhmedMerza/laravel-watchtower).

---

📄 License
---------

[](#-license)

MIT License. See [LICENSE](LICENSE.md) for details.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance91

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

91d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f5d8ff95e92a6d88388e9e13d09513a5734012c14a22c5e3a8c6c43acd3bcdf1?d=identicon)[AhmedMerza](/maintainers/AhmedMerza)

---

Top Contributors

[![AhmedMerza](https://avatars.githubusercontent.com/u/67040497?v=4)](https://github.com/AhmedMerza "AhmedMerza (6 commits)")

---

Tags

bot-blockingfirewallip-blockinglogscopesecuritylaravelsecurityfirewallip-blockinglogscope

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/ahmedmerza-logscope-guard/health.svg)

```
[![Health](https://phpackages.com/badges/ahmedmerza-logscope-guard/health.svg)](https://phpackages.com/packages/ahmedmerza-logscope-guard)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

816334.7k3](/packages/defstudio-telegraph)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M132](/packages/laravel-pulse)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77022.3M153](/packages/laravel-mcp)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.1k](/packages/simplestats-io-laravel-client)[api-platform/laravel

API Platform support for Laravel

58173.0k15](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
