imliamxo/shush

A configurable profanity filter for Laravel. Block or censor profanity with multi-language support.

Maintainers

Package info

github.com/imLiaMxo/Shush

pkg:composer/imliamxo/shush

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-04-10 14:25 UTC

This package is auto-updated.

Last update: 2026-05-10 14:44:09 UTC


README

A configurable, multi-language profanity filter for Laravel with severity tiers, per-language normalisers, and evasion-resistant detection.

Catches shit, sh!t, sh1t, $h!t, shiiit, sshit, s.h.i.t — and everything in between. Ships with extensive English and Polish dictionaries out of the box.

Installation

composer require imliamxo/shush
php artisan vendor:publish --tag=shush-config
php artisan vendor:publish --tag=shush-dictionaries   # optional

Quick Start

use ImLiaMxo\Shush\Facades\Shush;

Shush::check('Hello world');           // false
Shush::check('What the fuck');         // true
Shush::check('What the f*ck');         // true
Shush::check('What the f.u.c.k');      // true
Shush::check('What the fuuuck');       // true
Shush::check('No kurwa mać');          // true (Polish, if enabled)

Shush::censor('What the fuck');        // "What the ****"
Shush::clean($text);                   // censors or throws, per config

$hits = Shush::detect('Shit and kurwa');
// [
//   ['word' => 'shit', 'tier' => 'moderate', 'language' => 'en', 'match' => 'Shit'],
//   ['word' => 'kurwa', 'tier' => 'severe', 'language' => 'pl', 'match' => 'kurwa'],
// ]

Strictness Levels

Words are categorised into three severity tiers. The strictness setting controls which tiers are active:

Strictness Tiers caught Use case
relaxed severe only Catch slurs & hate speech, allow swearing
normal severe + moderate Block strong profanity (default)
strict severe + moderate + mild Family-friendly, zero tolerance
// Global
'strictness' => 'strict',

// Runtime
Shush::setStrictness('relaxed');

// Per-route
Route::post('/kids', ...)->middleware('shush:strict');
Route::post('/forum', ...)->middleware('shush:relaxed');

Evasion Detection

Each language has a dedicated normaliser that catches evasion techniques:

Technique Example Caught?
Leet-speak symbols sh!t, $h1t, b@stard
Number substitution sh1t, a55, f4g
Repeated characters shiiit, fuuuck, sshit
Separator tricks s.h.i.t, f-u-c-k, s h i t
Mixed evasion $h!iit, f.u.c.k
Unicode homoglyphs Cyrillic а/е/о lookalikes
Zero-width characters Hidden unicode between letters
Diacritic stripping Polish gównogowno
Diacritic substitution ąa, łl

Per-Language Normalisers

Each language can have its own normaliser that understands its character set and evasion patterns:

  • English — handles standard leet-speak, homoglyphs, basic Latin diacritics
  • Polish — handles ą↔a, ć↔c, ę↔e, ł↔l, ń↔n, ó↔o, ś↔s, ź↔z, ż↔z stripping

Adding a Custom Normaliser

<?php

namespace App\Normalisers;

use ImLiaMxo\Shush\Normalisers\BaseNormaliser;

class GermanNormaliser extends BaseNormaliser
{
    public function getLanguage(): string
    {
        return 'de';
    }

    protected function getExtraSubstitutions(): array
    {
        return [
            'ä' => 'a', 'ö' => 'o', 'ü' => 'u', 'ß' => 'ss',
        ];
    }

    protected function stripDiacritics(string $text): string
    {
        return strtr($text, [
            'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss',
            'Ä' => 'ae', 'Ö' => 'oe', 'Ü' => 'ue',
        ]);
    }

    protected function getDiacriticAlternates(string $char): array
    {
        return match ($char) {
            'a' => ['ä'],
            'o' => ['ö'],
            'u' => ['ü'],
            default => [],
        };
    }
}

Register it in config/shush.php:

'normalisers' => [
    'de' => \App\Normalisers\GermanNormaliser::class,
],

Or at runtime:

Shush::registerNormaliser('de', \App\Normalisers\GermanNormaliser::class);

Built-in False-Positive Whitelist

Shush ships with an extensive whitelist of 200+ words that contain profanity substrings but aren't profane. This prevents false positives on words like:

Place names: Scunthorpe, Penistone, Cockermouth, Middlesex, Sussex, Essex, Effingham

Common words: class, classic, assess, assessment, asset, assign, assist, associate, assume, assembly, assault, bass, brass, glass, grass, mass, pass, passport, passion, passive, embarrass, harassment

Compound words: cocktail, cockatoo, cockerel, cockpit, peacock, Hitchcock, shuttlecock

Other: therapist, grapefruit, skyscraper, dictionary, predict, constitution, document, accumulate, circumstance, Mississippi

Polish false positives are also covered: kurier, kurort, kurczak, duplikat.

// Remove a built-in whitelist entry if needed
Shush::disallow(['cocktail']);

// Add to whitelist
Shush::allow(['mycustomword']);

Adding Languages

Create a folder in resources/shush/dictionaries/:

resources/shush/dictionaries/
├── en/
│   └── words.php
├── pl/
│   └── words.php
└── de/
    ├── insults.php
    └── slurs.php

Each file returns an array keyed by tier:

<?php
return [
    'severe' => ['schlimmsteswort'],
    'moderate' => ['scheiße', 'arschloch', 'wichser'],
    'mild' => ['mist', 'verdammt', 'kacke'],
];

Flat arrays (no tier keys) are treated as moderate. All .php files in a language folder are merged.

Enable in config:

'languages' => ['en', 'pl', 'de'],

Modes

Censor (default)

Shush::censor('What the fuck');     // "What the ****"
Shush::setMask('#')->censor('...');  // "What the ####"

// Full-word replacement (config: mask_behaviour = 'full')
// → "What the [REDACTED]"

Block

try {
    Shush::clean($input);
} catch (ProfanityDetectedException $e) {
    $e->getDetectedWords(); // ['fuck']
}

Middleware

// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'shush' => \ImLiaMxo\Shush\Middleware\ShushMiddleware::class,
    ]);
})

Route::post('/comment', ...)->middleware('shush');
Route::post('/kids', ...)->middleware('shush:strict');
Route::post('/forum', ...)->middleware('shush:relaxed');

Validation Rule

use ImLiaMxo\Shush\Rules\NoProfanity;

$request->validate([
    'comment' => ['required', 'string', new NoProfanity],
]);

Runtime API

Shush::setStrictness('strict');
Shush::setMode('block');
Shush::setMask('#');
Shush::setNormalise(false);

Shush::addWords(['customword']);                              // moderate, first language
Shush::addWords(['slur'], 'severe', 'en');                    // specific tier + language
Shush::allow(['damn']);                                        // whitelist
Shush::disallow(['cocktail']);                                  // un-whitelist

Shush::registerNormaliser('de', GermanNormaliser::class);       // custom normaliser

Shush::getWords();                    // all dictionaries
Shush::getWords('severe', 'en');      // English severe words
Shush::getActiveWords();              // words at current strictness
Shush::getWhitelist();                // full whitelist
Shush::getStrictness();               // 'relaxed' | 'normal' | 'strict'
Shush::getLanguages();                // ['en', 'pl']

Configuration Reference

Key Default Description
mode censor censor or block
strictness normal relaxed, normal, or strict
normalise true Enable evasion detection
normalisers [] Custom normaliser class map ['de' => MyClass::class]
mask * Mask character for censor mode
mask_behaviour character character or full
mask_replacement *** Replacement when mask_behaviour is full
languages ['en'] Dictionary folders to load
extra_words [] Additional words (string or {word, tier, language})
whitelist [] Extra words to never flag (merged with built-in whitelist)
middleware_fields [] Request fields to scan (empty = all strings)

Architecture

src/
├── Shush.php                          # Core engine
├── ShushServiceProvider.php           # Laravel auto-discovery
├── Facades/Shush.php                  # Facade
├── Exceptions/
│   └── ProfanityDetectedException.php
├── Middleware/
│   └── ShushMiddleware.php            # Route middleware
├── Rules/
│   └── NoProfanity.php                # Validation rule
├── Normalisers/
│   ├── NormaliserInterface.php        # Contract
│   ├── BaseNormaliser.php             # Shared logic
│   ├── NormaliserFactory.php          # Registry / factory
│   ├── EnglishNormaliser.php          # English-specific
│   └── PolishNormaliser.php           # Polish-specific
└── Dictionaries/
    ├── en/
    │   └── words.php                  # ~350 entries, 3 tiers
    └── pl/
        └── words.php                  # ~450 entries, 3 tiers

License

MIT