Skip to content

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.

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>

Tokens alone are not enough. Without additional protection, attackers can copy your form and token to their own website:

Attack scenario:

  1. Attacker inspects your HTML on example.com
  2. Copies the entire form with your token to attacker.com
  3. Can now spam submissions to your form from their malicious site
  4. Your inbox gets flooded with spam

Formlander protects against token copying by checking the Origin or Referer header of each submission request.

  1. Configure allowed domains when creating/editing a form:

    example.com, www.example.com, *.example.com
  2. 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)
  3. Response on blocked origin:

    {
    "ok": false,
    "error": "origin not allowed"
    }

Leave the “Allowed Origins” field empty in the form settings.

⚠️ Warning: This makes your form vulnerable to token copying attacks. Only use for testing.

example.com

Only submissions from example.com will be accepted.

example.com, example.org, myapp.com

Separate multiple domains with commas.

*.example.com

This matches:

  • app.example.com
  • staging.example.com
  • www.example.com
  • Any other subdomain
example.com, *.example.com, www.example.org

Mix and match for complex setups.

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
  1. Navigate to Forms in the admin dashboard
  2. Edit or create a form
  3. Scroll to “Allowed Origins” field
  4. Enter comma-separated domains:
    example.com, *.example.com
  5. Save the form

Now only requests from example.com or its subdomains will be accepted.

Formlander includes per-IP rate limiting to prevent spam floods and denial-of-service attacks.

  • Tracks submission rate per IP address per form
  • Hardcoded to: 30 requests per 60 seconds
  • Returns 429 Too Many Requests when limit exceeded

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"
}
  • 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

Formlander never stores raw IP addresses:

  1. IP address is hashed automatically using internal salt
  2. Only the hash is used for rate limiting
  3. Original IP cannot be recovered from the hash

This ensures privacy compliance while preventing abuse.

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-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"
}

For maximum protection, combine multiple layers:

  • Origin Allowlisting - Prevents token copying
  • Rate Limiting - Prevents spam floods
  • Origin Allowlisting - Prevents token copying
  • Rate Limiting - Prevents spam floods
  • Captcha (Turnstile) - Blocks sophisticated bots

See Bot Protection for captcha setup.

Form configuration:

Name: Contact Form
Slug: contact
Allowed Origins: example.com, www.example.com
Captcha Profile: Production Captcha

HTML 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:

  1. ✅ Only accepts submissions from example.com or www.example.com
  2. ✅ Limits to 30 requests per minute per IP
  3. ✅ Validates Turnstile captcha token server-side

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 violation
  • rate_limit - Too many requests from same IP
  • captcha_failed - Failed Turnstile validation
  • invalid_token - Wrong or missing form token

Monitor storage/logs/ to:

  • Identify spam patterns
  • Detect potential attacks
  • Fine-tune security settings
  • Investigate false positives
  1. Always use HTTPS - Protect form data in transit
  2. Configure origin allowlisting - Never leave it empty in production
  3. Monitor logs - Watch for patterns in blocked submissions
  4. Combine protections - Use origin + rate limiting + captcha together
  5. Test thoroughly - Verify protection doesn’t block legitimate users
  • ✅ Verify the domain in “Allowed Origins” matches your website
  • ✅ Check for www vs non-www mismatches
  • ✅ Use wildcards (*.example.com) to allow all subdomains
  • ✅ Ensure HTTPS vs HTTP matches in the origin header
  • ✅ 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

Use browser DevTools or curl to verify:

Terminal window
# Should succeed (correct origin)
curl -X POST \
-H "Origin: https://example.com" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=Test&[email protected]" \
"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" \
-d "name=Test&[email protected]" \
"https://your-domain.com/forms/contact/submit?token=abc123"