Safeguard
Application Security•

ASVS 5.0 V3.3.1: Set the Secure Attribute and Cookie Prefixes

ASVS 5.0 V3.3.1 (Level 1) requires the Secure attribute on cookies, with a __Host- or __Secure- name prefix. Express, Django and Spring settings and tests.

Safeguard Security Team
4 min read

ASVS 5.0 V3.3.1 requires that every cookie has the Secure attribute, and that its name carries the __Host- prefix, or the __Secure- prefix if __Host- does not fit. It is a Level 1 requirement.

Why it matters

Without Secure, a browser will send the cookie over plain HTTP too. On a shared network an attacker can trigger one plain request, for instance by injecting an image or link, and read the session cookie. HSTS reduces this but does not cover the first visit or every subdomain.

The prefixes add a guarantee enforced by the browser. A cookie named __Secure- can only be set from a secure origin with the Secure attribute. A __Host- cookie must also have Path=/ and no Domain attribute, so a sibling subdomain cannot overwrite it.

How to implement it

Set the attribute and rename the cookie.

Express:

res.cookie("__Host-session", token, {
  secure: true,
  httpOnly: true,
  sameSite: "lax",
  path: "/",
});

Django, in settings:

SESSION_COOKIE_NAME = "__Host-sessionid"
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_PATH = "/"
SESSION_COOKIE_DOMAIN = None
CSRF_COOKIE_NAME = "__Host-csrftoken"
CSRF_COOKIE_SECURE = True

Spring Boot:

server.servlet.session.cookie.name=__Host-JSESSIONID
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.http-only=true

Do not forget cookies set by frameworks, analytics code and your own JavaScript. If a cookie must be shared across subdomains, use __Secure- and set Domain deliberately. Serve everything over HTTPS and add HSTS, since a Secure cookie is useless if the site never uses TLS.

How to test it

Log in and inspect the Set-Cookie headers in browser developer tools or with curl:

curl -si https://example.com/login -d 'user=a&pass=b' | grep -i set-cookie

Each line should include Secure, and the name should start with __Host- or __Secure-. Then request the site over HTTP and confirm that no cookie is sent back. Automated web scans flag missing attributes, and it is worth adding a CI check that fetches key pages and parses the headers.

The cookie inventory

List every cookie your domains set. Session and CSRF cookies are the obvious ones. Then add those set by the load balancer for stickiness, by single sign on libraries, by consent banners, analytics and chat widgets, and by your own front end code. Each one gets a row: name, purpose, Secure, HttpOnly, SameSite, prefix, lifetime, and whether it is shared across subdomains.

Browser developer tools, a proxy, or a crawl through a web scanner will give you the live list. Compare it with what the team believes it sets. The difference is usually the interesting part.

Local development and testing

Secure cookies are not stored over plain HTTP, and __Host- cookies need HTTPS too. Browsers treat localhost as a secure context, so most local work still functions. For staging, use real certificates. Avoid environment switches that turn Secure off, since they tend to reach production.

Rollout without logging everyone out

Renaming a cookie invalidates existing sessions. Plan for it. Accept both the old and the new name for a period, issue only the new one, and remove the old name after the longest session lifetime has passed. Expire the old cookie explicitly with a Set-Cookie that has a past expiry.

Check it in CI

A small test that requests the login page and parses the headers prevents regressions:

import requests

r = requests.post("https://staging.example.com/login", data={"u": "test", "p": "test"})
for header in r.raw.headers.getlist("Set-Cookie"):
    assert "Secure" in header, header
    assert header.startswith(("__Host-", "__Secure-")), header

Common mistakes

  • Setting Secure only in production, so tests never see it.
  • Renaming the cookie but adding a Domain attribute, which makes the browser drop a __Host- cookie.
  • Forgetting secondary cookies, such as CSRF or preferences.
  • Terminating TLS at a proxy and having the app think the request is plain HTTP, so it omits Secure.

Try it yourself

A web scan reports cookie attributes among its findings.

Click through it at your own pace, or open it full screen.

Quick check

What extra guarantee does the __Host- prefix give?

The browser only accepts it from a secure origin with Secure set, Path of slash and no Domain, so subdomains cannot overwrite it.

Why can a proxy cause missing Secure flags?

If TLS ends at the proxy, the app sees HTTP. Configure forwarded protocol headers so the app knows the original scheme.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.