Skip to content

VNX-501: Trust Boundary Violation

Detects HTTP request data (parameters, headers, cookies, query string, Spring request arguments, Flask/Django request fields) stored in the server-side session without validation, inline, through a few assignments or a list or map; operands the enclosing method proves constant are not reported. Java and Python.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-501
Severity Medium
SARIF level warning
Kind sast
Languages Java, Python
CWE CWE-501
CAPEC CAPEC-66
ATT&CK T1190
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 trust-boundary session cwe-501
Source internal/sast/rules/vnx-501.rego

Overview

VNX-501 maps to CWE-501: Trust Boundary Violation. The server-side session is trusted state: code that reads session.getAttribute("role") or session["user_id"] assumes the server put it there. Storing a value straight from the request in the session moves untrusted data across that boundary, so a later check that trusts the session trusts the attacker.

The rule reports:

  • Java: HttpSession.setAttribute() / putValue() (on a session variable or on request.getSession()) whose key or value is getParameter*, getHeader*, getCookies, getQueryString, getReader, getInputStream, getPathInfo, getRequestURI, or a Spring @RequestParam / @PathVariable / @RequestHeader / @CookieValue / @RequestBody argument. The value may arrive inline or through any chain of assignments up to twelve deep in the 60 lines above, including a for-each over getCookies(), a single-line if (...) x = y;, an assignment whose right side continues on the following lines, an object built from the request (new Reader(request)) whose getters are then called, a map entry read back under the same literal key, and a helper method of the same file that returns its parameter, unless every argument it was handed is a constant.
  • Python: session[...] = ... (Flask session, flask.session, Django request.session) and session.update() / setdefault() with a key or value from request.args/form/values/cookies/headers/json/data/query_string/get_data/stream/files/GET/POST/COOKIES/META, or from an object built from request, through the same kind of assignments (f-string interpolation included).

What Gets Flagged

// FLAGGED: request parameter becomes session state
String role = req.getParameter("role");
req.getSession().setAttribute("role", role);
// FLAGGED: Spring request argument stored in the session
public String applyDiscount(@RequestParam("code") String code, HttpSession session) {
    session.setAttribute("discountCode", code);
# FLAGGED
session["next_url"] = request.args.get("next")
request.session["lang"] = request.GET["lang"]

What Is Not Flagged

  • Constants and server-generated values (session.setAttribute("nonce", UUID.randomUUID().toString())).
  • A value checked first by a line that names it with .contains(), .matches(), re.match, a valid*/sanitize* call or in ALLOWED.
  • A session written after a password check or authenticate() call: the identity the server established.
  • A map read under a different key than the one the request value was stored under, and a same-file helper whose return value is not derived from its parameters.
  • A name that the enclosing method or function (at most 120 lines up) proves holds a constant where the session is written: a literal it was last assigned on every path; the branch an if/else or conditional expression takes when its condition folds to a constant (integer literals and integer locals assigned once, or in Python a literal tested with in / not in against a string local); the case a switch or match on a constant selects; the element a list index or a map, dict or ConfigParser key actually holds when that is a literal; in Python, a copy (saved = label) of what the name held at that line; the result of a same-file method or function whose every return is constant; in Python, a getter of a request-wrapper object whose method, declared anywhere in the project, returns only literals (a “safe value” accessor); a literal tested inside a string local ('x' in label) is a substring test on a constant and is not an allowlist check on label.
  • Reading the session, comments and string literals.
coupon = request.form.get("coupon", "")
fields = {"currency": "EUR"}
fields["coupon"] = coupon
currency = fields.get("currency")
session["cart.currency"] = currency   # not flagged: that key holds "EUR"
code = fields.get("coupon")
session["cart.coupon"] = code         # flagged

A value stored in a list or a Python dict is followed to a later read of that collection. Anything the analysis cannot resolve stays reported: a condition on run-time data, a helper in another file, a collection passed to a call.

Calls that cannot bring data in

A call into code the scan cannot see (another class or module) is treated as inert, and its value as a constant, when all of these hold:

  • its name is not that of a source (read*, next*, getParameter*, getHeader*, getCookie*, getenv, getProperty, query, execute);
  • every argument is a constant: a literal, or a name the method fixes;
  • its receiver is a type name, a new expression with constant arguments, or a local built from such a call, and that local is only ever called with constant arguments.
// NOT REPORTED: the helper is given literals only
ModeBook book = ModeBooks.standard();
String channel = book.entry("web");
session.setAttribute("shop.channel", channel);

// REPORTED: a run-time argument
String channel = book.entry(coupon);

// REPORTED: the receiver was built from, or fed, a run-time value
ModeBook scoped = new ModeBook(request.getParameter("scope"));
String region = scoped.entry("eu");

// REPORTED: a source-like method name
String channel = Prompts.readLine("channel");

Anything that fails one of the tests stays reported, so a helper that hands back its argument is never mistaken for a constant.

Remediation

  1. Validate the value against an allowlist or a strict pattern before it enters the session.
  2. Prefer storing a server-side lookup result (the user record found by id) over the raw request text.
  3. Keep authorisation state (roles, ownership) derived on the server, never copied from the request.

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

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

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