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
+1 -1
View File
@@ -108,7 +108,7 @@ function head(o) {
+ '</head><body><div class="wrap bl">';
}
function tail() {
return '</div><script src="/assets/common.js?v=20260912e"></script><script src="/assets/blog-page.js?v=20260912a"></script></body></html>';
return '</div><script src="/assets/common.js?v=20260913a"></script><script src="/assets/blog-page.js?v=20260912a"></script></body></html>';
}
const authorBox = () => '<div class="author"><img src="/logo-icon.png" alt="InstantAdPay"><div><b>' + AUTHOR + '</b><span>Founder of InstantAdPay and the Crypto Team Build Network. Twenty-plus years of internet marketing, and a habit of writing down what actually worked.</span></div></div>';
const cardHtml = p => '<a class="post-card" href="/blog/' + esc(p.slug) + '">' + (p.cover ? '<img src="' + esc(p.cover) + '" alt="" loading="lazy" style="width:100%;border-radius:10px;margin:0 0 12px">' : '') + '<h2>' + esc(p.title) + '</h2><p>' + esc(p.excerpt) + '</p><p class="meta">' + fmtDate(p.publishedAt || p.created) + ' · ' + readMinutes(p.body) + ' min read' + (p.tags.length ? ' · ' + p.tags.map(esc).join(', ') : '') + '</p></a>';
+1 -1
View File
@@ -396,7 +396,7 @@
</div>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/admin.js?v=20260912c"></script>
</body>
</html>
+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;
+1 -1
View File
@@ -141,7 +141,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/contract.js?v=20260908p"></script>
<script src="/assets/chat.js?v=20260906m"></script>
</body>
+1 -1
View File
@@ -26,7 +26,7 @@
<p class="muted small">You decide whether, and how much, to spend. Never spend more than you can afford to lose.</p>
</div>
</div></section>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/legal.js?v=20260908a"></script>
</body>
</html>
+1 -1
View File
@@ -67,6 +67,6 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
</body>
</html>
+1 -1
View File
@@ -461,7 +461,7 @@
</div>
</section>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/wallet.js?v=20260909c"></script>
<script src="/assets/home.js?v=20260912a"></script>
<script src="/assets/chat.js?v=20260906m"></script>
+1 -1
View File
@@ -156,7 +156,7 @@
InstantAdPay · <a href="/contract">Contract</a> · <a href="/terms">Terms</a> · <a href="/privacy">Privacy</a> · <a href="/disclaimer">Disclaimer</a>
</div>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/join.js?v=20260912d"></script>
</body>
</html>
+1 -1
View File
@@ -85,7 +85,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/launch.js?v=20260911b"></script>
</body>
</html>
+1 -1
View File
@@ -37,7 +37,7 @@
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
</footer>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/ledger.js?v=20260906m"></script>
<script src="/assets/chat.js?v=20260906m"></script>
</body>
+1 -1
View File
@@ -912,7 +912,7 @@
</div>
</div>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/wallet.js?v=20260911a"></script>
<script src="/assets/promo.js?v=20260911a"></script>
<script src="/assets/my.js?v=20260912i"></script>
+1 -1
View File
@@ -128,7 +128,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford to lose.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/partners.js?v=20260912a"></script>
</body>
</html>
+1 -1
View File
@@ -192,7 +192,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/plays.js?v=20260910b"></script>
</body>
</html>
+1 -1
View File
@@ -28,7 +28,7 @@
<p class="muted small">We use reasonable safeguards, but no system is perfectly secure. Protect your email and your wallet.</p>
</div>
</div></section>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/legal.js?v=20260908a"></script>
</body>
</html>
+1 -1
View File
@@ -36,7 +36,7 @@
<p class="muted small" style="margin-top:18px">See also the <a href="/disclaimer">Disclaimer</a> and <a href="/privacy">Privacy Policy</a>.</p>
</div>
</div></section>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/legal.js?v=20260908a"></script>
</body>
</html>
+1 -1
View File
@@ -34,7 +34,7 @@
<p><a href="/ledger">← Back to the live ledger</a> · <a href="/contract">Read the contract review</a></p>
</div>
</section>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/tx.js?v=20260906m"></script>
</body>
</html>
+1 -1
View File
@@ -47,7 +47,7 @@
</div>
</div>
</section>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/wall.js?v=20260911v"></script>
</body>
</html>
+1 -1
View File
@@ -121,7 +121,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never share your recovery phrase.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260912e"></script>
<script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/wallets.js?v=20260911b"></script>
</body>
</html>
+4 -1
View File
@@ -148,7 +148,10 @@ function codeGuard(req, b) {
// 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 hpRaw = String(b.hp_field_x9 || '').trim();
const hpIsEmail = !!hpRaw && hpRaw.toLowerCase() === String(b.email || '').trim().toLowerCase();
const hp = !!hpRaw && !hpIsEmail; // 2026-09-13: phones were autofilling the hidden field; own email = autofill, not a bot
if (hpRaw) console.log('signup-guard honeypot-value', clientIp(req), hpIsEmail ? 'own-email' : (/^https?:/i.test(hpRaw) ? 'url' : /@/.test(hpRaw) ? 'other-email' : /^\+?[\d\s()-]{6,}$/.test(hpRaw) ? 'phone' : 'text'), 'len=' + hpRaw.length, hpIsEmail ? '' : hpRaw.slice(0, 2) + '***');
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.' } }; }