How Scramjet emulates and stores cookies in proxified contexts
Scramjet provides a complete cookie emulation system that allows proxified pages to use cookies as if they were on the original domain. This guide explains how cookie management works and how to interact with it.
When a website is accessed through Scramjet, it’s technically served from your proxy domain, not the original domain. This means normal browser cookies wouldn’t work correctly. Scramjet solves this by:
Intercepting Set-Cookie headers from responses
Storing cookies in IndexedDB keyed by the original domain
Injecting stored cookies into requests to the original domain
Emulating document.cookie to work with the stored cookies
All cookie data is stored client-side in IndexedDB under the $scramjet database.
const url = new URL('https://example.com/page');// Get cookies for JavaScript access (excludes HttpOnly cookies)const jsCookies = cookieStore.getCookies(url, true);console.log(jsCookies); // "sessionId=abc123; theme=dark"// Get all cookies including HttpOnly (for HTTP requests)const allCookies = cookieStore.getCookies(url, false);console.log(allCookies); // "sessionId=abc123; theme=dark; consent=true"
The fromJs parameter controls whether HttpOnly cookies are included. Set it to true for JavaScript access, false for HTTP requests.
// Cookies with past expiration dates are filtered outif (cookie.expires && new Date(cookie.expires) < now) { // Cookie is removed from storage}
2
Secure flag check
Secure cookies are only sent over HTTPS:
// Secure cookies require HTTPSif (cookie.secure && url.protocol !== 'https:') { // Cookie is excluded}
3
HttpOnly flag check
HttpOnly cookies are excluded from JavaScript access:
// HttpOnly cookies are not accessible to JavaScriptif (cookie.httpOnly && fromJs) { // Cookie is excluded}
4
Path matching
Cookies are only sent to matching paths:
// URL path must start with cookie pathif (!url.pathname.startsWith(cookie.path)) { // Cookie is excluded}
5
Domain matching
Cookies are only sent to matching domains:
// Check domain match for wildcard domainsif (cookie.domain.startsWith('.')) { if (!url.hostname.endsWith(cookie.domain.slice(1))) { // Cookie is excluded }}
When setting cookies, Scramjet applies sensible defaults:
// If domain is not specified, use the current hostnameif (!cookie.domain) { cookie.domain = '.' + url.hostname;}// Ensure domain starts with a dot for wildcard matchingif (!cookie.domain.startsWith('.')) { cookie.domain = '.' + cookie.domain;}// Default path to rootif (!cookie.path) { cookie.path = '/';}// Default SameSite to Laxif (!cookie.sameSite) { cookie.sameSite = 'lax';}// Normalize expires to stringif (cookie.expires) { cookie.expires = cookie.expires.toString();}
The CookieStore provides methods to serialize and deserialize cookie data:
import { CookieStore } from '@/shared/cookie';const cookieStore = new CookieStore();// ... set some cookies ...// Serialize cookies to JSON stringconst serialized = cookieStore.dump();console.log(serialized); // '{"domain@path@name":{...},...]'// Save to localStorage, IndexedDB, etc.localStorage.setItem('cookies', serialized);// Later, restore cookiesconst restored = localStorage.getItem('cookies');const newCookieStore = new CookieStore();newCookieStore.load(restored);
// Cookie for exact domain'session=abc; Domain=example.com'// Stored as: .example.com// Accessible to: example.com only// Cookie for domain and subdomains'session=abc; Domain=.example.com'// Accessible to: example.com, www.example.com, api.example.com
// Cookie for all paths'token=xyz; Path=/'// Accessible to: /page, /api/data, /anything// Cookie for specific path'admin=1; Path=/admin'// Accessible to: /admin, /admin/users// Not accessible to: /page, /api
// Store auth token in cookiedocument.cookie = `auth=Bearer_${token}; path=/; secure; max-age=${60*60*24*7}`;// Automatically sent with requestsconst response = await fetch('/api/protected');