Compare commits

...

2 commits

Author SHA1 Message Date
Artemii Peretiachenko
1fb33e1bd8 Hide contact honeypot with inline styles so it stays invisible without CSS.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 19:17:25 +02:00
Artemii Peretiachenko
1d5596db5f Fix contact honeypot so browser autofill does not swallow submissions.
Rename the trap field away from "website" and keep legacy detection so real messages still reach Telegram.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 19:16:36 +02:00
11 changed files with 57 additions and 38 deletions

View file

@ -104,6 +104,6 @@ Backup is written even if Telegram fails (`telegram_ok: false`), so you still ha
## Behaviour notes ## Behaviour notes
- Honeypot field `website`: if filled, returns `200` without notifying Telegram or writing a backup. - Honeypot field `hp_company` (legacy `website` still checked): if filled, returns `200` without notifying Telegram or writing a backup.
- In-memory rate limit: 5 POSTs per IP per 60 seconds → `429`. - In-memory rate limit: 5 POSTs per IP per 60 seconds → `429`.
- Field limits: name/contact ≤ 200 chars, message ≤ 4000. - Field limits: name/contact ≤ 200 chars, message ≤ 4000.

View file

@ -114,8 +114,10 @@ def validate_fields(data: dict) -> tuple[str, str, str] | str:
return "invalid" return "invalid"
# Honeypot — treat as success upstream; caller checks separately. # Honeypot — treat as success upstream; caller checks separately.
website = data.get("website", "") # Accept legacy "website" (autofill magnets) and current "hp_company".
if isinstance(website, str) and website.strip(): for pot in ("hp_company", "website"):
raw_pot = data.get(pot, "")
if isinstance(raw_pot, str) and raw_pot.strip():
return "honeypot" return "honeypot"
def field(key: str, max_len: int) -> str | None: def field(key: str, max_len: int) -> str | None:

View file

@ -428,10 +428,10 @@
<h3>Contact form</h3> <h3>Contact form</h3>
<form id="contactForm" data-status-loading="..." data-status-success="Sent ✓" data-status-error="Could not send. Please try again."> <form id="contactForm" data-status-loading="..." data-status-success="Sent ✓" data-status-error="Could not send. Please try again.">
<label class="hp-field" aria-hidden="true"> <div class="hp-field" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;margin:0;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0;pointer-events:none">
<span>Website</span> <label for="hp_company">Company fax</label>
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input id="hp_company" type="text" name="hp_company" value="" tabindex="-1" autocomplete="off">
</label> </div>
<label> <label>
<span>Name</span> <span>Name</span>

View file

@ -277,10 +277,10 @@
<h3>Contact form</h3> <h3>Contact form</h3>
<form id="contactForm" data-status-loading="..." data-status-success="Sent ✓" data-status-error="Could not send. Please try again."> <form id="contactForm" data-status-loading="..." data-status-success="Sent ✓" data-status-error="Could not send. Please try again.">
<label class="hp-field" aria-hidden="true"> <div class="hp-field" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;margin:0;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0;pointer-events:none">
<span>Website</span> <label for="hp_company">Company fax</label>
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input id="hp_company" type="text" name="hp_company" value="" tabindex="-1" autocomplete="off">
</label> </div>
<label> <label>
<span>Name</span> <span>Name</span>

View file

@ -428,10 +428,10 @@
<h3>Форма для связи</h3> <h3>Форма для связи</h3>
<form id="contactForm" data-status-loading="..." data-status-success="Отправлено ✓" data-status-error="Не удалось отправить. Попробуйте ещё раз."> <form id="contactForm" data-status-loading="..." data-status-success="Отправлено ✓" data-status-error="Не удалось отправить. Попробуйте ещё раз.">
<label class="hp-field" aria-hidden="true"> <div class="hp-field" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;margin:0;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0;pointer-events:none">
<span>Website</span> <label for="hp_company">Company fax</label>
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input id="hp_company" type="text" name="hp_company" value="" tabindex="-1" autocomplete="off">
</label> </div>
<label> <label>
<span>Имя</span> <span>Имя</span>

View file

@ -333,10 +333,10 @@
<h3 data-i18n="form.title"></h3> <h3 data-i18n="form.title"></h3>
<form id="contactForm" data-status-loading="{{FORM_STATUS_LOADING}}" data-status-success="{{FORM_STATUS_SUCCESS}}" data-status-error="{{FORM_STATUS_ERROR}}"> <form id="contactForm" data-status-loading="{{FORM_STATUS_LOADING}}" data-status-success="{{FORM_STATUS_SUCCESS}}" data-status-error="{{FORM_STATUS_ERROR}}">
<label class="hp-field" aria-hidden="true"> <div class="hp-field" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;margin:0;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0;pointer-events:none">
<span>Website</span> <label for="hp_company">Company fax</label>
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input id="hp_company" type="text" name="hp_company" value="" tabindex="-1" autocomplete="off">
</label> </div>
<label> <label>
<span data-i18n="form.nameLabel"></span> <span data-i18n="form.nameLabel"></span>

View file

@ -182,10 +182,10 @@
<h3 data-i18n="form.title"></h3> <h3 data-i18n="form.title"></h3>
<form id="contactForm" data-status-loading="{{FORM_STATUS_LOADING}}" data-status-success="{{FORM_STATUS_SUCCESS}}" data-status-error="{{FORM_STATUS_ERROR}}"> <form id="contactForm" data-status-loading="{{FORM_STATUS_LOADING}}" data-status-success="{{FORM_STATUS_SUCCESS}}" data-status-error="{{FORM_STATUS_ERROR}}">
<label class="hp-field" aria-hidden="true"> <div class="hp-field" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;margin:0;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0;pointer-events:none">
<span>Website</span> <label for="hp_company">Company fax</label>
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input id="hp_company" type="text" name="hp_company" value="" tabindex="-1" autocomplete="off">
</label> </div>
<label> <label>
<span data-i18n="form.nameLabel"></span> <span data-i18n="form.nameLabel"></span>

View file

@ -441,8 +441,25 @@ a:hover{opacity:0.7;}
} }
#contactForm label{display:block; margin-bottom:20px; font-size:13px; color:var(--text);} #contactForm label{display:block; margin-bottom:20px; font-size:13px; color:var(--text);}
#contactForm .hp-field{ #contactForm .hp-field{
position:absolute; left:-10000px; top:auto; width:1px; height:1px; position:absolute !important;
overflow:hidden; opacity:0; pointer-events:none; left:-10000px !important;
top:auto !important;
width:1px !important;
height:1px !important;
margin:0 !important;
padding:0 !important;
overflow:hidden !important;
clip:rect(0,0,0,0) !important;
border:0 !important;
opacity:0 !important;
pointer-events:none !important;
}
#contactForm .hp-field input{
width:1px !important;
height:1px !important;
margin:0 !important;
padding:0 !important;
border:0 !important;
} }
#contactForm input, #contactForm textarea{ #contactForm input, #contactForm textarea{
width:100%; margin-top:6px; padding:10px; border:1px solid var(--border); width:100%; margin-top:6px; padding:10px; border:1px solid var(--border);

View file

@ -277,10 +277,10 @@
<h3>Форма для связи</h3> <h3>Форма для связи</h3>
<form id="contactForm" data-status-loading="..." data-status-success="Отправлено ✓" data-status-error="Не удалось отправить. Попробуйте ещё раз."> <form id="contactForm" data-status-loading="..." data-status-success="Отправлено ✓" data-status-error="Не удалось отправить. Попробуйте ещё раз.">
<label class="hp-field" aria-hidden="true"> <div class="hp-field" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;margin:0;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0;pointer-events:none">
<span>Website</span> <label for="hp_company">Company fax</label>
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input id="hp_company" type="text" name="hp_company" value="" tabindex="-1" autocomplete="off">
</label> </div>
<label> <label>
<span>Имя</span> <span>Имя</span>

View file

@ -428,10 +428,10 @@
<h3>Форма для зв'язку</h3> <h3>Форма для зв'язку</h3>
<form id="contactForm" data-status-loading="..." data-status-success="Надіслано ✓" data-status-error="Не вдалося надіслати. Спробуйте ще раз."> <form id="contactForm" data-status-loading="..." data-status-success="Надіслано ✓" data-status-error="Не вдалося надіслати. Спробуйте ще раз.">
<label class="hp-field" aria-hidden="true"> <div class="hp-field" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;margin:0;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0;pointer-events:none">
<span>Website</span> <label for="hp_company">Company fax</label>
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input id="hp_company" type="text" name="hp_company" value="" tabindex="-1" autocomplete="off">
</label> </div>
<label> <label>
<span>Ім'я</span> <span>Ім'я</span>

View file

@ -277,10 +277,10 @@
<h3>Форма для зв'язку</h3> <h3>Форма для зв'язку</h3>
<form id="contactForm" data-status-loading="..." data-status-success="Надіслано ✓" data-status-error="Не вдалося надіслати. Спробуйте ще раз."> <form id="contactForm" data-status-loading="..." data-status-success="Надіслано ✓" data-status-error="Не вдалося надіслати. Спробуйте ще раз.">
<label class="hp-field" aria-hidden="true"> <div class="hp-field" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;margin:0;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0;pointer-events:none">
<span>Website</span> <label for="hp_company">Company fax</label>
<input type="text" name="website" tabindex="-1" autocomplete="off"> <input id="hp_company" type="text" name="hp_company" value="" tabindex="-1" autocomplete="off">
</label> </div>
<label> <label>
<span>Ім'я</span> <span>Ім'я</span>