daikazu / gibberish-name-detector
Catch gibberish names like "asdfgh" in Laravel validation — flags keyboard-mash and random-string input in name fields.
Package info
github.com/daikazu/gibberish-name-detector
pkg:composer/daikazu/gibberish-name-detector
Requires
- php: ^8.2
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^3.0 || ^4.0 || ^5.0
README
Gibberish Name Detector
A pure-PHP gibberish / random-string detector for Laravel name fields. It returns a
calibrated probability that a short string is mashed or random input (asdfgh,
qwerty, xkjqwz) rather than a real name, using a logistic-regression classifier over
engineered features (n-gram scores + structural signals), with an optional Bloom-filter
name gazetteer that whitelists known real names.
The output is a probability in [0, 1], not a hard truth. Use it to fail validation,
soft-warn ("Did you type your name correctly?"), or flag a submission for review.
How it works
Inference is pure PHP — n-gram table lookups, a dot product through a sigmoid, and a
Bloom membership test. There is no ML runtime, no FFI, no ext-* requirement, and no
network call on the inference path. The trained model and gazetteer ship as committed
PHP array files under resources/data/ (opcache-friendly; never json_decoded).
analyze() runs a short-circuit ladder:
- Normalize — lowercase;
letters=[a-z];tokens= split on non-letters (soMary-Jane,O'Brien,Van Bergscore per token). - Length gate — too short → not gibberish (
reason: too_short). - Gazetteer — every token is a known name → not gibberish (
reason: gazetteer). - Fast pre-reject — extreme junk (long repeats, keyboard walks, no vowels) →
gibberish (
reason: heuristic). - Classifier — logistic regression → probability;
gibberish = p >= threshold(reason: model).
Requirements
- PHP 8.2+ (8.3+ for Laravel 13)
- Laravel 12 or 13 (the core detector also runs framework-free via plain
new)
Installation
composer require daikazu/gibberish-name-detector
No datasets to download. The trained model and gazetteer ship committed with the package under
resources/data/. The external name lists referenced in Regenerating the artifacts are only needed if you want to retrain from scratch.
The service provider and Gibberish facade are auto-discovered. Publish the config to
tune the threshold or messaging:
php artisan vendor:publish --tag=gibberish-config
Usage
use Daikazu\GibberishNameDetector\GibberishDetector; $detector = new GibberishDetector(); // works without the container $detector->probability('asdfgh'); // 1.0 $detector->probability('Okonkwo'); // 0.0 (whitelisted by the gazetteer) $detector->isLikelyGibberish('xkjqwz'); // true $detector->threshold(); // 0.4586 (the model's calibrated default) $analysis = $detector->analyze('asdfgh'); $analysis->probability; // 1.0 $analysis->gibberish; // true $analysis->reason; // Reason::Heuristic (too_short | gazetteer | heuristic | model) $analysis->features; // raw feature values that fed the decision
Facade
use Daikazu\GibberishNameDetector\Laravel\Facades\Gibberish; Gibberish::isLikelyGibberish('asdfgh'); // true Gibberish::analyze('Nguyen')->reason; // Reason::Gazetteer
Validation
Rule object:
use Daikazu\GibberishNameDetector\Laravel\Rules\NotGibberish; $request->validate([ 'first_name' => ['required', 'string', new NotGibberish()], // override the threshold, minimum length, and/or message per field: 'last_name' => ['required', new NotGibberish(threshold: 0.7, minLength: 2, message: 'That :attribute looks off.')], ]);
String rule:
$request->validate([ 'name' => ['required', 'not_gibberish'], ]);
Both resolve the configured singleton when given no overrides.
Configuration
config/gibberish.php:
| Key | Env | Default | Meaning |
|---|---|---|---|
threshold |
GIBBERISH_THRESHOLD |
null |
Probability cutoff. null → the model's calibrated threshold. |
min_length |
GIBBERISH_MIN_LENGTH |
3 |
Below this letter count, never flagged. |
gazetteer |
GIBBERISH_GAZETTEER |
true |
Enable the known-name whitelist. |
message |
— | … | Validation failure message (:attribute supported). |
Threshold note. The shipped model carries its own calibrated threshold (~0.46, chosen by Youden's J). Leaving
thresholdnulluses it — this is what keeps recall ≥ 90% out of the box. SetGIBBERISH_THRESHOLDto a fixed value to override (e.g. raise it for fewer false positives when hard-failing validation).
The gibberish:check command
Inspect a single value (debug / manual QA):
$ php artisan gibberish:check asdfgh
value: asdfgh
letters: asdfgh
probability: 1.0000
gibberish: yes
reason: heuristic
threshold: 0.4586
Caveats
- It is a probability, not the truth. Real but unusual names will sometimes score high; treat a positive as a signal (warn / review), not a certainty — especially when hard-failing validation.
- Non-Anglo names. Features are computed on
[a-z]only (accented characters are dropped), so the classifier alone can flag names likeOkonkwo. The gazetteer is the fix: it whitelists ~92k known names across many locales. Extend it (see below) with the names your users actually have. - The gazetteer is fail-open. A Bloom false positive only ever accepts a string (spares a gibberish value) — never wrongly rejects a real one.
- Not a security control. Don't use it to block abuse; it's a UX aid for typo/garbage detection.
Regenerating the artifacts
Both artifacts are committed and reproducible from scratch with a fixed seed.
Corpora
The source name lists are third-party data — git-ignored, not redistributed.
See resources/corpora/SOURCES.md for the exact
sources and licenses (MIT and The Unlicense). Fetch them into resources/corpora/:
curl -fsSL https://raw.githubusercontent.com/dominictarr/random-name/master/first-names.txt \ -o resources/corpora/first-names.txt curl -fsSL https://raw.githubusercontent.com/smashew/NameDatabases/master/NamesDatabases/surnames/us.txt \ -o resources/corpora/us-surnames.txt
Model (resources/data/model.php)
php scripts/prepare-corpus.php # merge + dedup, split off a held-out calibration set php scripts/train.php resources/corpora/training.txt --max-train=40000 --epochs=3000 --seed=1 # or: php artisan gibberish:train resources/corpora/training.txt --max-train=40000 --epochs=3000
Negatives are synthesized (uniform-random, keyboard walks, letter-shuffled real names),
classes balanced, features standardized, logistic regression fit with L2, and the
threshold calibrated by Youden's J (or --target-fpr=). The calibration test asserts the
held-out FPR ≤ 8% and recall ≥ 90%.
Gazetteer (resources/data/gazetteer.php)
Add multi-locale lists so non-Anglo names are whitelisted — this is the lever that fixes
false positives. A curated supplement ships in resources/names/multilocale.txt.
php scripts/build-gazetteer.php \
resources/corpora/first-names.txt resources/corpora/us-surnames.txt \
resources/names/multilocale.txt --p=0.001 --seed=1
# or: php artisan gibberish:build-gazetteer <lists...> --p=0.001
Regenerating
model.php/gazetteer.phpis a deliberate, reviewed commit — note the corpus and command in the commit body.
Testing
composer test
vendor/bin/pint --test
License
The MIT License (MIT). See LICENSE.