Sign-up guard: a filled honeypot triggers the icon check instead of a silent drop (form-filler extensions fill hidden fields)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -139,9 +139,10 @@ function codeChallenge(rec) {
|
||||
const guardLog = (req, why, b) => console.log('signup-guard', why, clientIp(req), String(b.email || '').replace(/^(.).*(@.*)$/, '$1***$2'));
|
||||
function codeGuard(req, b) {
|
||||
const now = Date.now();
|
||||
// honeypot: bots fill it, humans never see it. The field carries a nonsense name so browser
|
||||
// autofill (which likes "website" and "url") cannot fill it for a real person.
|
||||
if (b.hp_field_x9) { guardLog(req, 'honeypot', b); return { status: 200, body: { ok: true, sent: true } }; }
|
||||
// honeypot: bots fill it, humans never see it. Some form-filler extensions fill every field,
|
||||
// hidden or not (seen 2026-09-11), so a filled honeypot is a CHALLENGE, never a silent drop:
|
||||
// a person passes the icon check and gets the code, a bot cannot.
|
||||
const hp = !!b.hp_field_x9;
|
||||
const fts = Number(b.fts) || 0;
|
||||
if (!fts || now - fts < CODE_LIMITS.minFormMs) { guardLog(req, 'form-age', b); return { status: 400, body: { error: 'Give the page a second, then tap again.' } }; }
|
||||
if (now - fts > 12 * 3600 * 1000) { guardLog(req, 'form-stale', b); return { status: 400, body: { error: 'This page has been open a long time. Refresh it, then tap again.' } }; }
|
||||
@@ -152,12 +153,12 @@ function codeGuard(req, b) {
|
||||
const rec = codeHits.get(ip) || { t: [], passUntil: 0, chal: null };
|
||||
rec.t = rec.t.filter(ts => now - ts < 24 * 3600 * 1000);
|
||||
const n10 = rec.t.filter(ts => now - ts < 10 * 60 * 1000).length;
|
||||
const limited = n10 >= CODE_LIMITS.per10m || rec.t.length >= CODE_LIMITS.perDay;
|
||||
const limited = hp || n10 >= CODE_LIMITS.per10m || rec.t.length >= CODE_LIMITS.perDay;
|
||||
if (limited && now >= rec.passUntil) {
|
||||
const pick = String(b.pick || '');
|
||||
if (pick && rec.chal && rec.chal.exp > now && pick === rec.chal.answer) { rec.passUntil = now + CODE_LIMITS.passMs; rec.chal = null; }
|
||||
else {
|
||||
guardLog(req, pick ? 'wrong-pick' : 'ip-limit', b); codeTrip(req, ip);
|
||||
guardLog(req, pick ? 'wrong-pick' : hp ? 'honeypot-challenge' : 'ip-limit', b); if (!hp) codeTrip(req, ip);
|
||||
const challenge = codeChallenge(rec); codeHits.set(ip, rec);
|
||||
return { status: 429, body: { error: pick ? 'That was not it. Try once more.' : 'Quick check before we send another code.', challenge } };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user