Safeguard
Application Security•

CWE-799 Improper Control of Interaction Frequency: Rate Limits

No limit on how often someone can call you means password guessing, coupon abuse and easy denial of service. See how CWE-799 appears and how to add limits that hold.

Safeguard Security Team
4 min read

CWE-799 applies when a product does not properly limit the number or frequency of interactions with an actor, such as the number of incoming requests. It is a class level entry, and it touches nearly every system with a login form, an API or a one-use feature.

How it happens

MITRE says the actor can be a human or an automated process such as a virus or bot. Without limits, they can act far more often than you planned. The record gives two examples: an authentication routine that does not cap password guesses, and a poll that expects each human to vote at most once a day.

Common forms:

  • Login, reset and verification endpoints with no attempt limit.
  • Coupon, vote, referral or gift code actions enforced only in the browser.
  • Expensive API calls with no per-client quota.
  • Public forms that send email or text messages on every submit.
@app.post("/redeem")
def redeem():
    code = request.json["coupon"]
    apply_discount(code)         # no check on how many times, or how fast
    return "ok"

The MITRE record has two observed cases. CVE-2024-50653 is an e-commerce chain: the product had a front-end restriction for coupon use, but the server did not limit the number of requests for the same coupon. CVE-2002-1876 is a mail server that let attackers block other users from reaching their mail by sending a large number of rapid requests.

The first one shows a general rule. A limit enforced only in the client is not a limit, because the attacker does not use your client.

What an attacker gains

The record lists denial of service through resource consumption, bypass of protection mechanisms, and access control effects. In practice:

  • Unlimited password guessing against accounts.
  • Abuse of business logic, such as reusing a coupon or inflating a vote.
  • Exhausting CPU, memory, mail quotas or paid third-party calls.

How to find it

The record lists no detection methods, so test and review:

  • Send the same request many times from a script against a test copy and see whether anything stops it.
  • Check every sensitive action, such as login, reset, payment, redeem and invite, for a server-side limit.
  • Ask of each one: what happens if this runs a thousand times in a minute?
  • Review cost-bearing calls, like SMS, email and paid APIs, for caps.
  • Run an API or web scan to look for endpoints that accept unlimited repeated calls.

How to fix it

  1. Enforce limits on the server. Never rely on the browser or mobile app.
  2. Limit by several keys: account, IP address, API key and device where you can. A single key is easy to rotate around.
  3. Use sensible mechanisms: token bucket or sliding window counters for rate, and a stored counter for one-time use.
  4. For login, add escalating delays or temporary lockout, and alert on bursts of failures.
  5. Make one-time actions atomic. Mark a coupon used in the same transaction as applying it, so parallel requests cannot both win.
  6. Put quotas on costly operations and return a clear 429 response with a retry hint.
from flask_limiter import Limiter
limiter = Limiter(key_func=lambda: request.headers.get("X-Api-Key", request.remote_addr))

@app.post("/redeem")
@limiter.limit("5 per minute")
def redeem():
    with db.transaction():
        if not mark_coupon_used(request.json["coupon"]):   # single use, atomic
            abort(409)
        apply_discount(request.json["coupon"])
    return "ok"

Pair rate limits with logging of rejected requests so abuse is visible. See CWE-778 for what to record.

Try it yourself

API endpoints without rate controls are easy to spot in a scan. Step through this demo of an API security scan.

Click through it at your own pace, or open it full screen.

Quick check

Is a disabled button after one click a rate limit?

No. It runs in the client, and an attacker can send requests directly to the server.

Why make one-time actions atomic?

Parallel requests can all pass a check before any of them records use. An atomic update lets only one succeed.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.