Skip to content

Add per-IP rate limiting on anonymous public endpoints (contact, signup, password reset) #4625

Closed

Description

@ymarcon

Anonymous public endpoints have no rate limiting, so a single source can hit them as often as it likes. During the 2026-09-21/22 incident (#4624), an automated scanner sent about 880 requests to POST /ws/users/_contact, and nothing throttled it.

Now that Agate verifies the reCAPTCHA token (obiba/agate#730), invalid tokens no longer produce emails. But every junk request still costs a Mica → Agate → Google siteverify round trip, and the endpoint can still be flooded.

Proposal

Add a lightweight per-IP rate limit on anonymous, abuse-prone endpoints:

  • POST /ws/users/_contact (primary target)
  • also worth considering: POST /ws/users (signup, relayed to Agate _join), POST /ws/users/_forgot_password, and other unauthenticated POSTs that trigger emails or Agate calls

Constraints

  • The contact form must stay fully anonymous. Limits must key off the client IP (or a short-lived anonymous token), never off user identity, session, or account.
  • Behind a reverse proxy, the client IP comes from X-Forwarded-For/Forwarded. Only trust these headers from configured trusted proxies. Otherwise all traffic shares one bucket, or an attacker can rotate a spoofed header to get around the limit.
  • Keep it in memory, with no new infrastructure: a Guava cache or a Bucket4j token bucket, for example. Per-node limits are acceptable.

Suggested behaviour

  • Configurable limits in application.yml, e.g. security.rateLimit.contact: 5 requests / 10 minutes per IP, with sensible defaults, enabled by default.
  • Over the limit → 429 Too Many Requests with a Retry-After header, and log it at warn level with the IP.
  • Reject before any Agate or reCAPTCHA call is made.
  • The portal contact and signup forms show a readable message on 429.

Verification

  • Rapid repeated submissions from one IP get 429 after the configured threshold, and no email or Agate call follows.
  • A normal anonymous visitor submitting the form once or twice is not affected.
  • Requests from different client IPs behind a trusted proxy are counted separately.

Related: #4624

Activity

  1. kazoompa commented on Oct 18, 2026

    @kazoompa
    Member

    Is this going to be in Mica 7.x?

  2. ymarcon commented on Oct 18, 2026

    @ymarcon
    MemberAuthor

    No

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions