How to check if cookies are secure and HttpOnly
Print the response headers and read every Set-Cookie line with curl -s -o /dev/null -D - https://example.com/, piped to grep -i set-cookie. A session cookie is covered when its line carries both attributes. Secure keeps it off plain HTTP requests, and HttpOnly keeps it out of document.cookie.
Why check this
The two attributes defend against two different attacks and neither substitutes for the other. Without Secure, the cookie is attached to the first plain HTTP request a user makes, which happens before the redirect to HTTPS lands. Without HttpOnly, one stored cross-site scripting payload reads the session and posts it somewhere else. Run this check on every response that sets a cookie after a login, and repeat it after any change to the authentication service or the CDN in front of it.
Prerequisites
- curl 8.0 or later, with GNU grep and sed. See the curl manual.
- A request that sets the cookie. A logged-out homepage sets analytics cookies, not the session cookie you want to audit.
- MDN on Set-Cookie for the full attribute list.
Steps
- Step 1.
Print the name and the security attributes of every cookie the response sets, with the values and the lifetime removed.
curl -s -o /dev/null -D - https://www.cloudflare.com/ | grep -i '^set-cookie:' | sed -E 's/^[Ss]et-[Cc]ookie: ([^=]+)=[^;]*/\1/' | sed -E 's/ ?(Expires|Path|Domain|Max-Age)=[^;]*;?//gI'_ga; cfz_google-analytics_v4; HttpOnly; SameSite=Lax; Secure cfz_adobe; HttpOnly; SameSite=Lax; Secure kndctr_8AD56F28618A50850A495FB6_AdobeOrg_identity; Secure; SameSite=Lax __cf_bm; HttpOnly; SameSite=None; SecureFive cookies, one line each, in the order the server sent them. Anything after the name is an attribute you have to justify.
- Step 2.
Filter that list down to the cookies that are missing
Secure.curl -s -o /dev/null -D - https://www.cloudflare.com/ | grep -i '^set-cookie:' | sed -E 's/^[Ss]et-[Cc]ookie: ([^=]+)=[^;]*/\1/' | sed -E 's/ ?(Expires|Path|Domain|Max-Age)=[^;]*;?//gI' | grep -vi 'Secure'_ga;One cookie, set by the analytics script rather than the application. That is the judgement call this step produces: a name to check against what it holds.
- Step 3.
Run the same filter for
HttpOnly.curl -s -o /dev/null -D - https://www.cloudflare.com/ | grep -i '^set-cookie:' | sed -E 's/^[Ss]et-[Cc]ookie: ([^=]+)=[^;]*/\1/' | sed -E 's/ ?(Expires|Path|Domain|Max-Age)=[^;]*;?//gI' | grep -vi 'HttpOnly'_ga; kndctr_8AD56F28618A50850A495FB6_AdobeOrg_identity; Secure; SameSite=LaxThe second cookie has
Secureand noHttpOnly. It travels only over TLS and any script on the page can still read it. - Step 4.
Repeat step 1 against the
http://URL, because a cookie set before the redirect is a separate cookie.curl -s -o /dev/null -D - http://www.cloudflare.com/ | grep -i '^set-cookie:' | sed -E 's/^[Ss]et-[Cc]ookie: ([^=]+)=[^;]*/\1/' | sed -E 's/ ?(Expires|Path|Domain|Max-Age)=[^;]*;?//gI'__cf_bm; HttpOnly;The same cookie name that carried
SecureandSameSite=Noneover HTTPS is set here withHttpOnlyalone. The 301 response sets it before any TLS is involved. - Step 5.
Open the site in Chrome, press F12, then DevTools, Application tab, Storage, Cookies, and select the origin.
The grid has one column per attribute.
HttpOnlyandSecureare tick boxes, so a whole origin reads in one glance. To confirmHttpOnlyfrom the other side, open the Console tab and enterdocument.cookie: every cookie markedHttpOnlyis absent from the string that comes back, which is exactly the protection the attribute buys.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| sid; HttpOnly; Secure on a session cookie | Both defences are on | Nothing. Move on to the SameSite attribute. |
| Session cookie with Secure and no HttpOnly | Any script on the page can read the session | Fix it. One XSS finding becomes account takeover. |
| Session cookie with HttpOnly and no Secure | The cookie rides on the first plain HTTP request | Fix it. The redirect to HTTPS happens after that request. |
| Neither attribute on an analytics cookie | Normal for a third-party script | Record it and check what the value holds before waving it through. |
| A cookie set on the http:// response | It exists before TLS starts | Treat its contents as public. Nothing secret belongs there. |
| __Host- prefix on the name | The browser enforces Secure, host-only and Path=/ | Nothing. This is the strongest shape available. |
Common mistakes
What to check next
- How to check SameSite cookie attribute: the third attribute on the same line, and the one that governs cross-site requests.
- How to check cookie flags with curl: the cookie jar,
-band-c, and why curl's view differs from the browser's. - How to check HTTP to https redirect: the request that a cookie without
Secureis attached to. - How to check HTTP response headers with curl: the header-printing flags these commands rely on.
- Security headers checker: the rest of the response header set, read in one request.
FAQ
How to check if a cookie is HttpOnly?
Step 3 answers it from the server side. From the browser side, enter document.cookie in the Console: an HttpOnly cookie never appears in that string, while DevTools still lists it under Application, Cookies.
How to check the Secure flag on a cookie?
Step 2. The filter prints the cookies that lack it, which is shorter to read than the full list and is the answer you act on.
How to check cookies in browser DevTools?
Application tab, Storage, Cookies, then pick the origin. The grid shows one row per cookie with tick boxes for HttpOnly and Secure, so one screen covers the whole origin.
Does curl enforce the Secure attribute?
No. Against a local server on http://127.0.0.1, curl accepted a cookie marked Secure and wrote it to the jar with the secure column set to TRUE. curl records the attributes and leaves the rules to the browser.
Should analytics cookies carry both attributes?
They should carry Secure. HttpOnly is often impossible because the analytics script reads the value itself, which is a reason to keep anything identifying out of it.
Verified
Verified by Maks Vernycurl 8.21.0
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
basic5 minpublished updated Maks Verny