Honeypot + time-trap: невидимая защита форм без капчи
- security
- формы
- honeypot
- time-trap
- 152-ФЗ
Капча — самый дорогой UX-элемент на форме: ~5% реальных пользователей бросают заполнение, 1-2% не справляются с challenge’ем. На небольших B2B-сайтах с парой десятков заявок в день это лишняя плата за защиту, которой можно достичь дешевле. Honeypot + time-trap отсекают около 80% автоматических спам-ботов без единого пикселя в UI.
Контекст
Большинство форм-спама в 2026 году — это автоматические скрипты, которые либо парсят DOM целиком и заполняют все <input>, либо submit’ят форму через fetch за миллисекунды после загрузки страницы. Оба сценария ловятся примитивными серверными проверками без какого-либо ML или капчи.
На ozsm.ru мы поставили эту связку на формы /kontakt и /zayavka ещё до замены reCAPTCHA на Yandex SmartCaptcha — и именно она отсеивала большую часть спама. SmartCaptcha добавили позже как страховку для оставшихся 20%, и для compliance с 152-ФЗ.
Honeypot: невидимое поле-ловушка
Идея: добавить в форму поле, которое реальный пользователь никогда не заполнит, потому что не видит его. Бот, парсящий DOM «всё в один заход», заполнит. На сервере проверяем — если поле непустое, отбрасываем submit.
<input
type="text"
name="website"
class="absolute -left-[9999px] opacity-0"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
/>
Пять защит сразу:
absolute -left-[9999px]— поле физически за пределами вьюпорта. CSS-only, не влияет на layout.opacity-0— даже если CSS подгрузился частично, поле не видноtabindex="-1"— пользователь с клавиатуры не доскачет туда Tab’омautocomplete="off"— браузерный автозаполнятель не будет его трогатьaria-hidden="true"— скринридер не прочитает (не сбивает слепых пользователей)
Почему display: none хуже. Часть продвинутых ботов (Headless Chrome) умеет фильтровать display: none поля как «honeypot, не трогать». position: absolute в офф-вьюпорт — менее очевидная ловушка.
Почему имя website (а не bot_trap). Боты часто заполняют именно «website» автоматически — это распространённое поле в формах подписки. Оно работает как магнит.
Серверная проверка — одна строка:
if (formData.get('website')) {
return { ok: false, code: 'honeypot' };
}
Time-trap: проверка времени заполнения
Идея: бот сабмитит форму за миллисекунды, человек — за десятки секунд. Записываем время загрузки формы в скрытое поле, на сервере проверяем разницу.
Клиентская часть (Svelte 5 runes):
<script lang="ts">
let mountedAt = $state(0);
$effect(() => {
mountedAt = Date.now();
});
</script>
<form>
<input type="hidden" name="form_ts" value={mountedAt} />
<!-- ...остальные поля -->
</form>
$effect запускается на mount компонента. Свежее время кладётся в hidden поле. На submit оно уйдёт вместе с формой.
Серверная часть:
const formTs = Number(formData.get('form_ts'));
const elapsed = Date.now() - formTs;
if (!formTs || elapsed < 3000) {
return { ok: false, code: 'too_fast' };
}
if (elapsed > 30 * 60 * 1000) {
return { ok: false, code: 'too_old' };
}
Два порога:
< 3000 мс— невозможно заполнить даже короткую форму вручную> 30 мин— старая форма, токен protokolа устарел (защита от replay)
3 секунды — эмпирический порог: меньше = слишком много false-positive (быстрые пользователи на простых формах), больше = пропускаешь медленных ботов.
Server-side ОБЯЗАТЕЛЬНО
Главная ошибка — проверять honeypot/time-trap на клиенте. Бот пропустит JS вообще или просто не выполнит ваш if. Проверки только на сервере:
// app/src/lib/contact-handler.ts
export async function validateAndProcessContact(
formData: FormData,
opts: { verifyCaptcha: (t: string) => Promise<boolean>; sendEmail: (d: any) => Promise<void> }
): Promise<{ ok: boolean; code?: string; message?: string }> {
// 1. Honeypot
if (formData.get('website')) {
return { ok: false, code: 'honeypot' };
}
// 2. Time-trap
const formTs = Number(formData.get('form_ts'));
if (!formTs || Date.now() - formTs < 3000) {
return { ok: false, code: 'too_fast' };
}
// 3. Captcha (если включена)
const token = formData.get('smart-token') as string;
if (token && !(await opts.verifyCaptcha(token))) {
return { ok: false, code: 'captcha' };
}
// 4. Send
await opts.sendEmail({ /* ... */ });
return { ok: true };
}
Чистая функция с инъекцией зависимостей — тестируется vitest без mock’а HTTP-сервера.
Когда хватит без капчи (и когда нужна)
Хватит:
- B2B-сайт с 10-50 заявок в день
- Корпоративный лендинг
- Узкоспециализированная ниша (мало кому интересно автоматизировать спам именно сюда)
Уже мало:
- Высокотрафичные публичные формы (регистрация, подписка)
- Финансовые формы (заявка на кредит, ставки)
- Сайты в индустриях с organized spam (lead-gen, недвижимость, criminal)
Для нас на ozsm.ru было «хватит», но мы всё равно поставили SmartCaptcha — не из-за объёма спама, а из-за 152-ФЗ: с 1 июля 2025 reCAPTCHA в РФ запрещена для обработки ПД, штраф 1-6 млн ₽. Раз уж меняем, поставили лучшее доступное.
Что мы из этого вынесли
- Honeypot + time-trap отсекают 80% спама за 30 строк кода. Это базовая гигиена, должна быть на всех формах независимо от наличия капчи.
- Server-side обязательно. Клиент пропустит. Бот обходит JS, проверки на клиенте — вид защиты, а не защита.
- Капча — про compliance чаще, чем про эффективность. В 2026 году в РФ её ставят прежде всего из-за 152-ФЗ, а не потому что honeypot не работает.
Ссылки
- Yandex SmartCaptcha — официальная документация — для compliance с 152-ФЗ
- OWASP automated threats — анти-спам гайд — теоретическая база
- Bot Detection in 2025 — современный обзор — что меняется в инструментах ботов