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.
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
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
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
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
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)