Отсечь спам в форме можно тремя способами: поставить капчу и потерять часть живых людей, подключить внешний сервис и зависеть от сети, либо проверять саму заявку по десятку признаков. PHP Form Spam Checker 2.1.2 идёт третьим путём: конвейер проверок спама из двадцати модулей — от скрытой приманки и анализа времени заполнения до репутации IP, SPF/DMARC и байесовского классификатора. Ни капчи, ни зависимостей Composer во время работы: только PHP 8.5 и два расширения. Ниже — как устроен порядок проверок, что именно даёт новая версия, и что получилось при моих собственных запусках на PHP 8.5.0.

TL;DR: что это и как проверить за десять минут
- Что это. PHP-библиотека и CLI: конвейер из 20 проверок, опциональный WAF, вердикты с доказательствами.
- Требования. PHP 8.5 и новее,
ext-json,ext-mbstring; зависимостей Composer во время работы нет. - Как вызывать.
php bin/spam-checkс JSON-payload; коды выхода0чисто,1подозрительно,2спам,3ошибка. - Что нового в 2.1.2. Опциональная проверка кириллицы в полях имени для русскоязычных форм, по умолчанию выключена.
- Важное ограничение. На Packagist пакета нет —
composer requireиз README не сработает, установка только из архива или клона.
php -v # нужен PHP 8.5+
php bin/spam-check --help # список опций, код 0
printf '%s' "$PAYLOAD" | php bin/spam-check --config=/etc/spam.php --stdin --json
Проблема, которую это решает
Форма обратной связи — самый простой вход в приложение и самый частый канал спама. Капча решает проблему ценой конверсии: часть людей уходит, не разобравшись с картинками, а часть не проходит их с телефона. Внешние сервисы добавляют сетевую зависимость: недоступен API — форма либо пропускает всё, либо блокирует живых людей.
Второй эффект — отсутствие объяснений. «Спам» без причины невозможно разобрать: непонятно, что именно сработало, и как ослабить правило, не открыв дверь. Здесь каждое решение несёт доказательства: какая проверка сработала, сколько баллов дала и почему.
Третья проблема — стоимость. Дешёвые проверки должны идти первыми, а дорогие (внешние запросы к репутации IP, DNS) — только после них, иначе каждая заявка тянет сетевой вызов. В конвейере это зафиксировано явно: pipeline.stop_on_spam по умолчанию останавливает обработку на первом жёстком спаме.
Что такое PHP Form Spam Checker и из чего он состоит
| Компонент | Значение |
|---|---|
| Тип | PHP-библиотека, CLI и HTTP-эндпоинт |
| Версия в замерах | 2.1.2 |
| Требуется | PHP 8.5+, ext-json, ext-mbstring |
| Зависимости Composer при работе | ноль (только расширения PHP) |
| Проверок в конвейере | 20 |
| Правил WAF | 9 |
PHP-файлов в src/ |
93 (10 295 строк) |
| Тестовый набор по README | 559 тестов, 2 015 проверок |
flowchart TB
REQ["HTTP-запрос"] --> WAF{"WAF включён?"}
WAF -- да --> RULES["правила WAF: метод, URI, query, поля, cookies, заголовки, тело"]
RULES -- блок --> BLOCK["403 или запись в журнал в режиме monitor"]
RULES -- пропуск --> FORM["отправка формы"]
WAF -- нет --> FORM
FORM --> BEH["поведение: приманка, time trap, JS-челлендж"]
BEH --> HEUR["локальные эвристики: UA, частота, длина, SQLi, XSS, ссылки, слова, email, повторы, алфавиты"]
HEUR --> NET["репутация: AbuseIPDB, DNSBL"]
NET --> DOMAIN["домен: SPF, DMARC"]
DOMAIN --> BAYES["байесовский классификатор"]
BAYES --> AGG["агрегация: жёсткие вердикты и сумма баллов"]
AGG --> VERDICT{"чисто / подозрительно / спам"}Лицензия — MIT, одна строка, как и договаривались.
Каталоги устроены прямолинейно: src/ — библиотека (проверки, WAF, конвейер, адаптеры), bin/ — три CLI-моста (spam-check, waf-check, spam-train), integrations/ — готовые точки встраивания (guard.php, waf.php, auto-prepend.php), public/ — HTTP-эндпоинт с JSON, config/ — примеры конфигурации и настройки на сайт.
Возможности: что проверяется
Поведение и идентификация
| Проверка | По умолчанию | Эффект |
|---|---|---|
honeypot |
включена | заполненное скрытое поле — жёсткий спам, 100 |
dynamic_honeypot |
включена при секрете ≥ 16 символов | заполнено — спам 100; нет ожидаемого поля — 10 |
time_trap |
выключена | слишком быстро или подделка — спам 80; нет метки — 30 |
js_challenge |
выключена | нет или неверный ответ — 15, никогда жёстко |
user_agent |
включена | пустой — 40; строка инструмента (curl/) — жёсткий спам 100 |
rate_limit |
включена, 8 запросов за 900 с | превышение — жёсткий спам 100 |
autoban |
выключена | забаненный IP — спам 100 до всех проверок |
Полезная нагрузка, инъекции и контент
| Проверка | По умолчанию | Эффект |
|---|---|---|
length |
включена, максимум 5000 | длинно — 30; больше 20 000 или управляющие символы — спам 100 |
sql_injection |
включена | сильная сигнатура — спам 100; слабая — 40 |
xss |
включена | скрипт или обработчик — спам 100; закодированное — 45 |
links |
включена, максимум 2 | лишняя ссылка 15; сокращатель 35; обфускация 25 |
keywords |
включена | 12 за попадание, максимум 90 |
email |
включена | битый адрес 40; одноразовый домен 70 |
repetition |
включена | повторы 25, максимум 60 |
language |
выключена | смешанные алфавиты 20 (максимум 40); нет кириллицы в имени — missing_score |
Сеть, домен и модель
| Проверка | По умолчанию | Эффект |
|---|---|---|
abuseipdb |
выключена | уверенность ≥ 90 — жёсткий спам |
dnsbl |
выключена | попадание в список — спам 100 |
spf |
выключена | нет пригодной политики — 10 |
dmarc |
выключена | нет записи — 10; p=none — 5 |
bayes |
выключена | P(spam) ≥ 0,92 — балл равен вероятности × 100 |
Секция dkim зарезервирована под будущую проверку и в конвейер не входит — это сказано прямо в документации, а не оставлено на догадки.
Из таблиц видна логика порогов. Баллы складываются, но не любые: часть проверок ставит «жёсткий спам» и обрывает дальнейшую работу — это приманка, строка инструмента в UA, превышение частоты, сильная сигнатура инъекции. Остальные дают очки в общую сумму, и решение принимается по порогам «чисто / подозрительно / спам». Поэтому одна слабая проверка не приводит к блокировке, а сочетание нескольких — приводит. И поэтому же настраивать фильтр надо по тому, какие проверки включены, а не по одному числу порога.
Отдельно про выключенные по умолчанию проверки — сеть (AbuseIPDB, DNSBL, SPF, DMARC) и модель. Причины две: внешние запросы стоят времени и квоты, а байесовскому классификатору нужна обучающая выборка конкретного сайта, иначе он приносит больше шума, чем пользы. Включать их стоит после того, как базовый конвейер показал себя на живом трафике.
Опция версии 2.1.2: требование кириллицы в имени
Релиз маленький по объёму и важный по смыслу: появилась опциональная проверка кириллицы в полях имени.
| Что | Как работает | По умолчанию |
|---|---|---|
language.require_cyrillic |
поля вида name, fio, first_name должны содержать хотя бы одну кириллическую букву |
выключено |
language.missing_score |
сколько баллов даёт имя без кириллицы | 40 |
language.max_score |
потолок баллов этой проверки | 40 |
language.fields |
список проверяемых полей | набор имён по умолчанию |
Есть и менее очевидный слой настроек — пороги вердикта. Именно они решают, где заканчивается «подозрительно» и начинается «спам»: одно и то же количество баллов при разных порогах даёт разный код выхода и, соответственно, разное поведение сайта. Менять пороги без контрольных заявок нельзя: слишком низкий порог начнёт блокировать людей, слишком высокий — пропускать спам, и оба эффекта видны только по журналу за несколько дней.
Важная деталь, которую легко не заметить: письма, компания и свободный текст не проверяются никогда — только поля-имена. Иначе правило ловило бы нормальные заявки от людей, которые пишут латиницей.
Автор отдельно, прямо в заметках к релизу, описал исправление: ранее опубликованный архив 2.1.2 собирался из коммита aea672a, в его текстах правило было описано как включённое в эталонных конфигурациях, хотя фактически оно выключено. Архив пересобран из коммита 6a38c45, старый SHA-256 (dce99231…) помечен как заменённый. Это редкий случай, когда исправление не спрятано: «документировано, а не молча».
Как это устроено внутри
flowchart LR
IN["payload: form, fields, context"] --> CFG{"конфиг задан?"}
CFG -- нет --> DEF["умолчания: часть проверок выключена"]
CFG -- да --> RUN["конвейер проверок"]
DEF --> RUN
RUN --> HARD{"жёсткий вердикт?"}
HARD -- да --> SPAM["спам, код 2, stop_on_spam"]
HARD -- нет --> SUM["сумма баллов против порогов"]
SUM --> CLEAN["чисто, код 0"]
SUM --> SUSP["подозрительно, код 1"]
RUN --> ERR["ошибка проверки: error и пропуск, форма работает, код 3 только у оболочки"]Каждый шаг возвращает не «да/нет», а объект результата: статус (ham, spam, error, uncertain), баллы и доказательства — какие поля сработали и почему. Именно из этих объектов собирается итоговый вердикт и журнальная строка. Практическая польза видна при разборе ложного срабатывания: не надо воспроизводить заявку вручную — достаточно открыть журнал и прочитать, какая проверка дала баллы.
Порядок проверок — не список «что есть», а именно последовательность: дешёвые шаги впереди, дорогие сзади. Упавшая проверка записывается как error и не блокирует форму: лучше пропустить заявку, чем потерять клиента из-за недоступного API репутации.
| Шаг | Что происходит | Почему в этом месте |
|---|---|---|
| WAF | если включён — проверка метода, URI, query, полей, cookies, заголовков, тела | отсекает явные атаки до разбора формы |
| Поведение | приманка, анализ времени, JS-челлендж | не требует внешних данных |
| Эвристики | UA, частота, длина, SQLi, XSS, ссылки, слова, email, повторы | локальные, быстрые, детерминированные |
| Репутация | AbuseIPDB, DNSBL | сеть, дорого — только если раньше не поймали |
| Домен | SPF, DMARC | DNS-запросы |
| Модель | байесовский классификатор | по обучающей выборке сайта |
flowchart TD LANG["language.enabled и require_cyrillic"] --> L1["по умолчанию выключено: правила алфавитов не считаются"] SCORE["language.missing_score"] --> CAP["обрезается до language.max_score (40)"] NETC["abuseipdb.enabled, dnsbl"] --> NET1["по умолчанию выключено: нет сети в конвейере"] WAFC["waf.mode"] --> W1["по умолчанию off; начинать с monitor"] LOGPII["log.pii"] --> P1["по умолчанию false: журнал псевдонимизирован"]
Замеры и цифры
Всё ниже получено мной на этой машине: PHP 8.5.0 (CLI, NTS), архив релиза скачан из GitHub и проверен по контрольной сумме.
| Что измерено | Значение | Как измерено |
|---|---|---|
| SHA-256 архива | 9295006…23eaa, совпал с SHA256SUMS.txt |
Get-FileHash против файла из релиза |
| Архив | 360 743 байта | загрузка gh release download v2.1.2 |
| Версия PHP локально | 8.5.0 | php -v |
| Проверок в одном прогоне | 12 | вывод bin/spam-check на тестовой заявке |
| Чистая заявка, правило кириллицы выключено | clean, балл 0, код 0 |
запуск с конфигом по умолчанию |
Та же заявка, require_cyrillic включён |
suspicious, балл 40, код 1 |
запуск с language.enabled=true |
missing_score = 60 |
балл остался 40 | обрезка по language.max_score = 40 (Config.php) |
| Заявка с именем на кириллице | clean, балл 0, код 0 |
тот же конфиг |
| Явный спам-текст | spam, балл 100, код 2 |
превышение порогов и признак автоматизации |
| Пакет на Packagist | 404 | repo.packagist.org/p2/…json и поиск: 0 результатов |
vendor/ в архиве |
отсутствует | распаковка архива |
composer на машине |
отсутствует | Get-Command composer |

Три вывода из таблицы. Первый: опция 2.1.2 работает ровно как обещано — включили, и латинское имя сдвигает вердикт с clean в suspicious. Второй: missing_score = 60, указанный в заметках как «достаточно для спама», на деле даёт 40, потому что language.max_score по умолчанию 40; чтобы получить жёсткий спам, нужно поднимать именно потолок, а не очко. Третий: composer require из README и AGENTS.md сейчас не сработает — на Packagist пакета нет, а vendor/ в архиве отсутствует, поэтому тесты (php vendor/bin/phpunit) в этой поставке тоже не запустить без установки PHPUnit.
Почему это круто
- Без капчи. Форму проходит живой человек, а не тот, кто умеет разбирать картинки.
- Двадцать проверок по порядку. Дешёвые шаги впереди, сетевые — только после них, квота внешнего API не тратится на очевидный спам.
- Доказательства в вердикте. В ответе видно, какая проверка сработала, сколько дала баллов и на каком поле.
- Жёсткие вердикты отдельно от суммы. Заполненная приманка или строка
curl/в UA не «складываются» с остальным — это спам сразу. - Ноль зависимостей. Библиотека грузится через собственный
autoload.phpи работает безvendor/. - Упавшая проверка не блокирует форму.
errorфиксируется, а не превращается в отказ живому человеку. - Опции по умолчанию выключены. Сеть, WAF и проверка алфавитов включаются осознанно, на своём сайте.
- Есть CLI-мост. Тот же конвейер вызывается из не-PHP бэкенда через
bin/spam-check, коды выхода описаны контрактом.
Установка
| Путь | Что даёт | Работает ли сейчас |
|---|---|---|
composer require paulmann/php-form-spam-checker:2.1.2 |
библиотека в проект | нет: на Packagist пакета нет (404) |
архив релиза php-form-spam-checker-2.1.2.tar.gz |
всё дерево, включая тесты и документацию | да, SHA-256 сверяется с SHA256SUMS.txt |
клон по тегу v2.1.2 |
то же плюс git-история | да |
# проверка требований
php -v # 8.5 или новее
php -m | grep -E 'json|mbstring' # оба расширения
php bin/spam-check --help # список опций, код 0
Дальше два способа встроить: скопировать дерево и подключить autoload.php из своего кода, либо использовать готовую точку из integrations/ — guard.php для формы, waf.php для всего запроса, auto-prepend.php для раннего перехвата.
Настройка
Настройка — это файл конфигурации (PHP-массив или JSON) и флаг --config, либо переменная SPAM_CHECKER_CONFIG. Без конфига конвейер тихо работает на умолчаниях, и это первая ловушка эксплуатации.
| Ключ | По умолчанию | Когда менять |
|---|---|---|
language.enabled |
false |
русскоязычная форма, где имена всегда кириллицей |
language.require_cyrillic |
false |
то же, вместе с предыдущим |
language.missing_score |
40 |
ослабить или усилить правило |
language.max_score |
40 |
поднять, если нужно «спам» одним правилом |
pipeline.stop_on_spam |
true |
выключить, если нужно собирать все доказательства |
abuseipdb.enabled, dnsbl |
false |
когда есть ключ и готовность к сетевым запросам |
waf.mode |
off |
начинать с monitor, не с block |
log.pii |
false |
оставить выключенным: журнал псевдонимизирован намеренно |
Пример конфигурации для сайта, где нужна проверка кириллицы, выглядит так: секция language с enabled и require_cyrillic в true, missing_score равным 40, и путь к хранилищу в домашнем каталоге приложения. Всё остальное можно оставить по умолчанию: базовые проверки уже дают большую часть эффекта, а сеть и модель добавляются позже, когда есть чем их проверить.
Проверка после установки
Перед проверкой полезно понять, что именно вы тестируете. Формулировка «форма не пропускает спам» ничего не значит без контрольных заявок: нужны три эталона (нормальная заявка, заявка с подозрительным признаком и явный спам) и ожидаемые вердикты для них. Тогда любая настройка проверяется сравнением с эталоном, а не на глаз по живому трафику.
Всё это можно сделать без сети и без боевого сайта: CLI-мост принимает JSON и работает на копии конфигурации. Мой прогон занял одну команду на каждый случай, и именно он показал расхождение с примером из заметок к релизу — про missing_score 60 против фактических 40.
| Проверка | Ожидание | Что означает отклонение |
|---|---|---|
php bin/spam-check --help |
код 0 и список опций | код 3 — ошибка окружения или синтаксиса |
| чистая заявка с конфигом | clean, код 0 |
suspicious на нормальной заявке — порог слишком низкий |
| заявка с именем латиницей при включённом правиле | suspicious, балл 40, код 1 |
балл 0 — правило не включилось |
| заявка с явным спамом | spam, код 2 |
код 0 — проверки отключены конфигом |
| журнал вердиктов | одна JSON-строка на отправку, без персональных данных | персональные данные в журнале — log.pii включён напрасно |
Подводные камни
composer requireне работает. Пакета на Packagist нет: страница пакета и метаданные отдают 404, поиск — 0 результатов. Установка только из архива релиза или клона.vendor/в архиве отсутствует. Значитphp vendor/bin/phpunitиз инструкции требуетcomposer install, а Composer нужен отдельно: в этой поставке тесты «из коробки» не запускаются.missing_scoreобрезается потолком. Значение 60 в заметках к релизу даёт на практике 40:language.max_scoreпо умолчанию 40, и поднимать нужно его.- Без
--configконвейер молчит о важном. Отсутствующий конфиг означает умолчания, где сеть, WAF и алфавиты выключены; ничего не сломается, но и защиты будет меньше, чем ожидалось. uncertain— это не «чисто». Пустой результат сетевой проверки означает «не проверялось»: сеть выключена или запрос не удался.- WAF по умолчанию
off. Если ждали блокировку, её не будет; включать надо осознанно и начинать сmonitor. - Расхождение в числе тестов. В заметках к релизу — 560 тестов, в README — 559 тестов и 2 015 проверок на конкретном коммите. Разница в один тест не критична, но показывает, что цифры в двух документах считались в разных состояниях дерева.
- Жёсткие вердикты перебивают сумму. Заполненная приманка или маркер автоматизации в UA — сразу спам, независимо от остальных баллов; при разборе ложных срабатываний смотреть надо на них первыми.
- Правило про кириллицу ломает формы с иностранной аудиторией. Если форма общая и на ней пишут и по-русски, и по-английски, включённое
require_cyrillicбудет давать баллы половине нормальных заявок; в такой ситуации правило не включают или делят формы по языку.
Чек-лист внедрения
- Проверить окружение: PHP 8.5+,
json,mbstring, затемphp bin/spam-check --help. - Скачать архив релиза и сверить SHA-256 с
SHA256SUMS.txt. - Написать конфиг на сайт: путь к хранилищу и журналу, нужные проверки, сети — по готовности.
- Прогнать три заявки: чистую, с латинским именем при включённом правиле, с явным спамом.
- Сверить коды выхода с ожиданием: 0, 1, 2; код 3 — ошибка окружения.
- Включить журнал вердиктов и убедиться, что персональные данные в него не попадают.
- Только потом включать WAF — сначала в режиме
monitor, и смотреть, что он поймал бы.
Критерий «работает»: чистая заявка даёт clean и код 0, спам — код 2, спорная — код 1, а в журнале на каждую отправку есть строка с перечнем сработавших проверок.
FAQ
Нужна ли капча вместе с этим конвейером?
Нет, капчи здесь нет вовсе: вместо неё приманка, анализ времени, эвристики и модель. Если спам всё же проходит, ослаблять надо конкретную проверку, а не добавлять капчу сверху.
Почему composer require не находит пакет?
Потому что он не опубликован на Packagist: страница и метаданные отдают 404, поиск по имени даёт ноль результатов. Ставьте из архива релиза или клона по тегу.
Почему балл 40, если я задал missing_score 60?
Потому что у проверки есть потолок language.max_score, по умолчанию 40. Чтобы правило давало больше, поднимайте потолок, иначе значение обрежется.
Что будет, если внешняя проверка недоступна?
Она запишется как error или uncertain и не заблокирует форму. Это осознанный выбор: лучше пропустить заявку, чем потерять живого человека из-за недоступного API.
Можно ли вызывать конвейер не из PHP?
Да: bin/spam-check принимает JSON или base64 в аргументе либо --stdin и возвращает JSON, а коды выхода — часть контракта (0, 1, 2, 3).
Чем language отличается от проверки языка как таковой?
Она смотрит алфавиты: смешение латиницы с кириллицей в одном тексте даёт 20 баллов (максимум 40), а включённое require_cyrillic требует хотя бы одной кириллической буквы в поле имени.
Стоит ли включать проверку байесовским классификатором сразу?
Нет: без обучающей выборки конкретного сайта модель даёт случайные баллы. Сначала работают базовые проверки, затем на живом журнале оценивается доля спорных заявок, и только после этого модель включается на выборке, где есть и спам, и нормальные письма.
Что будет, если в форму пишут люди латиницей?
При включённом require_cyrillic их имена будут получать баллы и могут давать «подозрительно». Поэтому правило выключено по умолчанию и включается только там, где аудитория действительно пишет кириллицей; в остальных случаях работает лишь проверка смешанных алфавитов.
Итог
PHP Form Spam Checker 2.1.2 — зрелая вещь для узкой задачи: он не пытается быть WAF и антивирусом, а честно раскладывает проверку заявки на двадцать шагов с баллами и доказательствами. В версии 2.1.2 появилась ровно одна новая опция — требование кириллицы в поле имени, и она по умолчанию выключена, что для русскоязычных форм правильно: включать её нужно там, где имена действительно пишут кириллицей. Два практических замечания на выходе: пакета нет на Packagist (установка только из архива, а vendor/ в архиве отсутствует), и missing_score в примерах стоит читать вместе с потолком max_score.
Цитата: Антиспам без объяснений — это лотерея: ценно не то, что форма заблокировала заявку, а то, что она может сказать, какая проверка и почему это сделала.
Связанные материалы
- PHP-Form-Spam-Checker — репозиторий — код, проверки, WAF и документация.
- Релиз v2.1.2 — описание правила кириллицы и история с пересборкой архива.
- AGENTS.md репозитория — контракт для агентов: команды, коды выхода, ловушки.
- Плоская сеть и блокировка SMB — пример разбора, где главное — отделить настоящую защиту от её имитации.
- github-agent: гигиена репозитория одной командой — инструмент того же автора для проверки документации.
LLM Summary
- Тема:
PHP Form Spam Checker2.1.2 — PHP-библиотека и CLI с конвейером из 20 проверок для форм, без капчи и без зависимостей Composer во время работы. - Требования: PHP 8.5+,
ext-json,ext-mbstring; лицензия MIT. - Коды выхода:
bin/spam-check— 0 чисто, 1 подозрительно, 2 спам, 3 ошибка;bin/waf-check— 0 пропуск, 2 блок, 3 ошибка. - Новое в 2.1.2: опциональная проверка
language.require_cyrillic— поля имени должны содержать кириллицу;language.enabledиrequire_cyrillicпо умолчаниюfalse. - Мои замеры (PHP 8.5.0): SHA-256 архива совпал с
SHA256SUMS.txt; чистая заявка —clean, 0, код 0; та же заявка с включённым правилом —suspicious, 40, код 1;missing_score = 60дал 40 из-за потолкаlanguage.max_score; спам-заявка —spam, 100, код 2; на Packagist 404 и поиск даёт 0 результатов;vendor/в архиве отсутствует. - Грабли:
composer requireиз README не работает; тесты требуютcomposer install; отсутствие--configозначает умолчания с выключенными сетью, WAF и алфавитами;uncertainзначит «не проверялось», а не «чисто»; WAF по умолчаниюoff. - Расхождение в документации: заметки к релизу говорят о 560 тестах, README — о 559 тестах и 2 015 проверках на конкретном коммите.
- Ссылки: https://github.com/paulmann/PHP-Form-Spam-Checker, https://github.com/paulmann/PHP-Form-Spam-Checker/releases/tag/v2.1.2
Промт для агента (установка и настройка)
Готовый промт для AI-агента: скопируйте блок целиком кнопкой и отдайте своему ассистенту. Шаги, коды выхода и ловушки взяты из AGENTS.md репозитория — это инструкция автора для агентов, а не пересказ.
Задача: подключить PHP Form Spam Checker к форме на сайте и доказать, что конвейер различает чистую заявку, спорную и спам.
Материалы:
- репозиторий: https://github.com/paulmann/PHP-Form-Spam-Checker
- релиз: https://github.com/paulmann/PHP-Form-Spam-Checker/releases/tag/v2.1.2
- контракт исполнителя: https://github.com/paulmann/PHP-Form-Spam-Checker/blob/main/AGENTS.md
Установка (Packagist не содержит пакет — это проверено: 404):
gh release download v2.1.2 -R paulmann/PHP-Form-Spam-Checker -D ./rel
# сверить SHA-256 архива с SHA256SUMS.txt
tar -xzf rel/php-form-spam-checker-2.1.2.tar.gz -C /opt
Проверка перед использованием:
php -v # 8.5 или новее
php -m | grep -E 'json|mbstring'
php bin/spam-check --help # код 0 и список опций
Вызов (неинтерактивно, секреты только из окружения):
printf '%s' "$PAYLOAD" | php bin/spam-check --config=/etc/spam.php --form=contact --stdin --json
Коды выхода — контракт: spam-check 0 чисто, 1 подозрительно, 2 спам, 3 ошибка; waf-check 0 пропуск, 2 блок, 3 ошибка. Изменение кода — повод остановиться и сообщить, а не подстраиваться.
Настройка для русскоязычной формы (по умолчанию правило выключено):
'language' => ['enabled' => true, 'require_cyrillic' => true, 'missing_score' => 40, 'max_score' => 40]
# missing_score обрезается до max_score: чтобы правило давало «спам», поднимать нужно max_score
Запрещено:
- включать log.pii и публиковать сырые заявки: журнал псевдонимизирован намеренно;
- направлять examples/ или CLI-пробу на боевой сайт или реальный ящик;
- включать WAF сразу в режиме block: начинать с monitor;
- добавлять зависимость Composer времени выполнения;
- считать uncertain доказательством чистоты: это «не проверялось»;
- запускать без --config или SPAM_CHECKER_CONFIG: умолчания выключают сеть, WAF и алфавиты.
Что считать успехом: чистая заявка даёт clean и код 0, заявка с латинским именем при включённом правиле — suspicious и код 1, явный спам — код 2, в журнале на каждую отправку одна строка с перечнем сработавших проверок и без персональных данных.