PHPackages                             lyrasoft/firewall - 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. lyrasoft/firewall

ActiveWindwalker-package[Security](/categories/security)

lyrasoft/firewall
=================

A Firewall package for LYRASOFT

0.2.0(1mo ago)01.2k↓37.5%[1 issues](https://github.com/lyrasoft/luna-firewall/issues)MITPHPPHP &gt;=8.4.6

Since Jul 31Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/lyrasoft/luna-firewall)[ Packagist](https://packagist.org/packages/lyrasoft/firewall)[ RSS](/packages/lyrasoft-firewall/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (4)Versions (11)Used By (0)

LYRASOFT Firewall Package
=========================

[](#lyrasoft-firewall-package)

- [LYRASOFT Firewall Package](#lyrasoft-firewall-package)
    - [Installation](#installation)
        - [Language Files](#language-files)
    - [Register Admin Menu](#register-admin-menu)
    - [Redirect](#redirect)
        - [The Source Path Rules](#the-source-path-rules)
        - [The Dest Path](#the-dest-path)
        - [Other Params](#other-params)
        - [Use Different Type from DB](#use-different-type-from-db)
        - [Use Custom List](#use-custom-list)
        - [Instant Redirect](#instant-redirect)
        - [Disable](#disable)
        - [Hook](#hook)
        - [RedirectMiddleware params](#redirectmiddleware-params)
    - [IP Allow/Block (Firewall)](#ip-allowblock-firewall)
        - [Admin IP Rules Management](#admin-ip-rules-management)
        - [Paths and Domains](#paths-and-domains)
        - [Select DB Type](#select-db-type)
        - [Custom List](#custom-list)
        - [Disable](#disable-1)
        - [Hook](#hook-1)
        - [FirewallMiddleware params](#firewallmiddleware-params)
    - [Cache](#cache)
        - [Cache Lifetime](#cache-lifetime)
        - [Cache Clear](#cache-clear)
        - [Cache Disable](#cache-disable)
    - [Honeypot](#honeypot)
        - [Allow Good Bot](#allow-good-bot)
        - [Built-in Block Words](#built-in-block-words)
        - [HoneypotMiddleware params](#honeypotmiddleware-params)

Installation
------------

[](#installation)

Install from composer

```
composer require lyrasoft/firewall
```

Then copy files to project

```
php windwalker pkg:install lyrasoft/firewall -t routes -t migrations
```

### Language Files

[](#language-files)

Add this line to admin &amp; front middleware if you don't want to override languages:

```
$this->lang->loadAllFromVendor('lyrasoft/firewall', 'ini');

// OR
$this->lang->loadAllFromVendor(\Lyrasoft\Firewall\FirewallPackage::class, 'ini');
```

Or run this command to copy languages files:

```
php windwalker pkg:install lyrasoft/firewall -t lang
```

Register Admin Menu
-------------------

[](#register-admin-menu)

Edit `resources/menu/admin/sidemenu.menu.php`

```
$menu->link($this->trans('unicorn.title.grid', title: $this->trans('firewall.redirect.title')))
    ->to($nav->to('redirect_list')->var('type', 'main'))
    ->icon('fal fa-angles-right');

$menu->link($this->trans('unicorn.title.grid', title: $this->trans('firewall.ip.rule.title')))
    ->to($nav->to('ip_rule_list')->var('type', 'main'))
    ->icon('fal fa-network-wired');
```

Redirect
--------

[](#redirect)

Add `RedirectMiddleware` to `etc/app/main.php`

```
use Lyrasoft\Firewall\Middleware\RedirectMiddleware;

    // ...

    'middlewares' => [
        \Windwalker\DI\create(
            RedirectMiddleware::class,
            excludes: [
                'admin/*'
            ]
        ),

        // ...
    ],
```

Now you can add redirect records at admin:

[![](https://private-user-images.githubusercontent.com/1639206/353802532-3919fa71-b182-4e9e-a20c-c363f4dd3963.jpg?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ4NTczNzUsIm5iZiI6MTc3NDg1NzA3NSwicGF0aCI6Ii8xNjM5MjA2LzM1MzgwMjUzMi0zOTE5ZmE3MS1iMTgyLTRlOWUtYTIwYy1jMzYzZjRkZDM5NjMuanBnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMzMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMzBUMDc1MTE1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MjBlMjJlYmJlMzAwY2UxMjU2ZjczOWE5YzVkYjZjMjRlMDRhNTI1MjFkYjQ4ZjYwNDQ4OGE0ODI1YjhhNjdkOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.bdBxST-OCKCVwt5a13FxsjGoyhHBMuJl7P8PR2n-Wdk)](https://private-user-images.githubusercontent.com/1639206/353802532-3919fa71-b182-4e9e-a20c-c363f4dd3963.jpg?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ4NTczNzUsIm5iZiI6MTc3NDg1NzA3NSwicGF0aCI6Ii8xNjM5MjA2LzM1MzgwMjUzMi0zOTE5ZmE3MS1iMTgyLTRlOWUtYTIwYy1jMzYzZjRkZDM5NjMuanBnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMzMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMzBUMDc1MTE1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MjBlMjJlYmJlMzAwY2UxMjU2ZjczOWE5YzVkYjZjMjRlMDRhNTI1MjFkYjQ4ZjYwNDQ4OGE0ODI1YjhhNjdkOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.bdBxST-OCKCVwt5a13FxsjGoyhHBMuJl7P8PR2n-Wdk)

### The Source Path Rules

[](#the-source-path-rules)

- Add `/` at start, the path will compare from site base root (not domain root).
- If you enable the `Regex`:
    - Add `*` will compare a path segment with any string.
    - Add `**` will compare cross-segments.
    - You can add custom regex rules, like: `/foo/(\d+)`

### The Dest Path

[](#the-dest-path)

Thr dest path can be relative path: `foo/bar` or full URL: `https://simular.co/foo/bar`.

If you enabne the `Regex`, you may use variables start with `$` to insert matched string. For example, a `foo/*/edit/(\d+)`, can redirect to `new/path/$1/edit/$2`

### Other Params

[](#other-params)

- `Only 404`: Only redirect if a page is 404, if page URL exists, won't redirect.
- `Handle Locale`: If this site is multi-language, this params will auto auto detect the starting ;anguage prefix and auto add it to dest path, you may use `{lang}` in dest path to custom set lang alias position.

### Use Different Type from DB

[](#use-different-type-from-db)

Redirect tables has `type` colimn, you can use `admin/redirect/list/{type}` to manage different types.

And if you want to choose types for middleware, you can do this:

```
    // ...

    'middlewares' => [
        \Windwalker\DI\create(
            RedirectMiddleware::class,
            type: 'other_type',
            excludes: [
                'admin/*'
            ]
        ),

        // ...
    ],
```

The type supports `string|Enum|array|null|false`, if you send `NULL` into it, means all redirect records. If you send `FALSE`, means don't use DB records.

### Use Custom List

[](#use-custom-list)

You can use custom redirect list, custom list will auto-enable the regex:

This settings will merge DB list and custom list.

```
    // ...

    'middlewares' => [
        \Windwalker\DI\create(
            RedirectMiddleware::class,
            type: 'flower',
            list: [
                'foo/bar' => 'hello/world',
                'foo/yoo/*' => 'hello/mountain/$1',
            ],
            excludes: [
                'admin/*'
            ]
        ),

        // ...
    ],
```

This settings will disable DB list and only use custom list.

```
    // ...

    'middlewares' => [
        \Windwalker\DI\create(
            RedirectMiddleware::class,
            type: false,
            list: [
                'foo/bar' => 'hello/world',
                'foo/yoo/*' => 'hello/mountain/$1',
            ],
            excludes: [
                'admin/*'
            ]
        ),

        // ...
    ],
```

Custom List can use Closure to generate list:

```
// ...

    'middlewares' => [
        \Windwalker\DI\create(
            RedirectMiddleware::class,
            // ...
            list: raw(function (FooService $fooService) {
                return ...;
            });
        ),

        // ...
    ],
```

The custom list redirect status code default is `301`, if you want to use other status, set it to
`REDIRECT_DEFAULT_STATUS` env varialbe.

### Instant Redirect

[](#instant-redirect)

If there has some reason you can not wait `RedirectResponse` return, you may use instant redirect:

```
    // ...

    'middlewares' => [
        \Windwalker\DI\create(
            RedirectMiddleware::class,
            // ...
            instantRedirect: true,
        ),

        // ...
    ],
```

### Disable

[](#disable)

If you wanr to disable this middleware in debug mode, add this options:

```
        \Windwalker\DI\create(
            RedirectMiddleware::class,
            enabled: !WINDWALKER_DEBUG
        ),
```

### Hook

[](#hook)

Add `afterHit` hook that you can do somthing or log if redirect hit.

```
        \Windwalker\DI\create(
            RedirectMiddleware::class,
            afterHit: raw(function (string $dest, \Redirect $redirect) {
                \Windwalker\Core\Manager\Logger::info('Redirect to: ' . $dest);
            })
        ),
```

### RedirectMiddleware params

[](#redirectmiddleware-params)

```
    public function __construct(
        protected RedirectService $redirectService,
        protected AppContext $app,
        protected bool $enabled = true,
        protected string|\BackedEnum|array|false|null $type = 'main',
        protected array|\Closure|null $list = null,
        protected bool $instantRedirect = false,
        protected array $excludes = [],
        protected ?\Closure $afterHit = null,
        protected int $cacheTtl = 3600,
    ) {
    }
```

ParamTypeDefaultDescription`enabled``bool``true`Enable this middleware`type``mixed``main`Entity type, use `NULL` to select all types, `FALSE` to disable DB`list``array or Closure``null`Custom redirect list, will merge to DB items if type is not false.`instantRedirect``bool``false`If true, will redirect immediately without waiting `RedirectResponse` return, this may cause some issues, use with caution.`excludes``?Closure or array``null`Exclude some paths from redirect, support closure or array, if path match any exclude rule, redirect will not work.`afterHit``?Closure``null`A hook that will be called after a redirect hit, you can do something or log in this hook.`cacheTtl``int``3600`Cache lifetime in seconds, default is 3600 seconds.---

IP Allow/Block (Firewall)
-------------------------

[](#ip-allowblock-firewall)

To enable IP Rules, add `FirewallMiddleware` to `front.route.php`

```
use Lyrasoft\Firewall\Middleware\FirewallMiddleware;

    // ...

    ->middleware(
        FirewallMiddleware::class,
        // ...
        defaultAction: \Lyrasoft\Firewall\Enum\IpRuleKind::ALLOW, // Or BLOCK, default is ALLOW
        logger: 'firewall/blocks' // Or LoggerInterface instance, default is NULL
    )

    // ...
```

### Admin IP Rules Management

[](#admin-ip-rules-management)

Select Allow or Block, and enter the IP Range format:

[![](https://private-user-images.githubusercontent.com/1639206/353801836-b22e6598-fe16-4070-8182-9a471398afda.jpg?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ4NTczNzUsIm5iZiI6MTc3NDg1NzA3NSwicGF0aCI6Ii8xNjM5MjA2LzM1MzgwMTgzNi1iMjJlNjU5OC1mZTE2LTQwNzAtODE4Mi05YTQ3MTM5OGFmZGEuanBnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMzMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMzBUMDc1MTE1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MTFkNjBjZWI5MWFmZDg1NGVmZGQ0M2EwZGEwNjI3YTY4NzZhNTQ0MzRjYjQ3ZTcwNmQwODdiNTYyYTQwNDdkNSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.8e_RzQNm8LyP43fEt0naSSpmtngclIJ8h6wEGeuZQn4)](https://private-user-images.githubusercontent.com/1639206/353801836-b22e6598-fe16-4070-8182-9a471398afda.jpg?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ4NTczNzUsIm5iZiI6MTc3NDg1NzA3NSwicGF0aCI6Ii8xNjM5MjA2LzM1MzgwMTgzNi1iMjJlNjU5OC1mZTE2LTQwNzAtODE4Mi05YTQ3MTM5OGFmZGEuanBnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMzMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMzBUMDc1MTE1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MTFkNjBjZWI5MWFmZDg1NGVmZGQ0M2EwZGEwNjI3YTY4NzZhNTQ0MzRjYjQ3ZTcwNmQwODdiNTYyYTQwNDdkNSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.8e_RzQNm8LyP43fEt0naSSpmtngclIJ8h6wEGeuZQn4)

The supported formats:

TypeSyntaxDetailsIPV6`::1`Short notationIPV4`192.168.0.1`Range`192.168.0.0-192.168.1.60`Includes all IPs from *192.168.0.0* to *192.168.0.255*
and from *192.168.1.0* to *198.168.1.60*Wild card`192.168.0.*`IPs starting with *192.168.0*
Same as IP Range `192.168.0.0-192.168.0.255`Subnet mask`192.168.0.0/255.255.255.0`IPs starting with *192.168.0*
Same as `192.168.0.0-192.168.0.255` and `192.168.0.*`CIDR Mask`192.168.0.0/24`IPs starting with *192.168.0*
Same as `192.168.0.0-192.168.0.255` and `192.168.0.*`
and `192.168.0.0/255.255.255.0`And you can use `,` to separate multiple IPs or IP Ranges. For example, `0.0.0.0/0,::/0` means all IPs includes both IPV4 and IPV6.

We use [mlocati/ip-lib](https://github.com/mlocati/ip-lib) as IP Range parser.

### Paths and Domains

[](#paths-and-domains)

After `0.2.0` there has a paths textarea, you can set domains / URLs or paths that this rule will effect, 1 line for 1 path.

[![Image](https://private-user-images.githubusercontent.com/1639206/567391485-b682b032-8847-4e04-9933-f60867414282.jpg?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ4NTczNzUsIm5iZiI6MTc3NDg1NzA3NSwicGF0aCI6Ii8xNjM5MjA2LzU2NzM5MTQ4NS1iNjgyYjAzMi04ODQ3LTRlMDQtOTkzMy1mNjA4Njc0MTQyODIuanBnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMzMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMzBUMDc1MTE1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZThkNjQ5ODI1OWYwOGQ3NDRmMmU1ZmY4ODBiMjE2MWU4NWU1MWM4NzBiMmJjZjU4ZDk0NmI0Y2EzOTNmY2UyMSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.0_6kCIDU4ne5kAy31-mJv8H2z8jMnNCnozVV_yL4400)](https://private-user-images.githubusercontent.com/1639206/567391485-b682b032-8847-4e04-9933-f60867414282.jpg?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ4NTczNzUsIm5iZiI6MTc3NDg1NzA3NSwicGF0aCI6Ii8xNjM5MjA2LzU2NzM5MTQ4NS1iNjgyYjAzMi04ODQ3LTRlMDQtOTkzMy1mNjA4Njc0MTQyODIuanBnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMzMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMzBUMDc1MTE1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZThkNjQ5ODI1OWYwOGQ3NDRmMmU1ZmY4ODBiMjE2MWU4NWU1MWM4NzBiMmJjZjU4ZDk0NmI0Y2EzOTNmY2UyMSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.0_6kCIDU4ne5kAy31-mJv8H2z8jMnNCnozVV_yL4400)

For example, if you only want to block some IPs to access admin panel, you can set paths to `/admin/*`, or `/admin/login` if you only want to block access to login page.

You can also set domains or full URLs, for example, you can block some IPs to access `https://*.foo.com/bar`, or `https://foo.com/bar/*/baz`.

### Select DB Type

[](#select-db-type)

You can also access different type from `ip-rule/list/{type}`.

And set type name to middleware

```
    ->middleware(
        FirewallMiddleware::class,
        type: 'foo',
    )
```

type can also supports string, array and enum. Use `NULL` to select all, `FALSE` to disable DB.

### Custom List

[](#custom-list)

If you want to manually set ip list, `FirewallMiddleware` custom list must use 2 lists, `allowList` and `blockList`.

```
    ->middleware(
        FirewallMiddleware::class,
        type: false,
        allowList: [
            '0.0.0.0',
            '144.122.*.*',
        ],
        blockList: [
            '165.2.90.45',
            '222.44.55.66',
        ],
        allowAsFirst: true, // Or false, default is false
    )
```

### Disable

[](#disable-1)

If you wanr to disable this middleware in debug mode, add this options:

```
        \Windwalker\DI\create(
            FirewallMiddleware::class,
            enabled: !WINDWALKER_DEBUG
        ),
```

### Hook

[](#hook-1)

Add `afterHit` hook that you can do somthing or log if an IP was be blocked.

```
        \Windwalker\DI\create(
            FirewallMiddleware::class,
            afterHit: fn () => function (AppRequest $appRequest) {
                \Windwalker\Core\Manager\Logger::info('Attack from: ' . $appRequest->getClientIp());
            }
        ),
```

### FirewallMiddleware params

[](#firewallmiddleware-params)

ParamTypeDefaultDescription`enabled``bool``true`Enable this middleware`type``mixed``main`Entity type, use `NULL` to select all types, `FALSE` to disable DB`allowList``array``[]`Custom allow list, will merge to DB items.`blockList``array``[]`Custom block list, will merge to DB items.`allowAsFirst``bool``false`When use custom list, if an IP is in both allow and block list, this option will decide which rule is first.`excludes``?Closure or array``null`Exclude some paths from firewall, support closure or array, if path match any exclude rule, firewall will not work.`defaultAction``IpRuleKind``IpRuleKind::ALLOW`If no IP matched, this option will decide to allow or block this IP.`logger``LoggerInterface``string` or `NullLogger`Logger instance or logger name, default is `NullLogger`.`afterHit``?Closure``null`A hook that will be called after an IP was blocked, you can do something or log in this hook.`cacheTtl``int``3600`Cache lifetime in seconds, default is 3600 seconds.`clearExpiredChance``number``1/100`Every request has this chance to clear expired cache, default is `1/100`, set `0` to disable.Cache
-----

[](#cache)

### Cache Lifetime

[](#cache-lifetime)

Both middlewares has a `cacheTtl` param, default is `3600` seconds.

```
        \Windwalker\DI\create(
            FirewallMiddleware::class,
            cacheTtl: 3600,
            clearExpiredChance: 1 / 100 // Default is 1/100, means every 100 request will clear expired cache once, set it to 0 to disable auto clear expired cache
        ),
```

### Cache Clear

[](#cache-clear)

Everytime you edit `Redirect` or `IpRule` will auto clear all caches.

The cache files is located at `caches/firewall/`, and you can add `firewall` to clear cache command in `composer.json`

```
        "post-autoload-dump": [
        ...
        "php windwalker cache:clear renderer html firewall" group('front')
    ->namespace('front')
    ->middleware(
        HoneypotMiddleware::class,
        type: 'bot',
        matchParams: fn() => [
            '_ref' => 'preview'
        ],
        // Or use matchCallback to do more complex check:
        matchCallback: fn () => function (#[Input] string $foo) {
            return $foo === 'bar';
        },
        expires: '10minutes', // Default is 1 hour.
    )
    ->middleware(
        FirewallMiddleware::class,
        type: 'bot'
        // ...
    )
```

Then you can add an invisible link in your frontend page:

```
Preview
```

If a bot click this link, it will be blocked for 10 minutes. You can also set multiple block words:

```
matchParams: fn() => [
    '_ref' => [
        'preview',
        'internal',
        'test'
    ],
    'from' => [...],
    'f' => [...]
]
```

### Allow Good Bot

[](#allow-good-bot)

It is recommended to use the Honeypot for malicious bots that use random IPs and fake User-Agent strings to mimic real users. Legitimate bots can be identified by their User-Agent and will not click hidden links, so HoneypotMiddleware by default allows requests whose User-Agent contains the keyword `bot`, preventing accidental blocking of legitimate search engine crawlers and other good bots.

```
use Lyrasoft\Firewall\Middleware\HoneypotMiddleware;
use Lyrasoft\Firewall\Service\Honeypot;

// ...
    ->middleware(
        HoneypotMiddleware::class,
        // ...
        allowGoodRobot: true, // Default is true.
    )
```

If you want to detect bot by yourself, you can set `allowGoodRobot` to `false`, and use `excludes` to check if this request is an allowed bot:

```
use Lyrasoft\Firewall\Middleware\HoneypotMiddleware;
use Lyrasoft\Firewall\Service\Honeypot;

// ...
    ->middleware(
        HoneypotMiddleware::class,
        // ...
        allowGoodRobot: false,
        excludes: fn () => function (\Windwalker\Core\Http\AppRequest $appRequest) {
            $ua = $appRequest->getHeader('user-agent');

            // Allow Googlebot and Amazonbot, you can add more rules here.
            return str_contains($ua, 'googlebot') || str_contains($ua, 'amazonbot');
        }
    )
```

### Built-in Block Words

[](#built-in-block-words)

We provides a set of built-in block words, you can use `Honeypot::getBlockWords()` to get this list, and use it in `matchParams`:

```
use Lyrasoft\Firewall\Middleware\HoneypotMiddleware;
use Lyrasoft\Firewall\Service\Honeypot;

$router->group('front')
    ->namespace('front')
    ->middleware(
        HoneypotMiddleware::class,
        matchParams: fn() => [
            '_ref' => Honeypot::getBlockWords()
        ]
    )
```

And add auto link to page:

```
{!! $app->retrieve(\Lyrasoft\Firewall\Service\Honeypot::class)->link() !!}
```

`Honeypot` class provides a set of words will randomly add to the link, but only first 3 words will be block words, so you can add some fake words to make it more difficult for bots to detect the honeypot link.

You can set your own block words by:

```
HONEYPOT_BLOCK_WORDS=preview,internal,test,foo,bar,baz
```

Honeypot will auto use first 3 words as block words.

### HoneypotMiddleware params

[](#honeypotmiddleware-params)

```
public function __construct(
        protected AppContext $app,
        protected FirewallService $firewallService,
        protected BrowserNext $browserNext,
        protected string|\UnitEnum $type = 'bot',
        protected ?array $matchParams = null,
        protected ?\Closure $matchCallback = null,
        protected ?\Closure $blockHandler = null,
        protected bool $allowGoodRobot = true,
        protected \Closure|array|null $excludes = null,
        protected \DateTimeInterface|string $expires = '1hour',
    ) {
    }
```

ParamTypeDefaultDescription`type``mixed``bot`Entity type, use `NULL` to select all types, `FALSE` to disable DB.`matchParams``?array``null`Match params, for example, `['_ref' => ['hello', ...]]` will match URL params which in this list.`matchCallback``?Closure``null`A callback that will be called to check if this request should block, the callback should return `true` if it's a bot, or `false or null or void` if it's not.`blockHandler``?Closure``null`A callback that will be called when a bot is detected, you can do something or log in this callback. If not provided, will auto add IP to IpRules`allowGoodRobot``bool``true`Allow any bot which has `bot` keyword in User-Agent, if a bot trying to simulate a read user, we consider it as bad bot.`excludes``?Closure or array``null`Exclude some paths from honeypot, support closure or array, if path match any exclude rule, honeypot will not work.`expires``DateTimeInterface or string``1hour`The expires time for a bot, can be a `DateTimeInterface` instance or a string that can be parsed by `strtotime`, for example, `1hour`, `30min`, `2024-01-01 00:00:00`, etc.

###  Health Score

45

—

FairBetter than 93% of packages

Maintenance89

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 83.9% 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

Every ~74 days

Recently: every ~149 days

Total

9

Last Release

58d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1639206?v=4)[Simon Asika](/maintainers/asika32764)[@asika32764](https://github.com/asika32764)

---

Top Contributors

[![asika32764](https://avatars.githubusercontent.com/u/1639206?v=4)](https://github.com/asika32764 "asika32764 (26 commits)")[![yinminc](https://avatars.githubusercontent.com/u/28665511?v=4)](https://github.com/yinminc "yinminc (5 commits)")

---

Tags

luna-package

### Embed Badge

![Health badge](/badges/lyrasoft-firewall/health.svg)

```
[![Health](https://phpackages.com/badges/lyrasoft-firewall/health.svg)](https://phpackages.com/packages/lyrasoft-firewall)
```

###  Alternatives

[defuse/php-encryption

Secure PHP Encryption Library

3.9k162.4M214](/packages/defuse-php-encryption)[mews/purifier

Laravel 5/6/7/8/9/10 HtmlPurifier Package

2.0k16.7M113](/packages/mews-purifier)[robrichards/xmlseclibs

A PHP library for XML Security

41478.1M118](/packages/robrichards-xmlseclibs)[bjeavons/zxcvbn-php

Realistic password strength estimation PHP library based on Zxcvbn JS

87117.5M63](/packages/bjeavons-zxcvbn-php)[illuminate/encryption

The Illuminate Encryption package.

9229.7M280](/packages/illuminate-encryption)[paragonie/hidden-string

Encapsulate strings in an object to hide them from stack traces

7410.6M39](/packages/paragonie-hidden-string)

PHPackages © 2026

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