Skip to content

auth.flow.no-rate-limit

A /login, /signin, /auth, or /reset-password POST handler is

OAuthLint idAUTH-FLOW-002
SeverityINFO
LLM prevalenceHIGH
CWECWE-307
OWASPAPI4:2023
Languagesjavascript, typescript
Technologiesexpress, fastify

Why this matters

A /login, /signin, /auth, or /reset-password POST handler is registered without any rate-limit middleware in scope. Without a rate limit, credential-stuffing and brute-force attacks are essentially free for the attacker.

Add express-rate-limit, @fastify/rate-limit, or a gateway-level WAF rule. Per-IP + per-account is the typical pairing.

❌ Vulnerable

ts
type Handler = (...args: unknown[]) => unknown;
declare const app: { post: (path: string, handler: Handler) => void };
declare const router: { post: (path: string, handler: Handler) => void };
declare const fastify: { post: (path: string, handler: Handler) => void };

function loginHandler(_req: unknown, _res: unknown) {
  /* ... */
}

// ruleid: auth.flow.no-rate-limit
app.post('/login', loginHandler);

// ruleid: auth.flow.no-rate-limit
app.post('/signin', loginHandler);

// ruleid: auth.flow.no-rate-limit
app.post('/reset-password', loginHandler);

// ruleid: auth.flow.no-rate-limit -- router, not app
router.post('/auth/login', loginHandler);

// ruleid: auth.flow.no-rate-limit -- fastify
fastify.post('/login', loginHandler);

// ruleid: auth.flow.no-rate-limit -- path prefix
app.post('/api/v1/login', loginHandler);

✅ Safe

ts
type Mw = (req: unknown, res: unknown, next: () => void) => void;
type Handler = (req: unknown, res: unknown) => void;
declare const app: {
  post: (path: string, ...rest: (Mw | Handler)[]) => void;
};

const rateLimit =
  (_opts: { max: number }): Mw =>
  (_req, _res, next) => {
    next();
  };

const loginLimiter = rateLimit({ max: 5 });
function loginHandler(_req: unknown, _res: unknown) {
  /* ... */
}

// ok: auth.flow.no-rate-limit -- rate-limit middleware present (3-arg form)
app.post('/login', loginLimiter, loginHandler);

// ok: auth.flow.no-rate-limit -- not a login endpoint
app.post('/products', loginHandler);

// ok: auth.flow.no-rate-limit -- /authorize is the OAuth authorize endpoint, not a credential login
app.post('/oauth/authorize', loginHandler);

Suppressing this rule (when you really must)

ts
// oauthlint-disable-next-line auth.flow.no-rate-limit -- <reason>
thisLineWouldOtherwiseTriggerTheRule();

Disable directives are line-scoped by design — wholesale silencing of a rule across the codebase is intentionally not supported, because the next reviewer needs to see exactly which lines opted out.

References

Released under the MIT License · powered by Semgrep