Skip to content

VNX-640: Weak Password Recovery Mechanism for Forgotten Password

Detects weak forgotten-password flows: reset tokens, OTPs and temporary passwords from a non-cryptographic source (random, Math.random, java.util.Random, time), reset links built from the Host header, and resets decided by a security question. JavaScript/TypeScript, Python and Java.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-640
Severity Medium
SARIF level warning
Kind sast
Languages JavaScript / TypeScript, Python, Java
CWE CWE-640
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:M/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags weak-password-recovery password-reset host-header-poisoning cwe-640
Source internal/sast/rules/vnx-640.rego

Overview

VNX-640 maps to CWE-640: Weak Password Recovery Mechanism for Forgotten Password. A recovery flow is a second way to log in, and it is usually weaker than the first. Three concrete weaknesses are reported:

  1. A predictable recovery secret. A reset/recovery/forgot token, code, key, hash, link or pin, an OTP or one-time code, a verification code, or a temporary or new password generated by a non-cryptographic source: Python random.* (or randint/choice imported from random), time.time(), uuid1(), a hash of the email, username or time; JavaScript Math.random(), Date.now(), new Date().getTime(); Java new Random(), a java.util.Random field’s next*(), ThreadLocalRandom, RandomStringUtils.random*, System.currentTimeMillis(). These values can be predicted or brute-forced.
  2. A reset link built from the Host header (password-reset poisoning): Flask request.host/host_url/url_root, Django META['HTTP_HOST'], Express req.headers.host/req.get('host')/req.hostname, the X-Forwarded-Host header in Python and Express, Servlet getHeader("Host")/getServerName(), used in a link or passed with the reset token near reset/forgot/recover code. An attacker who sets the header to their own domain receives the victim’s token when the victim clicks.
  3. A reset decided by a security question: in a file that names a security or secret question/answer, a comparison of the stored answer with an answer from the request. Answers to “first pet” questions are guessable and often public.

A timestamp stored beside a token (resetPasswordExpires = Date.now() + 3600000, reset_token_created = time.time()) is not a secret and is not reported.

What Gets Flagged

reset_token = str(random.randint(100000, 999999))      # FLAGGED
link = f"http://{request.host}/reset/{token}"           # FLAGGED
if user.security_answer.lower() == request.form["answer"].lower():   # FLAGGED
const resetToken = Math.random().toString(36).slice(2);   // FLAGGED
String resetToken = RandomStringUtils.randomAlphanumeric(32);   // FLAGGED
String host = request.getHeader("Host");                        // FLAGGED (used in the reset mail)

What Is Not Flagged

  • Tokens from secrets, random.SystemRandom, crypto.randomBytes/randomInt, SecureRandom or UUID.randomUUID().
  • Random values for non-secret purposes (jitter, retry delays) and timestamps such as createdAt.
  • Host reads unrelated to a reset, reset links built from configuration, and Django get_host() (validated against ALLOWED_HOSTS).
  • Test code, comments and string literals.

Remediation

Generate a single-use, expiring token from a CSPRNG, build the link from a configured base URL, and send it to the registered address instead of asking a security question:

token = secrets.token_urlsafe(32)
link = f"{settings.PUBLIC_BASE_URL}/reset/{token}"

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-640
// nosec vnx-640

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

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