Anti-Abuse Protections

Anti-Abuse Protections

Anti-Abuse Protections

Formic includes multiple layers of protection against abuse, automated submissions, and denial-of-service attacks.

Rate Limiting

Rate limiting prevents excessive submissions that could overwhelm the system or exceed email provider quotas.

How It Works

Rate limits are enforced using Redis with atomic INCR/EXPIRE commands in a sliding window. Each form has its own counter keyed as rate_limit:form:{slug}.

Limits

Limit TypeDefaultPurpose
Per-form60 requests/minutePrevents abuse of a single form
Global80 requests/minuteProtects email provider quotas

Both limits are configurable via environment variables (DEFAULT_FORM_RATE_LIMIT and GLOBAL_RATE_LIMIT_PER_MINUTE).

Fail-Closed Behavior

If Redis is unreachable, Formic returns 500 Internal Server Error with the message "Rate limiting service unavailable". This fail-closed approach ensures rate limiting is never accidentally bypassed due to infrastructure issues.

Rate Limit Response

When a rate limit is exceeded, the API responds with:

HTTP 429 Too Many Requests
Retry-After: 60

{
  "error": "Rate Limit Exceeded",
  "message": "Too many requests. Please try again later."
}

The Retry-After header tells clients when to retry. The Formic client library respects this header automatically when retry is enabled.

Cloudflare Turnstile

Turnstile is Cloudflare’s privacy-focused anti-bot alternative to CAPTCHA. Formic requires a valid Turnstile token for every form submission.

How It Works

  1. Your frontend renders the Turnstile widget and obtains a token
  2. The token is sent to Formic via the CF-Turnstile-Token header
  3. Formic validates the token with Cloudflare’s API
  4. The submission is accepted or rejected based on the result

Client-Side Integration

<!-- HTML form with Turnstile widget -->
<form id="contact-form">
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
  <button type="submit">Submit</button>
</form>

<script>
// Using the Formic client with Turnstile
const turnstileToken = turnstile.getResponse();
const result = await client.submit(formData, turnstileToken);
</script>

Server-Side Proxy Integration

When using the server-side proxy pattern, the Turnstile token is forwarded automatically from the incoming request headers:

// The proxy adapter forwards the CF-Turnstile-Token header automatically
export const POST = handleFormicRequest({
  apiKey: import.meta.env.FORMIC_API_KEY,
  slug: 'contact-form'
});

Turnstile Error Responses

StatusMeaning
400 Bad RequestTurnstile token is missing or invalid
502 Bad GatewayTurnstile API is unreachable (2-second timeout)

Origin Validation

Formic supports per-form origin allow-lists to restrict which websites can submit to a form.

How It Works

Configure allowed_origins on your form configuration:

ConfigurationBehavior
NULL or empty []All origins allowed (backward compatible)
One or more originsOnly exact matches allowed

Origin comparison is case-sensitive and exact (no wildcard or pattern matching).

If the Origin header is absent (common for non-browser requests or same-origin submissions), the request is allowed.

Origin Validation Response

HTTP 403 Forbidden

{
  "error": "Forbidden",
  "message": "Origin not allowed"
}

Payload Size Limits

Formic enforces a maximum request body size to prevent abuse via oversized payloads.

SettingDefaultConfigurable
Maximum body size2 MB (2,097,152 bytes)Via MAX_PAYLOAD_SIZE_BYTES env var

Requests exceeding the limit receive:

HTTP 413 Payload Too Large

{
  "error": "Payload Too Large",
  "message": "Request body exceeds maximum size of 2097152 bytes"
}

Client-Side Validation

The Formic client library can optionally validate payload size before sending, avoiding wasted bandwidth:

const client = new FormicClient({
  apiKey: process.env.FORMIC_API_KEY,
  slug: 'contact-form',
  validatePayloadSize: true  // Check size before sending
});