DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

Set-Cookie: Secure=1 Is a Cookie Named Secure, and It Travels Over Plain HTTP

Six stateless evaluators in this series so far — paste input, get a verdict. This one has a jar. Feed it the Set-Cookie headers a server sent at an origin, then ask for a URL, and it computes the exact Cookie: request header a browser would send, ordering included, naming the rule that dropped every cookie that did not make it.

Working engine, 76 reference cases: https://dev48.infy.uk/solve/day67-cookie-jar-simulator.html

The trap at the centre is positional, not lexical

Set-Cookie: Secure=1; Path=/     -> a cookie NAMED "Secure". Not secure. Goes over http.
Set-Cookie: a=b; Secure          -> the flag. The cookie never leaves https.
Enter fullscreen mode Exit fullscreen mode

Same token, opposite meaning, decided only by which side of the first semicolon it sits on. It falls out of four lines that do no lookahead whatsoever:

const semi = s.indexOf(";");
const nv   = semi < 0 ? s : s.slice(0, semi);   // the name-value pair, WHATEVER it spells
const eq   = nv.indexOf("=");                   // the FIRST '=', so a value may contain more
Enter fullscreen mode Exit fullscreen mode

The same four lines make a=b; c=d one cookie — c is an unknown attribute, silently ignored — and a=b, c=d one cookie named a with the value "b, c=d". Which is why Set-Cookie is the single field RFC 9110 forbids folding: an Expires date has a comma in it.

The rule most people invert: the default Path is a directory

Omit Path and the cookie does not get the site. It gets the directory of the URL that set it.

const i = p.lastIndexOf("/");
return i === 0 ? "/" : p.slice(0, i);   // everything BEFORE the last slash
Enter fullscreen mode Exit fullscreen mode

So a session cookie set by POST /app/login has Path=/app and is invisible on the home page. The bug reads as "the user is logged out on the home page only". Drop the i === 0 branch and /login becomes "", which then matches everything for a completely different reason.

Runner-up: Domain= widens. No Domain is host-only; adding Domain=example.com shares the cookie with every subdomain you do not control, and you cannot go the other way — Domain=shop.example.com from example.com is refused outright.

Nor is path matching a prefix test. Path=/foo does not match /foobar:

if (cookiePath.charAt(cookiePath.length - 1) === "/") return true;
return reqPath.charAt(cookiePath.length) === "/";      // the boundary test
Enter fullscreen mode Exit fullscreen mode

Same shape and the same missing line in domain-match, where a plain endsWith lets notexample.com match example.com.

What is actually checked

check result
reference cases 76 (20 storage, 56 retrieval, 11 expecting nothing sent)
independent verifier assertions 103, 0 failures
path-match pairs vs an RFC §5.1.4 oracle 54, 0 disagreements
domain-match pairs vs an independent oracle 28, 0 disagreements

The oracles were written from RFC 6265 §5.1.3–§5.1.4 rather than from my implementation, which is the only version of this exercise worth doing.

The two specs disagree on 7 of 12 scenarios

Counted by running one scenario list twice behind a flag, rather than written down:

if (mode === "rfc6265") return "none";      // the attribute did not exist in 2011
return "lax";                               // the 6265bis default
Enter fullscreen mode Exit fullscreen mode

SameSite is absent from RFC 6265 (2011) entirely, and a current browser treats its absence as Lax — the biggest behaviour change in the header's history, invisible in the header itself. None of the seven differences are core rules; they are all additions. A server written against the 2011 spec still disagrees with a shipped browser about what is stored and what is sent.

And the jar key is (name, domain, path), not the name — so two cookies called sid are two rows and both go out, longest path first. That is the "I logged out and I am still logged in" bug: a delete is only an expiry in the past, and it matches only if the domain and the path match too.

Part of a from-scratch series — one tool a day, all client-side: https://dev48.infy.uk/solvefromzero.php

Top comments (0)