Skip to content

VNX-303: Incorrect Implementation of Authentication Algorithm

Detects password checks that cannot give the right answer: bcrypt.compare()/argon2.verify() used without await (a Promise is always truthy, so every password passes), and a password re-hashed with a fresh salt and compared with the stored hash (never equal).

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-303
Severity Medium
SARIF level warning
Kind sast
Languages Java, JavaScript / TypeScript, Python
CWE CWE-303
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 auth password-verification bcrypt cwe-303
Source internal/sast/rules/vnx-303.rego

Overview

VNX-303 maps to CWE-303: Incorrect Implementation of Authentication Algorithm. The right primitive is in use, but it is called in a way that defeats it.

  1. An asynchronous verifier used as a boolean (JavaScript/TypeScript). bcrypt.compare(), bcryptjs.compare() and argon2.verify() return a Promise. Without await, .then() or a callback, if (bcrypt.compare(pw, hash)) is always true and every password is accepted; if (!argon2.verify(...)) never rejects. Reported inside an if, &&, ||, ! or ternary, or when assigned to a name that a condition within five lines tests.
  2. Re-hashing with a fresh salt, then comparing (Python, Java, JavaScript/TypeScript). bcrypt.hashpw(pw, bcrypt.gensalt()) == stored, generate_password_hash(pw) == stored, a passlib/argon2 hasher’s .hash(pw) ==, passwordEncoder.encode(pw).equals(stored), BCrypt.hashpw(pw, BCrypt.gensalt()).equals(...), bcrypt.hash(pw, 10) === stored, inline or through a name assigned from the hash. A salted hash never repeats, so the check always fails, and is typically “fixed” by weakening it.

What Gets Flagged

if (user && bcrypt.compare(req.body.password, user.passwordHash)) {   // FLAGGED: always truthy
  req.session.userId = user.id;
}
if bcrypt.hashpw(password.encode(), bcrypt.gensalt()) == user.password_hash:   # FLAGGED
    return user
if (passwordEncoder.encode(rawPassword).equals(user.getPassword())) {   // FLAGGED

What Is Not Flagged

  • await bcrypt.compare(...), the callback and .then() forms, and a returned Promise.
  • compareSync, bcrypt.checkpw, check_password_hash, passwordEncoder.matches.
  • Re-hashing with the stored hash as the salt (bcrypt.hashpw(pw, stored) == stored) and hashing at registration.
  • Comments and string literals.

Remediation

Use the library’s verifier and await it:

if (user && await bcrypt.compare(req.body.password, user.passwordHash)) { ... }
if (passwordEncoder.matches(rawPassword, user.getPassword())) { ... }
if bcrypt.checkpw(password.encode(), user.password_hash): ...

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

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

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