Skip to content

VNX-372: Incomplete Internal State Distinction

A substring or element search whose result has three states (not found, found at position 0, found later) is tested as a boolean, so two of the states are treated as one

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-372
Severity Low
SARIF level note
Kind sast
Languages Python, PHP, JavaScript / TypeScript
CWE CWE-372
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L
Tags internal-state-distinction return-value cwe-372
Source internal/sast/rules/vnx-372.rego

Overview

VNX-372 maps to CWE-372: Incomplete Internal State Distinction. A substring or element search whose result has three states (not found, found at position 0, found later) is tested as a boolean, so two of the states are treated as one. Reported in Python: str.find()/rfind() used directly as a condition (if/elif/while/assert, an and/or operand, a conditional expression, bool()) or compared with True/False, which treats ‘not found’ (-1) as found and a match at index 0 as missing. Reported in PHP: strpos/stripos/strrpos/strripos (also mb_ and iconv_ forms) used as a condition, negated with !, used in a ternary, or loosely compared (==, !=, <>) with false, true, null or 0, which conflates ‘not found’ (false) with a match at offset 0. Reported in JavaScript/TypeScript: indexOf/lastIndexOf/findIndex used directly as an if/while condition, a && or || operand, a ternary test, or loosely compared with true/false, which treats -1 as found and index 0 as missing. Not reported: comparisons with -1, >= 0, > -1, !== false or === 0; the starts-with idioms not s.find(x) and !s.indexOf(x) (true only at index 0) and ~s.indexOf(x); a result assigned and checked later; BeautifulSoup, ElementTree, lxml, Selenium and MongoDB find() (files that import them, receivers named soup/tree/root/node/element and keyword-argument calls); in, includes() and str_contains(); comments and string literals.

Why This Matters

A search such as str.find(), strpos() or indexOf() has three outcomes: not found, found at position 0, found later. Testing it as a boolean folds them into two, and the wrong two: -1 (not found) is truthy in Python and JavaScript, and a match at offset 0 is falsy in all three. In PHP false and 0 are both falsy, so a match at the start reads as no match. A check written this way lets through the value it was meant to catch.

What Gets Flagged

if content_type.find("json"):      # FLAGGED: true when "json" is absent
    return True
if (strpos($email, 'admin@') == false) {   // FLAGGED: loose comparison
if (url.indexOf('/api/')) {        // FLAGGED
return user.roles.indexOf('editor') && doc.editable;   // FLAGGED
if (value.search(/@/)) {           // FLAGGED: String.search() returns -1 too
if (array_search($id, $ids)) {     // FLAGGED: false and key 0 are both falsy

What Is Not Flagged

if "json" in content_type: ...
if content_type.find("json") != -1: ...
if not path.find("/static"): ...   # true only at index 0 (starts-with idiom)
if (strpos($email, 'admin@') !== false) { ... }
if (url.includes('/api/')) { ... }
if (!url.indexOf('/static/')) { ... }   // starts-with idiom

BeautifulSoup, ElementTree, lxml, Selenium and MongoDB find() (which return an object or None), a find() in a Python file that defines its own def find( method, a JavaScript .search() whose argument is not a regex or string literal (a search client), comments and string literals are not reported.

Remediation

  1. Use the membership form: x in s, str_contains(), includes().
  2. When you need the position, compare it explicitly with -1 (Python, JavaScript) or with !== false (PHP).

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

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

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