CodamAIDocs
Topicdone

Protecting the public endpoints

Why self-registration always returns the same answer, why invalid links look the same, and how throttling works.

Variants
always 202link error 404, second click 202throttling per addressthrottling per client429 without Retry-After

What this is about

The endpoints under /cias/registration/** can be reached without signing in. Anyone on the internet can call them, including someone who wants to find out which addresses have an account with you, or who wants to create tenants in bulk. That is why these endpoints reveal as little as possible and are throttled.

What an attacker sees

Same response, different email
new address
  • Response 202 accepted
  • Email: “Please verify your address”
known address
  • Response 202 accepted
  • Email: invitation to join, or “You already have an account”
throttled
  • Response 429
  • no email

An attacker only sees the responses, not the emails. To them, new and known addresses look the same.

The responses of the public endpoints

EndpointResponseWhen
POST /self202 acceptedaccepted, no matter whether the address is known
400address invalid or request broken
422required field missing, unknown field, or a hook rejects
429throttled
POST /verify, POST /invitations/accept202 acceptedredeemed, or already redeemed earlier
404link unknown, or expired while the registration is still waiting
GET /invitations/{token}/form200 or 404open fields, or link unknown
GET /flows/{flow}/form200field description of a flow

All errors have the same format: { "error": "cias.registration.…", "message": "…" }.

Why does a second click return 202 and not an error? Many email programs open links in advance to scan them for malware. This redeems the token before the person clicks. An error on the real click would confuse them. See Verify the email.

Throttling

Self-registration counts attempts in a sliding time window. When the limit is reached, CIAS responds with 429 before it checks or creates anything.

Two counters

When: always

At most rate-limit attempts per address in rate-limit-window. Default and cias-runtime: 10 in 10 minutes. The hub sets 3 per hour.

Result: Protects a person's mailbox from being flooded with emails.

When: The installation sets client-ip-source to REMOTE_ADDR or HEADER.

A second counter per sender IP. The IP is never stored, but hashed with a salt. With HEADER, CIAS takes the first entry of the configured header, for example X-Forwarded-For. The hub sets HEADER, with 20 attempts in 10 minutes. In cias-runtime this counter is off.

Result: Protects against someone trying many different addresses from one machine.

Request
POST /cias/registration/self   (11th attempt in 10 minutes)
Response
429
{ "error": "cias.registration.rate-limited", "message": "too many attempts" }

The response deliberately does not say when it works again. There is no Retry-After header. Whoever waits gets through again; whoever tries automatically gets no pace to follow.

The administrative endpoints (creation by administrators, invitations) are not throttled. There the caller is signed in and known.

Next

Sources in the code and the knowledge base
  • CIAS/cias-registration – RegistrationController, RegistrationExceptionHandler, RegistrationService (throttle), ClientKeyResolver, RateLimiterPort
  • CIAS/cias-spring-boot-starter – InMemoryRateLimiter, CiasProperties (rate-limit, rate-limits, client-ip-*)
  • CIAS/cias-runtime – application.yml; hub-backend – application.yaml
  • CIAS/cias-registration/docs/adr – ADR-012, ADR-014
Search