Engineering · Sep 29, 2026 · 6 min read

Insecure cookie attributes: how a missing flag exposes a valid session

Secure, HttpOnly and SameSite each close a different attack path - but severity is set by the chain the missing flag enables, not by the absence alone.

A Set-Cookie header without the Secure flag sends a session token over an unencrypted connection. Without HttpOnly, any script on the page can read it. Without SameSite, a cross-origin form forces the browser to include it in a forged request. Each missing attribute is a deterministically readable fact - but the severity depends on the chain the absence enables, not on the absence alone.

01Three flags, three distinct protection classes

Secure, HttpOnly and SameSite each close a different attack path at the transport, script and cross-origin layer respectively. A cookie can be missing all three, one, or none - and the risk profile changes at each combination. Checking header presence is the first step; confirming the chain that the missing flag opens is what decides the severity number.

Set-Cookie: session=TOKEN; Path=/ missing Secure ──► sent over HTTP on any mixed-content request missing HttpOnly ──► document.cookie returns the token to any JS missing SameSite ──► cross-origin form or img tag carries it automatically Set-Cookie: session=TOKEN; Secure; HttpOnly; SameSite=Lax; Path=/ all three paths closed - baseline for a session token
Three flags, three independent protection layers. Each absence removes exactly one.

02Confirming the missing flag is a different task per class

Reading the Set-Cookie header tells you which flags are absent. Confirming the chain requires a second probe for each class. For Secure, the confirming artifact is the cookie appearing in a request sent over HTTP - observable when the application runs a mixed-content flow or has an HTTP endpoint that redirects to HTTPS after the cookie is already in-flight. For HttpOnly, the confirming step is an XSS-class reflection: if a script-execution sink is confirmed on any page that shares the cookie scope, the cookie is confirmed exfiltrable. SameSite absence is confirmed by a cross-origin POST that the server accepts with the session cookie forwarded - the same differential as the CSRF confirmation path.

set-cookie-inspection.httphttp
# Example A - three flags missing on a session cookie
HTTP/1.1 200 OK
Set-Cookie: session=abc123; Path=/
# no Secure, no HttpOnly, no SameSite

# Example B - same endpoint, all flags present
HTTP/1.1 200 OK
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/

# Example C - common mismatch: Secure and SameSite present,
# HttpOnly omitted - JS access still open
HTTP/1.1 200 OK
Set-Cookie: session=abc123; Secure; SameSite=Strict; Path=/

The probe is read-only: no write to any data store, no script execution triggered during header inspection. The confirming step for HttpOnly requires a live XSS sink be established first - the cookie-flag finding and the XSS finding are separate records that correlate into a combined severity when both are confirmed.

Proven - CriticalHttpOnly absent + stored XSS - /account/profile - session theft chain
Step 1: Set-Cookie on /login ──► session=TOKEN; Secure; SameSite=Lax; Path=/ (HttpOnly absent) Step 2: Stored XSS probe to /account/profile ──► payload stored Step 3: Victim session renders /account/profile ──► document.cookie evaluated by stored payload Combined chain: HttpOnly absent + script execution sink = confirmed cookie exfil
Oracle: multi-step - header inspection + stored XSS arithmetic confirmer. Severity critical - two individually high findings combine into a confirmed session-theft path. Correlation is source-aware: one deduplication key per root cause, not two alerts.

03Severity calibration by chain depth

A missing flag alone is a medium. A missing flag plus a confirmed sink that reaches it is high or critical, depending on the sink. The table below tracks how severity shifts as the chain deepens - confirming only the header state without the chain is accurate but incomplete, and the risk score reflects what is actually proven.

Missing flagAlone+ confirmed chainWhat decides the chain
SecureMedium - cookie sent over HTTPHigh - session observed on a plaintext exchangeHTTP endpoint or mixed-content resource in the same scope
HttpOnlyMedium - JS read access openCritical - confirmed cookie exfil via XSS execution sinkStored or reflected XSS confirmed on any in-scope page
SameSite absentMedium - cross-origin carriage openHigh - state-change accepted from a controlled origin (CSRF)State-changing endpoint without a validated CSRF token
All three absentHigh - compound exposureCritical - any of the three chains can confirmFirst confirmed chain anchors the severity

Correlating the cookie flag finding with an XSS or CSRF finding is what justifies the severity escalation. Raising the combined finding as a single unit of work - with both confirming artifacts attached - gives the remediation team the full picture rather than two medium findings that look independent on a dashboard.

04Domain scope, Path scope and persistence compound the blast radius

The Secure, HttpOnly and SameSite flags describe the access policy. The Domain, Path and Max-Age attributes describe who gets the cookie and how long it lives. An HttpOnly absence on a cookie scoped to a single path is a different finding from one scoped to the root domain - the second version hands the same token to every page and subdomain. When Domain is set to the registered domain (.example.com) rather than the specific host (app.example.com), any subdomain - including one that may have different trust properties or a looser CSP - receives the same session token. A persistent Max-Age in the tens of millions means a stolen token works long after the user thinks they have logged out.

cookie-scope-examples.httphttp
# Narrowest scope: current host, path-restricted, session-only
Set-Cookie: session=TOKEN; Secure; HttpOnly; SameSite=Strict; Path=/app

# Broad scope: all subdomains, root path, persistent for ~1 year
Set-Cookie: session=TOKEN; Secure; HttpOnly; SameSite=Lax; Domain=.example.com; Path=/; Max-Age=31536000
# Any subdomain - including a less-hardened one - receives this token

# Worst case: broad domain, no flags, persistent
Set-Cookie: session=TOKEN; Domain=.example.com; Path=/; Max-Age=31536000
# No Secure, no HttpOnly, no SameSite - sent over HTTP to all subdomains, JS-readable,
# cross-origin carriable, and the window is a year wide

The finding records all four dimensions: the flags present and absent, the Domain and Path scope, the persistence duration, and the confirmed chain that the combination enables. Fixing only the flags while leaving an overly broad Domain is an incomplete remediation; the posture reflects that until all four are correct.

3
flag classes, each a separate protection
4
scope dimensions checked per cookie
0
records written during flag inspection

The cookie-attribute check is one of the few findings where the header inspection alone is deterministic and the confirming chain is the work that follows - not a reason to skip the inspection, but a reason to keep severity calibrated to what is actually proven rather than what the worst-case chain could eventually reach.

Evidence over heuristicsReachable beats foundMonitor → Block: rolling out a gate without slowing teamsIaC-grounded threat modelingAir-gapped AppSec with no phone-homeOne platform vs three tools
See it on your own app →