Skip to content

VNX-551: Incorrect Behavior Order: Authorization Before Parsing and Canonicalization

An access decision is taken on a request-supplied path or resource name that is only decoded, parsed or canonicalised afterwards, so the check judges a different string from the one the code uses.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-551
Severity Low
SARIF level note
Kind sast
Languages Java, JavaScript / TypeScript, Python
CWE CWE-551
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 authz-before-canonicalization cwe-551
Source internal/sast/rules/vnx-551.rego

Overview

VNX-551 maps to CWE-551: Incorrect Behavior Order: Authorization Before Parsing and Canonicalization. It reports an authorisation check on a value taken from the request when that same value is decoded, parsed or canonicalised in the lines after the check.

Why This Matters

A check on the raw text is not a check on what the code goes on to use. /%61dmin, /a/../admin and /admin;x fail a startsWith("/admin") test, then decode or normalise to /admin, and the protected resource is served. A file name that contains .. gets past a permission check written against the literal name, then normpath or getCanonicalPath turns it into a different file.

What Gets Flagged

The rule reports the check when:

  • the value is assigned from the request (servlet getRequestURI/getPathInfo/getParameter/getHeader, Django/Flask request.path/args/GET, Express req.url/originalUrl/params/query) in the 20 lines above;
  • the check compares it with a '/...' literal (startsWith, equals, includes, ==, ===) in a condition, or passes it to an authorisation call (authorize, checkPermission, hasPermission, canAccess, isAllowed…);
  • one of the next 15 lines decodes or canonicalises that same variable (URLDecoder.decode, decodeURIComponent, unquote, normpath, realpath, getCanonicalPath, Paths.get(...).normalize(), path.normalize, urlparse, new URL).
String uri = request.getRequestURI();
if (uri.startsWith("/admin") && !isAdmin(request)) {   // FLAGGED
    response.sendError(HttpServletResponse.SC_FORBIDDEN);
    return;
}
String resource = URLDecoder.decode(uri, StandardCharsets.UTF_8);
name = request.args.get("name", "")
if not has_permission(current_user(), name):            # FLAGGED
    abort(403)
target = os.path.normpath(os.path.join(ROOT, name))

Not flagged: a value decoded or canonicalised before it is checked, a check on one variable followed by decoding another, values that do not come from the request, comments and string literals. Related rules: VNX-179 (checks for .. or < before decoding), VNX-289 (guards on the raw request path) and VNX-41 (directory-containment checks on non-canonical paths).

Remediation

  1. Decode, parse and canonicalise the value first: URLDecoder.decode then Paths.get(...).normalize(), unquote then os.path.realpath, decodeURIComponent then path.posix.normalize.
  2. Authorise the canonical form, and use exactly that form afterwards.
  3. Prefer framework path matching (getServletPath(), Spring Security matchers) over hand-written prefix checks on getRequestURI().

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

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

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