VNX-642: Authorization state kept in a client-controlled cookie
Reports a plain (unsigned) cookie whose name is a privilege flag or role, such as isAdmin, role or access_level, being set or read back in JavaScript/TypeScript, Python, Java or Kotlin. Signed cookies and server-side sessions are not reported.
| Field | Value |
|---|---|
| Rule ID | VNX-642 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Java, Kotlin, JavaScript / TypeScript, Python |
| CWE | CWE-642 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | external-control-state cookie authorization cwe-642 |
| Source | internal/sast/rules/vnx-642.rego |
Overview
VNX-642 maps to CWE-642: External Control of Critical State Data. It reports a cookie whose whole name is an authorization flag or role (admin, isAdmin, is_admin, role, user_role, permissions, access_level, isLoggedIn and similar) when the cookie is set or read without a signature.
Why This Matters
A plain cookie is client state. Anyone can open the browser’s developer tools and change isAdmin=0 to isAdmin=1. If the server reads that value back to decide what the user may do, the user decides their own privilege.
What Gets Flagged
res.cookie('role', user.role, { httpOnly: true }); // set, unsigned
const { isAdmin } = req.cookies; // read back
resp.set_cookie("is_admin", "1" if user.admin else "0")
admin_flag = request.COOKIES.get('admin')
Cookie roleCookie = new Cookie("role", acct.getRole());
if (c.getName().equals("isAdmin") && "true".equals(c.getValue())) {
public ResponseEntity<String> reports(@CookieValue("role") String role) {
A read that is itself the decision, compared or tested on its line (if (req.cookies.isAdmin === 'true'), if request.COOKIES.get('admin') == "1":) or assigned to a variable that a condition uses within 15 lines, is reported by VNX-784 (a security decision on a cookie), so VNX-642 does not report that line again. Writes, and reads that decide nothing on the spot, stay with VNX-642.
Not flagged: res.cookie(..., { signed: true }), req.signedCookies, Django set_signed_cookie/get_signed_cookie, Flask session[...], and cookies with other names (session, auth_token, theme, admin_theme).
Remediation
- Keep roles and privilege flags in the server-side session, keyed by an opaque session id.
- If the value must travel in a cookie, sign it (
cookie-parserwith a secret andsigned: true, Djangoset_signed_cookie) and still re-check it against the user record. - Never authorise from a value the client can write.
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-642
// nosec vnx-642
Or record an ignore rule: vulnetix ignore add --rule vnx-642 --reason "...". See Suppressing findings.