diff --git a/blog.js b/blog.js index f7e2e0b..168b05e 100644 --- a/blog.js +++ b/blog.js @@ -108,7 +108,7 @@ function head(o) { + '
'; } function tail() { - return '
'; + return ''; } const authorBox = () => '
InstantAdPay
' + AUTHOR + 'Founder of InstantAdPay and the Crypto Team Build Network. Twenty-plus years of internet marketing, and a habit of writing down what actually worked.
'; const cardHtml = p => '' + (p.cover ? '' : '') + '

' + esc(p.title) + '

' + esc(p.excerpt) + '

' + fmtDate(p.publishedAt || p.created) + ' · ' + readMinutes(p.body) + ' min read' + (p.tags.length ? ' · ' + p.tags.map(esc).join(', ') : '') + '

'; diff --git a/public/admin.html b/public/admin.html index 1dc9d99..72df072 100644 --- a/public/admin.html +++ b/public/admin.html @@ -396,7 +396,7 @@ - + diff --git a/public/assets/common.js b/public/assets/common.js index 7dedf91..bd8a6b0 100644 --- a/public/assets/common.js +++ b/public/assets/common.js @@ -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; diff --git a/public/contract.html b/public/contract.html index eecd2c3..8b2a5d2 100644 --- a/public/contract.html +++ b/public/contract.html @@ -141,7 +141,7 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.
- + diff --git a/public/disclaimer.html b/public/disclaimer.html index 094e790..4d0110d 100644 --- a/public/disclaimer.html +++ b/public/disclaimer.html @@ -26,7 +26,7 @@

You decide whether, and how much, to spend. Never spend more than you can afford to lose.

- + diff --git a/public/earning.html b/public/earning.html index 7a9cf68..db14492 100644 --- a/public/earning.html +++ b/public/earning.html @@ -67,6 +67,6 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees.
- + diff --git a/public/index.html b/public/index.html index d399420..a5fabca 100644 --- a/public/index.html +++ b/public/index.html @@ -461,7 +461,7 @@ - + diff --git a/public/join.html b/public/join.html index bdb5f28..79e4c30 100644 --- a/public/join.html +++ b/public/join.html @@ -156,7 +156,7 @@ InstantAdPay · Contract · Terms · Privacy · Disclaimer - + diff --git a/public/launch.html b/public/launch.html index a430aea..05d78c0 100644 --- a/public/launch.html +++ b/public/launch.html @@ -85,7 +85,7 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees.
- + diff --git a/public/ledger.html b/public/ledger.html index 64abdca..42063c0 100644 --- a/public/ledger.html +++ b/public/ledger.html @@ -37,7 +37,7 @@
InstantAdPay · how it works · contract source ↗
- + diff --git a/public/my.html b/public/my.html index 199e0d7..759891a 100644 --- a/public/my.html +++ b/public/my.html @@ -912,7 +912,7 @@ - + diff --git a/public/partners.html b/public/partners.html index e48fc1c..bde2603 100644 --- a/public/partners.html +++ b/public/partners.html @@ -128,7 +128,7 @@
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.
- + diff --git a/public/plays.html b/public/plays.html index 0f18252..ea36deb 100644 --- a/public/plays.html +++ b/public/plays.html @@ -192,7 +192,7 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.
- + diff --git a/public/privacy.html b/public/privacy.html index bc154a6..59767b3 100644 --- a/public/privacy.html +++ b/public/privacy.html @@ -28,7 +28,7 @@

We use reasonable safeguards, but no system is perfectly secure. Protect your email and your wallet.

- + diff --git a/public/terms.html b/public/terms.html index 8490863..96e1a67 100644 --- a/public/terms.html +++ b/public/terms.html @@ -36,7 +36,7 @@

See also the Disclaimer and Privacy Policy.

- + diff --git a/public/tx.html b/public/tx.html index e5d5647..d3bc518 100644 --- a/public/tx.html +++ b/public/tx.html @@ -34,7 +34,7 @@

← Back to the live ledger · Read the contract review

- + diff --git a/public/wall.html b/public/wall.html index 771590b..a295834 100644 --- a/public/wall.html +++ b/public/wall.html @@ -47,7 +47,7 @@ - + diff --git a/public/wallets.html b/public/wallets.html index 06c149a..2825df5 100644 --- a/public/wallets.html +++ b/public/wallets.html @@ -121,7 +121,7 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never share your recovery phrase.
- + diff --git a/server.js b/server.js index 1cb809f..691ed3b 100644 --- a/server.js +++ b/server.js @@ -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.' } }; }