VNX-NODE-029: Missing HttpOnly flag on cookie
Reports session and credential cookies set from Node.js without HttpOnly: res.cookie()/cookie.serialize() calls lacking httpOnly: true, Set-Cookie header literals without the attribute, and any cookie options with httpOnly: false.
| Field | Value |
|---|---|
| Rule ID | VNX-NODE-029 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | JavaScript / TypeScript |
| CWE | CWE-1004 |
| CAPEC | CAPEC-63 |
| ATT&CK | T1059.007 |
| 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 | cookie httponly session |
| Source | internal/sast/rules/vnx-node-029.rego |
Overview
This rule detects calls to cookie() or res.cookie() in JavaScript files where the httpOnly option is absent or explicitly set to false. The HttpOnly flag instructs browsers to deny JavaScript access to a cookie via document.cookie. Without it, any XSS vulnerability in the application, or in a third-party script loaded by the page, can read session and authentication cookies and relay them to an attacker-controlled server, enabling complete session hijacking. This is classified as CWE-1004 (Sensitive Cookie Without ‘HttpOnly’ Flag).
Session tokens are the most valuable target of XSS attacks: a stolen session token provides the attacker with the victim’s authenticated identity without requiring their password. The HttpOnly flag is one of the simplest and most effective mitigations for this attack path. It does not prevent all XSS impacts, but it specifically and reliably removes the ability to steal cookies via document.cookie, significantly raising the cost of a successful XSS exploitation.
Why This Matters
Session hijacking via XSS-based cookie theft is one of the most impactful and easily executed web attacks. Once an attacker has a valid session token, they can impersonate the victim for as long as the session remains active, bypassing MFA, accessing sensitive account data, and performing actions on the victim’s behalf. The attack requires no interaction beyond a successful XSS payload, which can be delivered via a crafted link, an injected ad, or a compromised third-party script.
The HttpOnly flag has been supported by all major browsers since Internet Explorer 6, making it a zero-cost, universally compatible mitigation. Its absence is flagged as a finding in penetration tests, bug bounty programs, and compliance audits (PCI-DSS requirement 6.4). It should be set on all cookies that do not require client-side JavaScript access, which, for session and authentication cookies, is always the case.
What Gets Flagged
// FLAGGED: a token cookie with no options (Express defaults to script-readable)
res.cookie('token', token);
// FLAGGED: options across lines without httpOnly
res.cookie('access_token', accessToken, {
secure: true,
sameSite: 'strict',
});
// FLAGGED: the httpOnly: false line, for any cookie
response.cookie('sid', sid, { httpOnly: false, secure: true });
// FLAGGED: Set-Cookie header and cookie.serialize without HttpOnly
res.setHeader('Set-Cookie', `sessionId=${sessionId}; Path=/; Secure`);
cookie.serialize('auth', jwt, { path: '/', secure: true });
Not flagged: options with httpOnly: true (inline or in an options object defined in the file), non-sensitive cookies (theme, lang), CSRF/XSRF token cookies the page must read, Koa ctx.cookies.set() (HttpOnly by default), cookies being cleared, and test files.
Remediation
- Add
{ httpOnly: true }to allres.cookie()calls. This single flag eliminates JavaScript access to the cookie. - Combine
httpOnlywithsecure: true(HTTPS-only transmission) andsameSite: 'strict'or'lax'(CSRF mitigation) to form a complete cookie security configuration. - Use the
cookie-sessionorexpress-sessionmiddleware withhttpOnly: truein the session options object so that all session cookies are consistently secured. - Review whether any cookies genuinely need to be JavaScript-accessible, for most authentication and session cookies, they do not.
// SAFE: session cookie with full security flags
res.cookie('sessionId', token, {
httpOnly: true, // not accessible via document.cookie
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 3600000, // 1 hour in ms
path: '/',
});
// SAFE: express-session with httpOnly enabled
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000,
},
resave: false,
saveUninitialized: false,
}));
References
- CWE-1004: Sensitive Cookie Without ‘HttpOnly’ Flag
- MDN Web Docs, Set-Cookie: HttpOnly
- OWASP Session Management Cheat Sheet
- OWASP Testing for Cookies Attributes
- Express.js, res.cookie()
- PortSwigger Web Security Academy, Stealing cookies
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-node-029
// nosec vnx-node-029
Or record an ignore rule: vulnetix ignore add --rule vnx-node-029 --reason "...". See Suppressing findings.