Skip to content

VNX-539: Use of Persistent Cookies Containing Sensitive Information

Detects persistent cookies that carry a credential or personal secret, and session or authentication cookies given a literal lifetime longer than 31 days, in JavaScript/TypeScript (Express, Koa), Python, Java (Servlet Cookie, Spring ResponseCookie), PHP and Ruby on Rails.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-539
Severity Medium
SARIF level warning
Kind sast
Languages JavaScript / TypeScript, Python, Java, PHP, Ruby
CWE CWE-539
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 cookie-persistent cwe-539
Source internal/sast/rules/vnx-539.rego

Overview

VNX-539 maps to CWE-539: Use of Persistent Cookies Containing Sensitive Information. A cookie with a lifetime is written to the browser’s disk and survives the session, so anyone with access to the machine, its backups or malware on it can read it later. This rule reports two shapes:

  1. A credential in a persistent cookie. A cookie named for a password, secret, SSN, credit card, CVV, API key or private key, whose value is not a constant, set with any positive lifetime or an expiry that cannot be read statically: Express res.cookie(..., { maxAge | expires }), Koa ctx.cookies.set(...), Python set_cookie(..., max_age= | expires=), Java Cookie.setMaxAge(n) and Spring ResponseCookie.from(...).maxAge(...), PHP setcookie(name, value, expiry), Rails cookies.permanent[...] = v and cookies[...] = { value:, expires: N.days.from_now }.
  2. A long-lived session. A session or authentication cookie (session, sid, auth, token, jwt, userid) or the framework session cookie (express-session/cookie-session cookie.maxAge, Django SESSION_COOKIE_AGE, Flask PERMANENT_SESSION_LIFETIME) with a literal lifetime over 31 days.

What Gets Flagged

// FLAGGED: the password is stored in the browser for 15 minutes past the session
res.cookie('password', req.body.password, { maxAge: 900000, httpOnly: true });
# FLAGGED: an identity cookie that lasts almost a year
response.set_cookie('userid', user.userid, max_age=31449600)
// FLAGGED
Cookie remember = new Cookie("password", password);
remember.setMaxAge(60 * 60 * 24 * 30);

What Is Not Flagged

  • Cookies with no lifetime (browser-session cookies) and deletions (maxAge 0, setMaxAge(0), an expiry in the past).
  • Constant values and preference cookies (theme, lang).
  • Session lifetimes of 31 days or less (Flask’s and Django’s defaults are within that), lifetimes computed at run time, and remember-me cookies.
  • Comments, string literals and test files.

Remediation

Never put credentials or personal data in cookies; keep them on the server and refer to them by an opaque session id. Give session cookies no maxAge/Expires, or a short one, and implement “remember me” as a separate, revocable, rotating token.

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

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

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