diff --git a/.jules/sentinel.md b/.jules/sentinel.md index fa5439f..1656710 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -27,3 +27,8 @@ **Vulnerability:** Allowing users to register or submit forms with disposable email addresses (e.g., mailinator.com) can lead to spam, abuse, and polluted data. **Learning:** While true email verification requires a backend or API, a simple client-side blocklist of common disposable domains is a highly effective, low-cost first line of defense. **Prevention:** Maintain a list of known disposable domains (e.g., `BLOCKED_DOMAINS`) and check the domain part of the email address during validation. + +## 2026-02-14 - Blocked Domains in Test Data +**Vulnerability:** Tests failing unexpectedly on valid inputs can lead to disabling security features or ignoring real bugs. +**Learning:** The application blocks specific domains (like `example.com`) in its validation logic. Using these domains in test data (e.g. `john@example.com`) causes tests to fail with "Invalid email", confusing debugging efforts. +**Prevention:** Always use safe, non-blocked domains (e.g. `valid-email.com` or a custom test domain) in test fixtures, and check the security configuration (`BLOCKED_DOMAINS`) when validation tests fail unexpectedly. diff --git a/src/pages/Contact.module.css b/src/pages/Contact.module.css index 80dfa15..5a5b528 100644 --- a/src/pages/Contact.module.css +++ b/src/pages/Contact.module.css @@ -139,3 +139,14 @@ .infoItem a:hover { text-decoration: underline; } + +.honeypot { + opacity: 0; + position: absolute; + top: 0; + left: 0; + height: 0; + width: 0; + z-index: -1; + overflow: hidden; +} diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx index 6332455..7f8f32a 100644 --- a/src/pages/Contact.tsx +++ b/src/pages/Contact.tsx @@ -18,6 +18,7 @@ interface FormData { email: string; subject: string; message: string; + website: string; // Honeypot } interface FormErrors { @@ -25,6 +26,7 @@ interface FormErrors { email?: string; subject?: string; message?: string; + website?: string; } export function Contact() { @@ -34,6 +36,7 @@ export function Contact() { email: "", subject: "", message: "", + website: "", }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); @@ -87,6 +90,19 @@ export function Contact() { return; } + // Honeypot check - if filled, silently succeed + if (formData.website) { + setSubmitStatus("success"); + setFormData({ + name: "", + email: "", + subject: "", + message: "", + website: "", + }); + return; + } + setIsSubmitting(true); setSubmitStatus("idle"); @@ -116,7 +132,13 @@ export function Contact() { ); setSubmitStatus("success"); - setFormData({ name: "", email: "", subject: "", message: "" }); + setFormData({ + name: "", + email: "", + subject: "", + message: "", + website: "", + }); } catch (error) { console.error("EmailJS Error:", error); setSubmitStatus("error"); @@ -208,6 +230,20 @@ export function Contact() { maxLength={MESSAGE_MAX_LENGTH} /> + {/* Honeypot field - invisible to users */} + +