Skip to content

Commit 4be10af

Browse files
committed
feat: add honeypot + time-based anti-bot checks to email flow
- Hidden honeypot field: bots auto-fill, humans never see it - Time check: rejects submissions < 3s after modal opens - Both enforced client-side and server-side (Apps Script) - Rate limiting still active as additional protection
1 parent 88688b4 commit 4be10af

5 files changed

Lines changed: 53 additions & 4 deletions

File tree

changelogs/CHANGELOG-antibot.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Anti-Bot Protection (Honeypot + Time Check)
2+
3+
- Added hidden honeypot input field (bots auto-fill, humans never see it)
4+
- Added time-based check (rejects submissions < 3 seconds after modal opens)
5+
- Both checks enforced client-side and server-side
6+
- Rate limiting (100/day global, 7/day per-recipient) still active
7+
8+
## Files Changed (5 total)
9+
10+
| File | Change |
11+
|------|--------|
12+
| `js/modal-templates.js` | Added honeypot input field |
13+
| `css/modals.css` | Honeypot CSS (hidden from view) |
14+
| `js/cloud-share.js` | Time tracking, honeypot + time validation, send hp/ts |
15+
| `scripts/email-apps-script.js` | Server-side honeypot + time checks |

css/modals.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,17 @@
404404
margin-top: 0;
405405
}
406406

407+
/* Honeypot — invisible to humans, bots auto-fill */
408+
.share-email-hp {
409+
position: absolute;
410+
left: -9999px;
411+
opacity: 0;
412+
height: 0;
413+
width: 0;
414+
overflow: hidden;
415+
pointer-events: none;
416+
}
417+
407418
.share-email-status {
408419
font-size: 13px;
409420
margin-top: 8px;

js/cloud-share.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -851,13 +851,19 @@
851851
var emailSubjectInput = document.getElementById('share-email-subject');
852852
var emailSendBtn = document.getElementById('share-email-send');
853853
var emailStatus = document.getElementById('share-email-status');
854+
var emailHoneypot = document.getElementById('share-email-website');
855+
var emailModalOpenTime = 0;
854856

855857
// Restore last-used email
856858
var savedEmail = localStorage.getItem(M.KEYS.EMAIL_SELF);
857859
if (savedEmail && emailInput) emailInput.value = savedEmail;
858860

859-
860-
861+
// Track when the share result modal opens (for time-based bot detection)
862+
var _origShowShareResult = showShareResult;
863+
showShareResult = function (url, isSecure) {
864+
_origShowShareResult(url, isSecure);
865+
emailModalOpenTime = Date.now();
866+
};
861867
if (emailSendBtn) emailSendBtn.addEventListener('click', async function () {
862868
var email = emailInput.value.trim();
863869
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
@@ -867,8 +873,12 @@
867873
return;
868874
}
869875

876+
// Anti-bot: honeypot check (hidden field bots auto-fill)
877+
if (emailHoneypot && emailHoneypot.value) return;
870878

871-
879+
// Anti-bot: time check (reject submissions < 3 seconds after modal opened)
880+
var elapsed = Date.now() - emailModalOpenTime;
881+
if (elapsed < 3000) return;
872882
// Persist email for next time
873883
try { localStorage.setItem(M.KEYS.EMAIL_SELF, email); } catch (e) { /* ignore */ }
874884

@@ -903,7 +913,9 @@
903913
subject: subject,
904914
title: heading,
905915
content: content,
906-
shareLink: shareUrl
916+
shareLink: shareUrl,
917+
hp: emailHoneypot ? emailHoneypot.value : '',
918+
ts: elapsed
907919
})
908920
});
909921
// If fetch didn't throw, the request reached Google's servers

js/modal-templates.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
<div class="share-email-header"><i class="bi bi-envelope me-1"></i> Email to Self</div>
139139
<input type="email" id="share-email-input" class="share-email-field" placeholder="your@email.com" autocomplete="email" />
140140
<input type="text" id="share-email-subject" class="share-email-field" placeholder="Subject (optional)" />
141+
<input type="text" id="share-email-website" class="share-email-hp" tabindex="-1" autocomplete="off" aria-hidden="true" />
141142
<div class="share-email-row">
142143
<button class="share-btn-primary" id="share-email-send" title="Send link & file to your email">
143144
<i class="bi bi-send me-1"></i> Send

scripts/email-apps-script.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ function doPost(e) {
2121
try {
2222
var data = JSON.parse(e.postData.contents);
2323

24+
// ── 1. Anti-bot checks ──
25+
// Honeypot: hidden field that only bots fill
26+
if (data.hp) {
27+
return jsonResponse({ success: false, error: 'Invalid request' });
28+
}
29+
// Time check: reject submissions faster than 3 seconds
30+
if (data.ts && data.ts < 3000) {
31+
return jsonResponse({ success: false, error: 'Invalid request' });
32+
}
33+
2434
// ── 2. Rate limiting ──
2535
var props = PropertiesService.getScriptProperties();
2636
var today = new Date().toDateString();

0 commit comments

Comments
 (0)