daikazu/gibberish-name-detector

Catch gibberish names like "asdfgh" in Laravel validation — flags keyboard-mash and random-string input in name fields.

Maintainers

Package info

github.com/daikazu/gibberish-name-detector

pkg:composer/daikazu/gibberish-name-detector

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-11 13:40 UTC

This package is auto-updated.

Last update: 2026-08-11 13:44:37 UTC


README

Logo for Gibberish Name Detector

Gibberish Name Detector

Latest Version on Packagist Tests Code Style Total Downloads License

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:

  1. Normalize — lowercase; letters = [a-z]; tokens = split on non-letters (so Mary-Jane, O'Brien, Van Berg score per token).
  2. Length gate — too short → not gibberish (reason: too_short).
  3. Gazetteer — every token is a known name → not gibberish (reason: gazetteer).
  4. Fast pre-reject — extreme junk (long repeats, keyboard walks, no vowels) → gibberish (reason: heuristic).
  5. 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 threshold null uses it — this is what keeps recall ≥ 90% out of the box. Set GIBBERISH_THRESHOLD to 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 like Okonkwo. 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.php is 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.