Skip to content

VNX-227: Improper Fulfillment of API Contract: security verification result ignored

A password check, MAC or constant-time comparison, signature verification or OpenSSL verify/RNG call is made as a bare statement, so its verdict is discarded; also PHP openssl_verify() used directly as an if condition.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-227
Severity Medium
SARIF level warning
Kind sast
Languages C, C++, Go, Java, JavaScript / TypeScript, PHP, Python, Ruby
CWE CWE-227
CAPEC CAPEC-97
ATT&CK T1557
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 api-contract unchecked-return authentication signature-verification cwe-227
Source internal/sast/rules/vnx-227.rego

Overview

Security APIs such as hmac.compare_digest(), password_verify() or SSL_get_verify_result() do not stop the program when a check fails: they return the verdict, and the contract is that the caller acts on it. VNX-227 reports a call to one of these APIs written as a statement on its own, whose verdict is therefore never read. This maps to CWE-227: Improper Fulfillment of API Contract (‘API Abuse’).

APIs covered: Go hmac.Equal, subtle.ConstantTimeCompare, bcrypt.CompareHashAndPassword, rsa.VerifyPKCS1v15/VerifyPSS, ecdsa.Verify/VerifyASN1, ed25519.Verify; Python compare_digest, bcrypt.checkpw, check_password_hash, check_password, passlib verify; Node.js crypto.timingSafeEqual, crypto.verify, bcrypt.compare/compareSync, argon2.verify, verify() on a crypto.createVerify() object; PHP password_verify, hash_equals, openssl_verify, sodium verify functions; Ruby secure_compare, fixed_length_secure_compare, authenticate; Java MessageDigest.isEqual, BCrypt.checkpw, PasswordEncoder.matches, Signature.verify; C/C++ SSL_get_verify_result, X509_verify_cert, RAND_bytes, RAND_priv_bytes, EVP_DigestVerify(Final), EVP_VerifyFinal, RSA/ECDSA/DSA_verify, CRYPTO_memcmp. Go’s _ = and C’s (void) are reported too: discarding a verdict verifies nothing.

PHP’s openssl_verify() returns 1, 0 or -1 on error; if (openssl_verify(...)) treats the error as a valid signature and is reported as well.

Why This Matters

A discarded verdict turns an authentication or integrity check into a no-op: any password logs in, any webhook signature is accepted, any certificate is trusted.

Not reported: a result that is assigned, returned, tested or part of an expression (also across line breaks), the last expression of a Ruby method or block (its return value), a call that hands the verdict to a callback, APIs that raise on failure (jwt.verify, a JWTVerifier, the cryptography package’s verify), comments, string literals and test code.

What Gets Flagged

bcrypt.CompareHashAndPassword(u.Hash, []byte(password))
return newSession(u)
hmac.compare_digest(sig, expected)
return handle(request.json)
if (openssl_verify($data, $sig, $pub, OPENSSL_ALGO_SHA256)) {
SSL_get_verify_result(ssl);
RAND_bytes(nonce, 16);

Remediation

Test the return value (or the error) and stop when verification fails:

if err := bcrypt.CompareHashAndPassword(u.Hash, []byte(password)); err != nil {
	return nil, err
}
if (openssl_verify($data, $sig, $pub, OPENSSL_ALGO_SHA256) !== 1) {
    throw new InvalidSignature();
}

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

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

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