Same-site cookies: a defense-in-depth layer for cross-site attacks

Dropbox relies on traditional CSRF defenses, but has added same-site cookies as an additional layer of protection on modern browsers. This piece details how we rolled out that defense and what we learned along the way.

Why same-site cookies matter

Cross-site requests are the backbone of several attack classes, most notably cross-site request forgery (CSRF). In a CSRF scenario, a victim visits a malicious site like www.evil.com, which serves a page whose payload triggers a request to a trusted endpoint such as https://dropbox.com/cmd/delete. If the user is authenticated, the browser attaches cookies, and the server processes an unintended state change.

Our comms team told us we need an image; our legal team told us it needed to be freely licensed. Credit: Carsten Schertzer (Creative Commons Attribution 2.0).

A standard defense is the CSRF token: a random value stored in a cookie like csrf_cookie, which the client must also send as a request parameter on unsafe requests. The server compares the two values and rejects mismatches. However, even with that in place, cross-origin information leakage remains possible—for example, cross-site search attacks or JSON hijacking. A GET endpoint like /get_files might return filenames, and the response size alone could leak how many files a user has.

The newly standardized same-site cookie (SameSite) gives us a simpler way to reduce that attack surface. Browsers simply will not attach such cookies to cross-site requests.

Design goals and tradeoffs

Our requirements for a same-site cookie solution were straightforward:

  • CSRF defense: all POST requests must be same-site by default, since they are state-changing. GET requests are safe from CSRF but need scrutiny for information leakage.
  • Information leakage defense: AJAX GET requests should be same-site, as they are common vectors for cross-origin leakage.
  • Availability: we needed a careful rollout with easy rollback, without breaking legitimate cross-site GET requests like shared public links opened from email.
  • Flexibility: the mechanism should extend beyond POST and AJAX GET to other request types and routes as needed.

The SameSite attribute takes two enforcement values. With strict, the cookie is never sent on a cross-site request. With lax, the cookie is withheld only on unsafe requests—POSTs—but still sent on safe ones like GETs.

Consider Dropbox’s simplified cookie set: a session_cookie for authentication and a csrf_cookie for CSRF protection. We quickly rejected several naive approaches:

  • Applying SameSite=strict to session_cookie would lock out users clicking shared links from external pages, as the browser wouldn’t attach the session cookie on that cross-site GET.
  • Using lax for session_cookie only blocks CSRF on authenticated users, not unauthenticated login CSRF, and leaves information leakage via AJAX GET open.
  • Making csrf_cookie itself strict disrupts incremental rollout, and because the CSRF token is cryptographically tied to session_cookie to prevent session fixation, a missing token on a simple cross-site GET would trigger a logout.
Candidate design #1: Dropbox sets the session cookie as SameSite with enforcement mode strict on login.
Candidate design #1 isn’t ideal as a benign cross-site GET requests treat the user as logged out even if they were logged in.
Candidate design #2: Dropbox sets the session cookie as SameSite with enforcement mode lax on login. CSRF defenses will only work for authenticated requests, e.g. the first login request could have been a cross-site request.
Candidate design #3: Dropbox sets the CSRF cookie as SameSite with enforcement mode strict on login.
Candidate design #3 isn’t ideal because we always check whether the session cookie was cryptographically bound to the CSRF token, even on benign GET requests.

Instead of modifying existing cookies, we introduced a new one: __Host-samesite_cookie. It is marked SameSite=strict and set on all browsers that support same-site cookies. Its value is derived from the CSRF token, and on every relevant request we validate both its presence and correctness—the value check guards against session fixation if an attacker had pre-set a cookie with the same name.

Final design: Introduce a new cookie with SameSite set to strict and value derived from the CSRF token.

Because the cookie is strict, it won’t be sent on benign cross-site GETs, such as a user opening a Dropbox link from an external page. That’s acceptable, because enforcement happens server-side: if a GET arrives without the cookie, we can still allow it; only a state-changing POST lacking the cookie is treated as a CSRF failure.

We can control enforcement on the server side. Benign GET requests will be allowed, as we can ignore samesite protection on non-AJAX GET requests.

We also chose the __Host- prefix. A __Host- cookie must be set only by the host that receives it, so JavaScript on a Dropbox subdomain cannot forge it. When both csrf_cookie and __Host-samesite_cookie are present and valid, we can be confident no session fixation occurred.

Rollout in two phases

Cookie authentication changes are risky—they can lock users out, misdirect sessions, or disable CSRF defenses entirely. We therefore rolled out in two stages: first “warnings-only,” logging all violations, and then “enforcement” once we saw no unexpected errors.

To recap, the new cookie is __Host-samesite_cookie, set as SameSite=strict on supporting browsers, with its value derived from csrf_cookie. During the warnings phase we checked it against three request categories:

  1. POST requests on routes not whitelisted for CSRF: report an error if the cookie is invalid. (Some logging routes are intentionally exempt.)
  2. AJAX GET requests: report a potential cross-origin information leakage error if the cookie is invalid.
  3. Non-AJAX GET requests: log the route as a potential entry point if the cookie is invalid.
if not is_present_and_valid(cookies.get("__Host-samesite_cookie")):
  if request.method == "POST" and not skip_csrf_check(route): # Case 1
    report("CSRF error", route)
  elif request.type == "AJAX" and request.method == "GET": # Case 2
    report("Cross-origin error", route)
  elif request.type != "AJAX" and request.method == "GET": # Case 3
    log("entry_points_log", route)
  else:
    pass

In the first category, false alarms were minimal, so we switched to enforcement mode, returning HTTP 403 on violations.

The second category exposed a few surprises. Some AJAX GET endpoints, like those used by the Dropbox Saver, are cross-site by design and required whitelisting. We also found that service worker AJAX GET fetches did not send the same-site cookie—a Chrome bug we reported. For those service worker routes, we added an explicit header check to block cross-site requests before enabling enforcement.

The third category led us to an interesting observation about site structure. Most sites have only a few true “entry points”—top-level pages like https://www.dropbox.com or https://www.dropbox.com/help—while internal pages like https://www.dropbox.com/team/admin/members are reached by navigation from those entry points, not by direct visits. Enforcing same-site checks on all non-AJAX GET routes helped us identify routes that appeared to be non-entry points:

  • Routes used by team administrators to manage member accounts.
  • Support flow pages where users answer questions before reaching customer service.
  • Some legacy routes that relied on confirmation dialogs before taking action.

In the end, we found far fewer non-entry points than expected—modern web applications likely have relatively few such routes, though we’d welcome outside observations on that front.

SameSite as Defense in Depth

Because SameSite cookie enforcement is not yet supported in every browser, it should be treated as a defense-in-depth measure rather than a standalone fix. Deployed alongside existing CSRF protections, it adds a meaningful layer of security without compromising availability for users on older browsers.

For new deployments, the recommended approach is to introduce a separate cookie with SameSite set to strict, while controlling the actual enforcement logic server-side. This design keeps the strict policy from interfering with legitimate cross-site flows that require the primary session cookie. The dedicated cookie should ideally use the __Host- prefix, and its value should be derived from the CSRF token to tie the two protections together.

Dropbox has adopted this strategy to take advantage of same-site protections in modern browsers, adding another layer of security for user data without disrupting existing sessions or workflows.