Abuse Prevention
Prevent form abuse with origin allowlisting and rate limiting.
Formlander provides multiple layers of protection against form abuse, spam floods, and cross-site attacks. These protections work automatically and require minimal configuration.
Understanding Form Tokens
Section titled “Understanding Form Tokens”Each form in Formlander has a unique public token that’s embedded in your HTML:
<form action="https://your-formlander.com/forms/contact/submit?token=abc123"> <!-- form fields --></form>The Security Problem
Section titled “The Security Problem”Tokens alone are not enough. Without additional protection, attackers can copy your form and token to their own website:
Attack scenario:
- Attacker inspects your HTML on
example.com - Copies the entire form with your token to
attacker.com - Can now spam submissions to your form from their malicious site
- Your inbox gets flooded with spam
The Solution: Origin Allowlisting
Section titled “The Solution: Origin Allowlisting”Formlander protects against token copying by checking the Origin or Referer header of each submission request.
Origin Allowlisting
Section titled “Origin Allowlisting”How It Works
Section titled “How It Works”-
Configure allowed domains when creating/editing a form:
example.com, www.example.com, *.example.com -
Formlander validates every submission:
- ✅ Request from
example.com→ Allowed - ✅ Request from
www.example.com→ Allowed - ✅ Request from
app.example.com→ Allowed (wildcard match) - ❌ Request from
attacker.com→ Rejected (403 Forbidden)
- ✅ Request from
-
Response on blocked origin:
{"ok": false,"error": "origin not allowed"}
Configuration Options
Section titled “Configuration Options”Allow all origins (default)
Section titled “Allow all origins (default)”Leave the “Allowed Origins” field empty in the form settings.
⚠️ Warning: This makes your form vulnerable to token copying attacks. Only use for testing.
Allow a specific domain
Section titled “Allow a specific domain”example.comOnly submissions from example.com will be accepted.
Allow multiple domains
Section titled “Allow multiple domains”example.com, example.org, myapp.comSeparate multiple domains with commas.
Allow domain + all subdomains (wildcard)
Section titled “Allow domain + all subdomains (wildcard)”*.example.comThis matches:
app.example.comstaging.example.comwww.example.com- Any other subdomain
Combine specific domains + wildcards
Section titled “Combine specific domains + wildcards”example.com, *.example.com, www.example.orgMix and match for complex setups.
Why Tokens Are Still Public
Section titled “Why Tokens Are Still Public”Even though tokens are visible in HTML, they’re designed for write-only access:
- ✅ Write-only: Token only allows submitting to that specific form
- ✅ Cannot read data: Attackers can’t view existing submissions
- ✅ Form-specific: Each form has its own token, limiting blast radius
- ✅ Can be rotated: Regenerate tokens if compromised
With origin checking enabled:
- ✅ Token + correct origin required
- ✅ Prevents cross-site form abuse
- ✅ Same security model as Stripe, Google Maps API, and other public API keys
Configuring Origin Allowlisting
Section titled “Configuring Origin Allowlisting”- Navigate to Forms in the admin dashboard
- Edit or create a form
- Scroll to “Allowed Origins” field
- Enter comma-separated domains:
example.com, *.example.com
- Save the form
Now only requests from example.com or its subdomains will be accepted.
Rate Limiting
Section titled “Rate Limiting”Formlander includes per-IP rate limiting to prevent spam floods and denial-of-service attacks.
How It Works
Section titled “How It Works”- Tracks submission rate per IP address per form
- Hardcoded to: 30 requests per 60 seconds
- Returns
429 Too Many Requestswhen limit exceeded
Example: Spam Flood Protection
Section titled “Example: Spam Flood Protection”If someone (or a bot) tries to submit more than 30 times in 60 seconds from the same IP:
{ "ok": false, "error": "rate limit exceeded"}Rate Limit Behavior
Section titled “Rate Limit Behavior”- Per IP: Each IP address has its own counter
- Per Form: Limits apply to individual forms, not globally
- Sliding Window: 60-second window slides with each request
- Automatic Reset: Counter resets after 60 seconds of inactivity
Privacy-Preserving IP Hashing
Section titled “Privacy-Preserving IP Hashing”Formlander never stores raw IP addresses:
- IP address is hashed automatically using internal salt
- Only the hash is used for rate limiting
- Original IP cannot be recovered from the hash
This ensures privacy compliance while preventing abuse.
Best Practices
Section titled “Best Practices”For contact forms:
- 30 requests/minute prevents spam while allowing legitimate use
- Catches automated spam floods
- Legitimate users rarely hit this limit
For high-traffic forms:
- Consider using authenticated endpoints if you need higher limits
- Monitor submission patterns in the dashboard
- Combine with captcha for additional protection
Monitoring:
- Check
storage/logs/for rate limit violations - Identify spam patterns and attack sources
- Adjust form protection if needed
Rate Limit Logs
Section titled “Rate Limit Logs”Rate-limited submissions are logged in storage/logs/:
{ "level": "warn", "ts": "2025-11-07T14:30:00Z", "msg": "submission blocked: rate limit exceeded", "form_slug": "contact", "ip_hash": "abc123...", "reason": "rate_limit"}Recommended Security Stack
Section titled “Recommended Security Stack”For maximum protection, combine multiple layers:
✅ Minimal Protection (Default)
Section titled “✅ Minimal Protection (Default)”- Origin Allowlisting - Prevents token copying
- Rate Limiting - Prevents spam floods
✅ Enhanced Protection (Recommended)
Section titled “✅ Enhanced Protection (Recommended)”- Origin Allowlisting - Prevents token copying
- Rate Limiting - Prevents spam floods
- Captcha (Turnstile) - Blocks sophisticated bots
See Bot Protection for captcha setup.
Example: Secure Contact Form
Section titled “Example: Secure Contact Form”Form configuration:
Name: Contact FormSlug: contactAllowed Origins: example.com, www.example.comCaptcha Profile: Production CaptchaHTML form:
<form action="https://your-domain.com/forms/contact/submit?token=abc123" method="post"> <label> Name <input type="text" name="name" required> </label>
<label> Email <input type="email" name="email" required> </label>
<label> Message <textarea name="message" required></textarea> </label>
<!-- Cloudflare Turnstile (if enabled) --> <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Send Message</button></form>
<!-- Load Turnstile script (if using captcha) --><script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>Protection layers:
- ✅ Only accepts submissions from
example.comorwww.example.com - ✅ Limits to 30 requests per minute per IP
- ✅ Validates Turnstile captcha token server-side
Monitoring Blocked Submissions
Section titled “Monitoring Blocked Submissions”All blocked submissions are logged with detailed information:
{ "level": "warn", "ts": "2025-11-07T14:30:00Z", "msg": "submission blocked", "form_slug": "contact", "ip_hash": "abc123...", "reason": "origin_not_allowed", "origin": "attacker.com"}Reasons for blocked submissions:
origin_not_allowed- Origin allowlist violationrate_limit- Too many requests from same IPcaptcha_failed- Failed Turnstile validationinvalid_token- Wrong or missing form token
Log Analysis
Section titled “Log Analysis”Monitor storage/logs/ to:
- Identify spam patterns
- Detect potential attacks
- Fine-tune security settings
- Investigate false positives
Additional Security Tips
Section titled “Additional Security Tips”- Always use HTTPS - Protect form data in transit
- Configure origin allowlisting - Never leave it empty in production
- Monitor logs - Watch for patterns in blocked submissions
- Combine protections - Use origin + rate limiting + captcha together
- Test thoroughly - Verify protection doesn’t block legitimate users
Troubleshooting
Section titled “Troubleshooting””Origin not allowed” errors
Section titled “”Origin not allowed” errors”- ✅ Verify the domain in “Allowed Origins” matches your website
- ✅ Check for
wwwvs non-www mismatches - ✅ Use wildcards (
*.example.com) to allow all subdomains - ✅ Ensure HTTPS vs HTTP matches in the origin header
Legitimate users getting rate limited
Section titled “Legitimate users getting rate limited”- ✅ Check if users are refreshing/resubmitting rapidly
- ✅ Verify no browser extensions are auto-submitting
- ✅ Confirm no bugs causing form to submit multiple times
- ✅ Check logs for the IP hash and submission pattern
Testing origin allowlisting
Section titled “Testing origin allowlisting”Use browser DevTools or curl to verify:
# Should succeed (correct origin)curl -X POST \ -H "Origin: https://example.com" \ -H "Content-Type: application/x-www-form-urlencoded" \ "https://your-domain.com/forms/contact/submit?token=abc123"
# Should fail with 403 (wrong origin)curl -X POST \ -H "Origin: https://attacker.com" \ -H "Content-Type: application/x-www-form-urlencoded" \ "https://your-domain.com/forms/contact/submit?token=abc123"Additional Resources
Section titled “Additional Resources”- Bot Protection - Cloudflare Turnstile setup
- Integration Guide - Complete form integration examples
- Formlander GitHub - Source code and issues