Skip to content

VNX-351: Insufficient Type Distinction

PHP in_array()/array_search() without strict mode on a request or JSON value, loose ==/!= on json_decode() output, strcmp() of a request value compared loosely with 0, and loosely compared hashes, where a value of another type passes the check.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-351
Severity Low
SARIF level note
Kind sast
Languages PHP
CWE CWE-351
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 insufficient-type-distinction cwe-351
Source internal/sast/rules/vnx-351.rego

Overview

VNX-351 maps to CWE-351: Insufficient Type Distinction. PHP’s loose comparison does not distinguish types: a JSON true equals every non-empty string, and numeric strings such as '1e0' or ' 1' equal numbers. The rule reports:

  • in_array() and array_search() without true as the third argument whose needle is json_decode() output, or a request value ($_GET, $_POST, $_REQUEST, $_COOKIE, $_SERVER, Laravel $request->input()) checked against a list that is not provably made of non-numeric string literals;
  • a loose ==, != or <> on an element or property of a json_decode() result (of anything but a file on disk);
  • strcmp(), strcasecmp() or strncmp() of an uncast request value compared loosely with 0 (== 0, !strcmp(...)): sent as password[]=, the value is an array, strcmp() returns null, and null == 0;
  • a loose ==/!= on md5(), sha1(), hash(), hash_hmac(), crypt() or crc32() output on a line that uses a request value: two 0e… digests are equal as numbers (“magic hashes”).

The needle may be inline or a variable assigned from the source in the 15 lines above.

Why This Matters

An allowlist or credential check that compares loosely accepts values the author never meant to allow. {"password": true} passes $input['password'] == $stored; in_array("1e0", [1, 2, 3]) is true.

Not reported: hash_equals(), === on hashes, strcmp() with a (string) cast or === 0, strict calls, constant needles, server-side needles, request strings checked against a list of non-numeric strings (a request string can only equal one of those exactly), ===/!==, comparisons with null or '', json_decode() of a file on disk, comments and string literals. Java class-by-name comparison is VNX-CWE1025-AUTO’s concern.

What Gets Flagged

$allowed_ids = [1, 2, 3];
if (in_array($_GET['id'], $allowed_ids)) {
$input = json_decode(file_get_contents('php://input'), true);
if ($input['password'] == $user['password_plain']) {
if (strcmp($_POST['password'], ADMIN_PASSWORD) == 0) {
if (md5($pass) == $row['pw_md5']) {

Remediation

  1. Pass true as the third argument to in_array() and array_search().
  2. Check the type of decoded JSON values (is_string, is_int) and compare with ===, or with hash_equals() for secrets.

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

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

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