Fixes
Cookie & Privacy Hygiene · Updated 2026-05-02
SameSite Audit
Pass needs explicit SameSite on session cookies. SameSite=None without Secure fails outright; missing on sessions warns.
The SameSite attribute is the browser's built-in CSRF defense. It tells the browser when to send your cookie on requests originating from another site: never (Strict), only on top-level navigation (Lax), or always (None, with Secure required). Modern browsers (Chrome 80+, ~2020) default to SameSite=Lax when no attribute is set, but explicit is still better, and SameSite=None without Secure is rejected outright by recent browsers.
RedScore parses every cookie set in first-response Set-Cookie headers across primary hosts and audits the SameSite attribute, with two failure tiers depending on what is wrong.
How the check works
Per primary host, the check evaluates SameSite in two passes:
- First pass (hard fail): if any cookie has SameSite=None without the Secure flag, the host scores 0.0. Reason: COOKIE_SAMESITE_NONE_NO_SECURE. Modern browsers reject these cookies outright; the combination is invalid.
- Second pass (warn): if no None+!Secure issues, but at least one session cookie has no SameSite attribute at all, the host scores 0.5. Reason: COOKIE_SAMESITE_MISSING. Non-session cookies missing SameSite do not trigger this; only session cookies are required to have an explicit value.
- Pass (1.0): every session cookie has an explicit SameSite, and any SameSite=None cookies also have Secure.
Per-host scores are weighted (apex highest) and averaged across all primary hosts to produce the category score (33 pts possible).
How the verdict maps to evidence
- Pass: every session cookie has an explicit SameSite; no SameSite=None cookies are missing Secure.
- Warn: at least one session cookie missing SameSite on at least one host.
- Fail: at least one cookie has SameSite=None without Secure on at least one host. This is the most damaging tier because the cookie may be rejected by the browser.
SameSite values explained
- SameSite=Strict. Cookie is NEVER sent on cross-site requests, including top-level navigation (clicking a link from another site to yours). Maximum CSRF protection. Breaks any flow where users arrive at your site from external links and expect to be logged in immediately. Use for high-sensitivity actions; pair with Lax for general session.
- SameSite=Lax. Cookie is sent on top-level navigation (clicking a link to your site) but NOT on subresource requests (images, iframes, form POSTs from other sites). The right default for most session cookies. Browser default since 2020.
- SameSite=None. Cookie is sent on ALL cross-site requests. Required for legitimate cross-site flows like third-party SSO IDPs, embedded payment widgets, embedded auth iframes. MUST be paired with Secure; the combination None+!Secure is rejected by modern browsers.
Fix the two failure modes
Fail: SameSite=None without Secure
If any cookie has SameSite=None without Secure, browsers may reject it entirely; the cookie is not stored at all. Two paths:
- If the cookie genuinely needs cross-site delivery (SSO IDP, embedded widget): add the Secure flag. SameSite=None requires HTTPS-only delivery, and the framework must be configured to set Secure. See the Cookie Secure Flag Audit guide.
- If the cookie does not need cross-site delivery: change SameSite to Lax (or Strict if cross-site nav is also unwanted). This is the right answer for most session cookies; SameSite=None is much rarer than people think.
Warn: session cookie missing SameSite
Modern browsers default missing SameSite to Lax, so this is less critical than it once was, but the score still warns because (a) older browsers default to None-equivalent behavior, and (b) explicit policy removes the ambiguity. Set SameSite=Lax (or Strict) on every session cookie.
Per-framework snippets
Express / Node.js (express-session)
import session from "express-session";
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
secure: true,
httpOnly: true,
sameSite: "lax", // or "strict" for max protection
maxAge: 1000 * 60 * 60 * 8,
},
}));Django
# settings.py
SESSION_COOKIE_SAMESITE = "Lax" # or "Strict" or "None" (with SESSION_COOKIE_SECURE = True)
CSRF_COOKIE_SAMESITE = "Lax"Rails
# config/environments/production.rb
Rails.application.config.session_store :cookie_store,
key: "_yourapp_session",
secure: true,
httponly: true,
same_site: :lax # or :strict, or :none (with secure: true)ASP.NET Core
services.AddSession(options => {
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
// Or for the cookie policy middleware globally:
services.Configure<CookiePolicyOptions>(options => {
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});Spring Boot
# application.properties
server.servlet.session.cookie.same-site=laxLaravel
// config/session.php
'same_site' => 'lax',Plain Set-Cookie header
Set-Cookie: session_id=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
# Cross-site cookie (must have Secure):
Set-Cookie: sso_token=xyz; Path=/; Secure; HttpOnly; SameSite=None__Host- prefix for self-policing
Cookies whose name starts with __Host- are required by the browser to have Secure, Path=/, no Domain attribute, and (effectively) host-only scope. Combined with explicit SameSite, this is the strictest cookie shape a browser supports:
Self-policing host-bound session cookie
Set-Cookie: __Host-session=abc123; Path=/; Secure; HttpOnly; SameSite=LaxVerify the fix
- curl -sI -c /dev/null https://yourdomain.tld | grep -iE 'set-cookie' shows the Set-Cookie headers. Confirm SameSite= appears on every session cookie and that any SameSite=None pairs with Secure.
- Open the page in a browser, open DevTools → Application → Cookies → yourdomain.tld. The "SameSite" column shows the parsed value. "None" entries should also have Secure ticked.
- Browser console errors. If a cookie is being rejected for SameSite=None without Secure, Chrome and Firefox log it explicitly in the console. Check after deploying.
- Re-run the RedScore lookup. Pass requires explicit SameSite on every session cookie and Secure on every SameSite=None cookie.
Common pitfalls
- Setting SameSite=None without thinking about why. SameSite=None is for genuine cross-site delivery: SSO IDPs, embedded payments, third-party widget auth. Most session cookies do not need it. Default to Lax.
- SameSite=None on a non-Secure cookie. Modern Chrome and Firefox reject the cookie outright. The user is silently logged out (or never logged in). Always pair None with Secure.
- SameSite=Strict breaking inbound links. With Strict, a user clicking a link from gmail.com to yourdomain.tld arrives logged out because the cookie is not sent on the cross-site navigation. Use Lax for general session cookies; reserve Strict for sensitive operations or pair with a separate "navigation OK" cookie.
- Behind a TLS-terminating proxy. If the framework thinks the request is HTTP (because the proxy is HTTP-to-app), some implementations refuse to set Secure, which then breaks SameSite=None requirements. Configure proxy trust headers (X-Forwarded-Proto) so the framework knows.
- OAuth callback flows breaking with Strict. Auth providers redirect users back to your callback URL after sign-in; with Strict, the cookie set during the OAuth start is not sent on the callback navigation, so the OAuth state cannot be verified. Use Lax for OAuth-flow cookies.
- Third-party widgets that need cross-site cookies. Embedded chat, support widgets, payment frames may need to set their own SameSite=None+Secure cookies. Their failures show up in this audit on the embedding host. Either configure the third-party correctly or accept the warn.
What to do next
See how these recommendations apply to your site's current scan results.
Scan domain