Sign-up guard: honeypot is read-only until a trusted focus and ignores browser autofill (own email); masked value logging

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-13 04:57:51 -05:00
parent 2bd612d9ba
commit 4caa0ef5a8
19 changed files with 33 additions and 19 deletions
+12 -1
View File
@@ -154,12 +154,23 @@ window.IAP = (function () {
host.querySelectorAll('.ic-btn').forEach(b => b.addEventListener('click', () => { host.innerHTML = ''; host.hidden = true; resolve(b.textContent); }, { once: true }));
});
}
// honeypot fields (join + sign-in): read-only until a trusted focus, so browser autofill and
// password managers leave them alone; a value that appeared without a trusted event is ignored
function armHoneypots() {
document.querySelectorAll('.hp-field').forEach(el => {
if (el.dataset.armed) return; el.dataset.armed = '1'; el.readOnly = true;
const touch = e => { if (e.isTrusted) { el.readOnly = false; el.dataset.touched = '1'; } };
el.addEventListener('focus', touch); el.addEventListener('input', touch); el.addEventListener('keydown', touch);
});
}
armHoneypots(); document.addEventListener('DOMContentLoaded', armHoneypots);
const hpValue = el => (el && el.dataset.touched === '1') ? (el.value || '') : '';
async function requestCode(email, opts) {
const o = opts || {};
let pick = null;
for (let i = 0; i < 4; i++) {
const r = await (await fetch('/api/auth/email/start', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, fts: FORM_TS, hp_field_x9: (o.honeypot && o.honeypot.value) || '', pick }) })).json();
body: JSON.stringify({ email, fts: FORM_TS, hp_field_x9: hpValue(o.honeypot), pick }) })).json();
if (r.challenge && o.host) { pick = await iconCheck(o.host, r.challenge, r.error); continue; }
if (r.error) throw new Error(r.error);
return r;