If your consent cookie’s expires is built from Date.prototype.toString() rather than toUTCString(), Safari ignores the attribute and keeps the cookie only until the browser quits. The banner then comes back for every returning visitor on Safari, and nowhere else. WebKit fixed it in Technology Preview 252 on 11 September 2026, but only for document.cookie — the Set-Cookie header path is untouched. We reproduced both halves in shipping Safari 26.6.2 today.
This is the kind of bug that gets logged as “our consent rate is worse on Safari” and never traced, because nothing errors. The cookie is written. It reads back correctly for the whole session. It simply is not there tomorrow.
The two date formats
JavaScript gives you two obvious ways to turn a date into a string, and they are not interchangeable in a cookie:
const exp = new Date(Date.now() + 180 * 864e5);
exp.toUTCString() // "Sun, 11 Oct 2026 22:11:46 GMT" day before month
exp.toString() // "Sun Oct 11 2026 16:11:46 GMT-0600 (Central Standard Time)"
// month before day
RFC 6265 defines Expires in terms of the first format. The second is what you get if you interpolate a Date into a template string without thinking about it, which is an easy thing to do:
// the version that breaks on Safari
document.cookie = `consent=${value}; expires=${exp}; path=/`;
// the version that works everywhere
document.cookie = `consent=${value}; expires=${exp.toUTCString()}; path=/`;
The first line has no template placeholder for a format, so exp is coerced with toString(). That is the whole bug.
What WebKit says
The Technology Preview 252 release notes, published today, list it under Networking: “Fixed cookies being stored as session cookies when their Expires date uses the month-before-day format produced by Date.prototype.toString().”
The bug report is worth reading because it argues the spec point precisely: under RFC 6265 section 5.1.1 the parenthesised timezone comment should be harmless, since “(“, “)” and “-” are all delimiters, so "Thu Apr 10 1980 16:33:12 GMT-0700 (Pacific Daylight Time)" tokenises into pieces from which the time, day, month and year are all recoverable. The assignee then narrowed it further: “the real issue was not parenthesized comments. The true issue was that CFNetwork date parsing rejects dates in ‘Month First’ order.”
Two details from the commit matter for anyone deciding what to do about this. It describes the effect as “converting the cookie from a persistent value to a session cookie (which disappears when the browser quits)”. And it states its own limit:
This fix can only cover
document.cookiecases. TheSet-Cookieresponse-header path is parsed inside NSURLSession before WebKit sees the response, so there is no interception point.
So a consent cookie set by your server, in that format, is not fixed by this at all.
Reproducing it without restarting the browser
“Disappears when the browser quits” is awkward to test. There is a shortcut: set the cookie’s Expires to a date in the past. A browser that parses the date deletes the cookie immediately. A browser that rejects the date ignores the attribute and keeps the cookie — which is the same code path that makes it a session cookie. So the cookie still being there is the failure.
// plant it as a session cookie first
document.cookie = "probe=1; path=/";
// then try to expire it, month-first
document.cookie = "probe=1; expires=" + new Date(Date.now() - 7*864e5).toString() + "; path=/";
// still readable? then Expires was ignored
document.cookie.includes("probe=1"); // Safari: true. Chrome: false.
We ran that on macOS 26.6.2 today, against both write paths:
document.cookie Set-Cookie header
Safari 26.6.2 month-first: KEPT month-first: KEPT
RFC format: deleted RFC format: deleted
Chromium 152 both deleted both deleted
Both halves are live in the shipping browser. Neither the Safari 26.6 nor the Safari 27 release notes mention either fix as of today, so the document.cookie half is currently in Technology Preview only, and the header half has no public fix at all.
One aside for anyone trying to check this in Safari’s own tooling: its Cookie Store API returns objects with only name and value. There is no expires property to read, so cookieStore.get() cannot tell you whether a cookie is persistent there. Use the Storage tab in Web Inspector, or the probe above.
The second Safari bug: seven-day consent
Technology Preview 251, two weeks earlier, fixed “the CNAME-cloaking cookie expiry cap being applied to a top-level page’s own cookies instead of only subresources”. Safari caps cookies set through CNAME-cloaked third parties at seven days. The commit explains what went wrong: a change from December 2024 “dropped the top-level navigation exemption”, so “a site whose own hostname is CNAMEd across registrable domains has cookies set by its own top-level response capped to 7 days”.
Read that condition carefully, because it is more common than it sounds. If www.yoursite.com is a CNAME to a hosting platform or CDN on a different registrable domain — which is the normal setup on most managed platforms — then cookies your own server sets in the page response have been capped at seven days on Safari since that regression landed. A consent record written that way is gone the following week, and the banner returns.
The distinction to check is where the cookie is written. A cookie set by JavaScript in the browser is not affected by this cap. One set by your server in the Set-Cookie header of the page response is. If you store consent server-side and reflect it into a cookie, that is the affected pattern.
What to check in your own stack
The major open-source CMPs are not affected. We read the current sources today: CookieConsent (src/utils/cookies.js), Klaro (src/utils/cookies.js) and c15t (packages/core/src/libs/cookie/operations.ts) all call toUTCString(), and tarteaucitron uses toGMTString(), which produces the same format. The exposure is in hand-written consent code, which is most sites that never installed a CMP, plus any custom layer wrapping one.
- Grep for cookie writes that interpolate a date:
grep -rn "expires=" --include=*.js .and look for anything that is not followed bytoUTCString(),toGMTString()or a literal string. - Prefer
Max-Age, which takes an integer number of seconds and has no date parsing at all:consent=1; Max-Age=15552000; Path=/; SameSite=Lax. Every browser in current use supports it, and it takes precedence overExpireswhere both are present. - Check your server-set cookies too, since those are unfixed even in the Technology Preview.
- Then verify on a real Safari with the past-date probe above, not on a WebKit-branded build of something else.
There is a measurement worth knowing even where the date parses: browsers ignore the timezone offset in Expires and read the clock time as UTC. We wrote a cookie in Chromium with toString() from a UTC-6 machine, intending 22:11 UTC, and it was stored expiring at 16:11 UTC — six hours early. West of UTC your cookies die sooner than you meant; east of it they outlive what you intended, which is its own problem when the number you wrote down is a retention period.
A consent record that quietly resets is the same class of problem as the ones we have covered before: a stored decision that stops applying, and a refusal that does not do what the visitor thinks it does. If you are keeping records to show a regulator, the expiry you believe you set is part of the record — what proof of consent needs to contain covers the rest.
A consent cookie that silently becomes a session cookie looks fine in every test you run in Chrome. Scan your site to see what is actually stored, and for how long with CookieInspector.