VNX-863: Incorrect Authorization
Authorization decided by a client-sent header, cookie or privilege-named query value, a role checked as a substring, a Python assert used as the check, or a JavaScript if that assigns the role instead of comparing it.
| Field | Value |
|---|---|
| Rule ID | VNX-863 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | JavaScript / TypeScript, Python |
| CWE | CWE-863 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H |
| Tags | incorrect-authorization cwe-863 access-control |
| Source | internal/sast/rules/vnx-863.rego |
Overview
VNX-863 maps to CWE-863: Incorrect Authorization. It reports four ways an authorization check is made wrongly, in Python and JavaScript/TypeScript:
- The client decides. A condition (
if/elif, ternary,&&/||) on a request header, cookie or DjangoMETAentry, or on a query parameter whose name says admin/role/staff/permission, directly or through a variable assigned from it within the 15 lines above. A header or cookie compared with an administrative literal ('admin','superuser','owner'…) counts whatever its name. Two cookie forms are left to the rule that names them more precisely: a cookie named for a privilege or login state, compared or tested on its line or through a variable, is reported by VNX-784 (a security decision on a cookie), and anif/elifcomparing any other request cookie with a privileged literal is reported by VNX-807 (untrusted input in a security decision). - Substring role check.
'admin' in user.role,role.startswith('admin'),user.role.includes('admin'),role.indexOf('superuser') !== -1,/moderator/.test(user.role):'not-admin'passes. - Assert as the check.
assert request.user.is_staff,assert user.has_role(...)outside tests;python -Oremoves it. - Assignment in the condition.
if (user.role = 'admin'),if (req.user.isAdmin = true): always true.
Not reported: roles read from the server-side user or session (req.user.role, req.session.role, user.roles lists), the Authorization header and other non-privilege headers, a query value compared with 'admin' for navigation, a condition that rejects a request claiming the privilege (if (req.query.role === 'admin') return 403), which refuses an escalation attempt. A negated guard that rejects when the claim is absent (if request.cookies.get("role") != "admin": abort(403)) still trusts the client and is reported, asserts in tests, comments and strings.
Why This Matters
Each of these checks exists and runs, which is why it survives review: it simply answers the wrong question. A cookie named admin is set by the browser, so any caller can send admin=1. A substring test accepts roles nobody intended, an assert vanishes in optimised builds, and an assignment grants the role it was meant to test.
What Gets Flagged
if request.META.get('HTTP_X_IS_STAFF'): # FLAGGED
if "admin" not in current_user.role: # FLAGGED (substring)
assert request.user.is_staff, "staff only" # FLAGGED
if request.headers.get("X-Role") != "admin": # FLAGGED (guard trusts the header)
abort(403)
if request.COOKIES.get('admin') == "1": # reported by VNX-784, not VNX-863
if (req.headers['x-role'] === 'admin') { // FLAGGED
const role = req.header('X-Role');
if (role === 'admin' || role === 'owner') { // FLAGGED
if (user.role.includes('admin')) { // FLAGGED
if (user.role = 'admin') { // FLAGGED
Not flagged: if (req.user.role === 'admin'), if "admin" in user.roles:, if request.args.get("tab") == "admin":, if (req.query.role === 'admin') return res.status(403)....
Remediation
- Decide authorization from the authenticated identity (session or verified token) and its stored roles, never from a value the client sends.
- Compare roles for equality, or test membership in a list of roles.
- Raise
PermissionDeniedorabort(403)instead of asserting. - Use
===in conditions; a linter rule such asno-cond-assigncatches the assignment form.
References
- CWE-863: Incorrect Authorization
- CWE-565: Reliance on Cookies without Validation and Integrity Checking
- OWASP Authorization Cheat Sheet
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-863
// nosec vnx-863
Or record an ignore rule: vulnetix ignore add --rule vnx-863 --reason "...". See Suppressing findings.