Skip to content

VNX-799: Authentication endpoint without attempt-rate control

Reports the registration of a credential-checking HTTP route (a path segment login, log-in, signin, sign-in, logon, token, otp, verify-otp, 2fa, mfa, authenticate, reset-password, forgot-password or password-reset, or a segment ending in login/signin/logon such as devlogin or admin-login) that accepts POST: Express/Koa-style routers (app/router/server/fastify .post/.all, including an array of paths and a Koa named route), a chained router.route('/login').post(...), a hapi or fastify route object (server.route({ method: 'POST', path: '/login' })), NestJS @Post/@All, Flask @app.route(..., methods=[...POST...]) and @bp.post, FastAPI @app.post/@router.post, and Django path()/re_path() in urls.py. It is reported only when nothing limits how often it can be tried: no limiter, brute-force or throttle middleware on the route itself (loginLimiter, brute.prevent, @limiter.limit, @Throttle, RateLimiter dependency, a django-ratelimit @ratelimit decorator on the view the urls.py entry names), no app-wide limiter in the project (app.use(limiter|rateLimit(...)), koa-ratelimit, @fastify/rate-limit, rate-limiter-flexible, NestJS ThrottlerGuard, flask-limiter or slowapi default_limits, django-axes, django-ratelimit middleware), no path-scoped app.use('<prefix>', rateLimit(...)) covering the route, and no account-lockout bookkeeping (failedLoginAttempts, lockedUntil, lockout). Not reported: GET-only routes, other paths, commented or quoted routes, client calls (axios.post, api.post), test and example files. A limiter enforced by a reverse proxy or API gateway is invisible to this rule.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-799
Severity Medium
SARIF level warning
Kind sast
Languages JavaScript / TypeScript, Python
CWE CWE-799
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags improper-control-of-interaction-frequency cwe-799 brute-force rate-limiting
Source internal/sast/rules/vnx-799.rego

Overview

VNX-799 maps to CWE-799: Improper Control of Interaction Frequency. It reports login, token, one-time-code and password-reset routes that accept credentials while nothing in the project caps how often they can be tried.

Why This Matters

An endpoint that checks a password or a six-digit code answers as fast as the server can run. Without a limiter or an account lockout, credential stuffing, password spraying and OTP brute force cost the attacker nothing but requests.

The rule judges the project, not the line: a global app.use(limiter), a NestJS ThrottlerGuard, flask-limiter default_limits, django-axes or lockout bookkeeping anywhere in the project silences it.

What Gets Flagged

// FLAGGED: login accepts POST, no limiter on the route or the app
app.post('/login', async (req, res) => { /* ... */ });

// FLAGGED: the limiter covers only the reset path
app.use('/rest/user/reset-password', rateLimit({ windowMs: 300000, max: 100 }));
app.post('/rest/user/login', login());
# FLAGGED: Flask login accepting POST
@app.route("/login", methods=["GET", "POST"])
def login(): ...

# FLAGGED: Django urls.py with no axes / ratelimit
path("accounts/login/", views.login_view, name="login"),

Not flagged: router.post('/login', loginLimiter, ctrl.login), @limiter.limit("5 per minute") under the route, a GET-only @app.route("/login"), axios.post('/login') client code.

// FLAGGED: chained route and hapi route object
router.route('/login').post(async (req, res) => { /* ... */ });
server.route({ method: 'POST', path: '/auth/login', handler });

Also not flagged: a Django path("login/", views.login_view) whose view carries @ratelimit(key="ip", rate="5/m"), and a Koa app with app.use(ratelimit({...})).

Remediation

  1. Put a limiter on every credential-checking route (express-rate-limit, @nestjs/throttler, flask-limiter, slowapi, django-ratelimit), keyed by account as well as IP.
  2. Add progressive delay or temporary lockout after repeated failures (django-axes, a failed-attempts counter).
  3. If a gateway enforces the limit, record that with a vulnetix-ignore: VNX-799 comment on the route.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-799
// nosec vnx-799

Or record an ignore rule: vulnetix ignore add --rule vnx-799 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-799.rego, cli/internal/sast/docs/vnx-799.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.