glitchr / omnishield
Omnishield: one contract for guarding a form - is this token valid (a captcha: ALTCHA, Turnstile, reCAPTCHA), is this content spam (Akismet), are this address, this e-mail, this name known for abuse (StopForumSpam, disposable e-mail domains) - whoever answers; and its Symfony bridge: a form field, a
Requires
- php: >=8.2
- symfony/http-client-contracts: ^3.0
Requires (Dev)
- phpunit/phpunit: ^11.0
- psr/cache: ^2.0|^3.0
- symfony/cache: ^6.4|^7.0|^8.0
- symfony/config: ^6.4|^7.0|^8.0
- symfony/dependency-injection: ^6.4|^7.0|^8.0
- symfony/form: ^6.4|^7.0|^8.0
- symfony/http-client: ^6.4|^7.0|^8.0
- symfony/http-foundation: ^6.4|^7.0|^8.0
- symfony/http-kernel: ^6.4|^7.0|^8.0
- symfony/routing: ^6.4|^7.0|^8.0
- symfony/translation: ^6.4|^7.0|^8.0
- symfony/validator: ^6.4|^7.0|^8.0
- symfony/yaml: ^6.4|^7.0|^8.0
- twig/twig: ^3.0
Suggests
- omnishield/akismet: Akismet: is this content spam - comment-check, submit-spam, submit-ham
- omnishield/altcha: ALTCHA: a proof of work served and checked by the site itself - no third party, no cookie, no consent; the default
- omnishield/disposable: Disposable e-mail domains: a free list shipped in the package, no call
- omnishield/recaptcha: Google reCAPTCHA: v2 checkbox and invisible, v3 score, Enterprise assessments
- omnishield/stopforumspam: StopForumSpam: are this IP address, this e-mail, this name reported for abuse
- omnishield/turnstile: Cloudflare Turnstile: a token checked by Cloudflare's siteverify
- psr/cache: A spent token remembered in a PSR-6 pool (Omnishield\Replay\CacheReplayStore) rather than in this process's memory
- symfony/config: In a Symfony application: the configuration of Omnishield\Bridge\Symfony\OmnishieldBundle
- symfony/dependency-injection: In a Symfony application: the services of Omnishield\Bridge\Symfony\OmnishieldBundle
- symfony/form: In a Symfony application: Omnishield\Bridge\Symfony\Form\ChallengeType
- symfony/http-kernel: In a Symfony application: Omnishield\Bridge\Symfony\OmnishieldBundle itself
- symfony/routing: In a Symfony application: the route that serves an ALTCHA challenge
- symfony/translation: In a Symfony application: the widgets' texts in the visitor's language (domain omnishield)
- symfony/validator: In a Symfony application: the PassesChallenge constraint
- twig/twig: With Twig: Omnishield\Bridge\Twig\OmnishieldExtension, the widget printed by omnishield_widget()
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-16 14:38:04 UTC
README
One contract for guarding a form, whoever answers. Three questions, and a gateway answers the one that concerns it:
- is this token valid? - a captcha: ALTCHA, Cloudflare Turnstile, Google reCAPTCHA;
- is this content spam? - a classifier: Akismet;
- are this address, this e-mail, this name known for abuse? - a list: StopForumSpam, the disposable e-mail domains.
$widget = $registry->challenge('forms')->widget('contact'); // what the page shows: script, element, field echo $widget->html(); // inside the <form> $verdict = $registry->challenge('forms')->verify(Attempt::fromPost($_POST, $widget, $ip)); $verdict->passed; // false: $verdict->reasons - missing, invalid, duplicate... $registry->classifier('comments')->classify($submission)->isSpam(); $registry->reputation('emails')->lookup(new Identity($ip, $email))->known;
This package holds the contract (ChallengeInterface, ClassifierInterface,
ReputationInterface, GatewayFactory, Registry), the models (Widget, Attempt, Verdict,
Submission, Classification, Identity, Reputation, Capabilities), the store of spent
tokens (ReplayStoreInterface), Testing\FixedGateway for an application's tests, and a bridge
for Symfony. It needs no framework: it requires PHP and symfony/http-client-contracts (the
interfaces, no client). Each gateway is a package of its own:
| Package | It answers | Third party | Cookies |
|---|---|---|---|
omnishield/altcha |
is this token valid: a proof of work the site issues and checks itself | none (the widget's script: yours, or jsDelivr's) | none |
omnishield/turnstile |
is this token valid: Cloudflare's widget, Cloudflare's siteverify | Cloudflare | none |
omnishield/recaptcha |
is this token valid: v2 checkbox and invisible, v3 score, Enterprise | _GRECAPTCHA |
|
omnishield/akismet |
is this content spam: ham, spam, flagrant; reports back | Akismet (Automattic) | - |
omnishield/stopforumspam |
is this identity known: the public database, a threshold of the site's | StopForumSpam | - |
omnishield/disposable |
is this e-mail's domain disposable: a free list shipped in the package | none | - |
Formerly glitchr/omniguard, renamed on 2026-10-10: on Packagist the vendor omniguard belongs to
another project, so the family's providers could not be published under it. The namespace is
Omnishield\, the bundle OmnishieldBundle, its configuration omnishield:.
What a gateway is, and is not
A gateway answers. It does not decide what a refusal means for the form, nor what to do when its
provider does not answer: the application does. A refusal is a Verdict, a Classification, a
Reputation - never an exception; a provider that did not answer is an
UnreachableException, a key it refused an InvalidKeyException, and neither is ever taken
for "fine".
It is told strings - a token, an address, a text, a name - and never an account, an entity or a request.
What a gateway cannot do is a NotSupportedException, and its Capabilities say so beforehand:
a captcha classifies nothing, a list without a key takes no report.
Documentation
- Installation and a first form
- Models
- The three questions: the contracts, the gateways, writing one
- Symfony: the bundle, the form field, the constraint, the route, Twig
- Privacy: who sees what, cookies, consent
- The Docker harness: the console, bare PHP
Plain PHP
composer require glitchr/omnishield omnishield/altcha omnishield/disposable
use Omnishield\Altcha\AltchaGatewayFactory; use Omnishield\Disposable\DisposableGatewayFactory; use Omnishield\Registry; $registry = new Registry([new AltchaGatewayFactory($spentTokens), new DisposableGatewayFactory()], [ 'forms' => ['factory' => 'altcha', 'options' => ['hmac_key' => getenv('ALTCHA_HMAC_KEY')]], 'emails' => ['factory' => 'disposable'], ]);
No bundle, no container: a factory per gateway package, the registry built by hand. docs/installation.md opens on a whole script that runs as it is.
Symfony
Omnishield\Bridge\Symfony\OmnishieldBundle does that wiring in a Symfony application, and adds a
form field, a constraint, the route of the ALTCHA challenge and a Twig function
(docs/symfony.md); none of their components is required by this package.
omnishield: gateways: forms: { factory: altcha, options: { hmac_key: '%env(ALTCHA_HMAC_KEY)%' } } emails: { factory: disposable }
$builder->add('captcha', ChallengeType::class, ['action' => 'contact']);
Docker: every gateway, bare PHP
cd docker && cp .env.dist .env docker compose run --rm omnishield gateways docker compose run --rm omnishield bare # plain PHP: no bundle, no container, and what PHP loaded docker compose run --rm omnishield bare --live # the same against the providers, with their testing keys docker compose run --rm omnishield test
Responsibility
glitchr/omnishield and its omnishield/* providers are independent, unofficial clients: none is
affiliated with, endorsed by or sponsored by the services it reaches. Each service keeps its own
terms (accounts and keys, rate limits, commercial or non-commercial use, what becomes of the
visitors' data), named with a link in the "Responsibility" section of the README of each provider
that reaches one. The MIT licence covers this code only and grants no right to any service or to its
data.
Whoever installs and runs these packages is responsible for complying with those terms, for obtaining whatever they require, and for any use made of what they return. The authors publish them free of charge, make no commercial use of them, and accept no liability for how they are used (see LICENSE).
License: MIT since 2026-10-09; earlier versions remain published under LGPL-3.0-or-later.