Skip to content

VNX-437: Incomplete Model of Endpoint Features

Reports JavaScript/TypeScript and Python code that vets a URL with a simpler model than the client uses to act on it (a raw-string startsWith/includes/endsWith/in check against a host, or Node's legacy url.parse()) and then requests or redirects to the unchanged string.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-437
Severity Low
SARIF level note
Kind sast
Languages JavaScript / TypeScript, Python
CWE CWE-437
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:L/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L
Tags incomplete-model-endpoint-features url-parsing ssrf open-redirect cwe-437
Source internal/sast/rules/vnx-437.rego

Overview

VNX-437 maps to CWE-437: Incomplete Model of Endpoint Features. The check and the HTTP client or browser disagree about which host a URL names. A string check does not know about userinfo, subdomains or query strings, so https://trusted.example.com.evil.net, https://trusted.example.com@evil.net and https://evil.net/?trusted.example.com all pass it, and the request goes to the attacker (SSRF or open redirect).

What Gets Flagged

The line that requests or redirects, when within fifteen lines above it the same variable was checked as a raw string against a host literal:

  • startsWith/startswith with a scheme and host but no / after the host;
  • endsWith/endswith, includes, indexOf or Python in with a host literal;
  • a regular-expression literal whose pattern ends at the host, tested with .test() (/^https:\/\/cdn\.acme\.com/.test(u)).

Also, in Node, a request of the original string when the host, hostname, protocol or origin was vetted on the result of the legacy url.parse(), whether assigned, read inline (url.parse(u).hostname) or destructured (const { hostname } = url.parse(u)).

if (!target.startsWith('https://cdn.acme-media.com')) return res.sendStatus(400);
const upstream = await fetch(target);                          // FLAGGED

const parsed = url.parse(hookUrl);
if (!ALLOWED.has(parsed.hostname)) throw new Error('denied');
https.request(hookUrl, { method: 'POST' });                    // FLAGGED
if not url.startswith("https://api.partner-data.com/"):        # ends at "/": not flagged
    abort(400)
return requests.get(url, timeout=5)

Not reported: prefix checks that end at a path separator, checks on a parsed new URL()/urlparse hostname (including a re-parse between the raw check and the request), checks not followed by a request of the same variable, a positive check whose block does not contain the request, scheme-only checks, comments and strings. Request smuggling through a lenient HTTP parser is VNX-444.

Remediation

  1. Parse the URL once (new URL(), urllib.parse.urlsplit) and compare the hostname exactly against an allow-list.
  2. Request the parsed URL, not the original string.
  3. If a prefix check is unavoidable, end it with / after the host.

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

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

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