Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/binary-person/rammerhead/llms.txt

Use this file to discover all available pages before exploring further.

A session is the fundamental unit of Rammerhead. Every browsing context that flows through the proxy belongs to exactly one session. The session holds the user’s cookies, their localStorage data, an optional per-session upstream proxy, and the character dictionary used for URL shuffling. Understanding sessions is essential before configuring session stores, expiry, or IP restriction.

What a session contains

A RammerheadSession wraps testcafe-hammerhead’s Session class and extends it with persistence data stored in session.data:
class RammerheadSession extends Session {
    data = {};
    createdAt = Date.now();
    lastUsed  = Date.now();
    // ...
    constructor({ id = generateId(), dontConnectToData = false,
                  disableShuffling = false, prependScripts = [] } = {}) {
        // ...
        this.id         = id;
        this.shuffleDict = disableShuffling ? null : StrShuffler.generateDictionary();
        if (!dontConnectToData) {
            this.connectHammerheadToData();
        }
    }
}
The data object is what gets serialized to disk or memory. It holds:
FieldDescription
createdAtUnix timestamp when the session was created
lastUsedUnix timestamp updated on every request
shuffleDictThe per-session URL shuffling dictionary (null when shuffling is off)
cookiesThe cookie jar, stored as the idx of _cookieJar.store
externalProxySettingsUpstream HTTP proxy for this session, or null
injectableScripts injected into every proxied page
restrictIPIP address that is allowed to use this session

Session lifecycle

1

Create a session

The client calls GET /newsession. Rammerhead generates a unique ID, creates a RammerheadSession, stamps session.data.restrictIP with the caller’s IP, and immediately persists the empty session to the store.
curl http://localhost:8080/newsession
# returns: a32f91bc...  (the session ID)
The session ID is what the client embeds in all subsequent proxy URLs.
2

Use the session

Every proxied request looks up the session by ID. The store updates session.lastUsed on each retrieval so the expiry clock resets with activity.
3

Edit the session (optional)

Use GET /editsession to attach an upstream proxy or toggle URL shuffling without recreating the session:
# attach an upstream proxy
curl "http://localhost:8080/editsession?id=SESSION_ID&httpProxy=http://proxy.example.com:3128"

# disable URL shuffling
curl "http://localhost:8080/editsession?id=SESSION_ID&enableShuffling=0"

# re-enable URL shuffling
curl "http://localhost:8080/editsession?id=SESSION_ID&enableShuffling=1"
4

Session expires

The session store periodically sweeps for sessions that have exceeded their inactivity timeout or maximum lifetime and deletes them. The exact thresholds depend on which store you use.
5

Delete a session explicitly

curl "http://localhost:8080/deletesession?id=SESSION_ID"

Cookies

Rammerhead mirrors the proxied site’s cookies into the session’s cookie jar (session.cookies). The jar is backed by _cookieJar.store.idx, which is wired into session.data via a property hook:
connectHammerheadToData(dontCookie = false) {
    // ...
    if (!dontCookie) this._connectObjectToHook(this.cookies._cookieJar.store, 'idx', 'cookies');
}
This means every Set-Cookie header the proxied site sends is captured in the session and is automatically replayed on future requests within the same session — just like a real browser. When a session is serialized to disk, the full cookie jar is included:
serializeSession() {
    return JSON.stringify({
        data: this.data,
        serializedCookieJar: this.cookies.serializeJar()
    });
}
Because cookies are stored server-side in the session, the proxied site’s cookies are invisible to the user’s browser. This prevents browser extensions or DevTools from leaking them, and means cookies survive even if the user clears their browser.

localStorage sync

Unlike cookies, localStorage is managed by the browser’s JavaScript engine and is not visible to the proxy at the network layer. Rammerhead solves this by injecting rammerhead.js into every proxied page, which intercepts localStorage reads and writes and syncs them to the server via the /syncLocalStorage endpoint. The sync uses a timestamp-based conflict resolution strategy: when two clients have diverging localStorage state, the version with the higher timestamp wins. This allows a session to be shared across browser tabs or devices without silent data loss.
localStorage sync can be disabled globally by setting disableLocalStorageSync: true in src/config.js. This is useful if clients send very large localStorage payloads that would cause excessive memory use on the server.

IP restriction

When restrictSessionToIP is true in src/config.js, the IP that called /newsession is recorded in session.data.restrictIP. The proxy rejects any subsequent request for that session that comes from a different IP.
// in GET /newsession handler (setupRoutes.js)
session.data.restrictIP = config.getIP(req);
This prevents session hijacking: even if a session ID is leaked, an attacker on a different IP cannot use it.
If Rammerhead sits behind a reverse proxy (nginx, Cloudflare, etc.), the socket address will always be the proxy’s IP. Set getIP to read from X-Forwarded-For so that the real client IP is captured:
// src/config.js
getIP: req => (req.headers['x-forwarded-for'] || req.connection.remoteAddress || '').split(',')[0].trim()

External proxy per session

Each session can route its outbound traffic through a separate upstream HTTP proxy, set via /editsession?httpProxy=. This is stored in session.externalProxySettings and passed directly to testcafe-hammerhead’s request pipeline:
// setupRoutes.js
if (httpProxy) {
    if (httpProxy.startsWith('http://')) {
        httpProxy = httpProxy.slice(7);
    }
    session.setExternalProxySettings(httpProxy);
} else {
    session.externalProxySettings = null;
}
Passing no httpProxy parameter (or an empty value) clears the upstream proxy and routes traffic directly.

Session stores

Rammerhead ships two session store implementations. Both extend RammerheadSessionAbstractStore.
Sessions are serialized to JSON files in a configurable directory (.rhfsession extension). Active sessions are held in a memory cache; inactive ones are flushed to disk after a timeout and re-loaded on demand.
// src/config.js
fileCacheSessionConfig: {
    saveDirectory: path.join(__dirname, '../sessions'),
    cacheTimeout: 1000 * 60 * 20,          // flush to disk after 20 min idle
    cacheCheckInterval: 1000 * 60 * 10,     // check every 10 min
    deleteUnused: true,                      // drop sessions never used after creation
    staleCleanupOptions: {
        staleTimeout: 1000 * 60 * 60 * 24 * 3, // delete after 3 days idle
        maxToLive: null,                         // no absolute lifetime cap
        staleCheckInterval: 1000 * 60 * 60 * 6  // sweep every 6 hours
    },
    deleteCorruptedSessions: true,
}
When to use: production deployments where sessions must survive server restarts.
RammerheadSession.serializeSession() produces a JSON string containing the data object (shuffleDict, cookies idx, proxy settings, timestamps) and a separately serialized cookie jar. DeserializeSession(id, json) reconstructs a full RammerheadSession from that string without touching the hook system, then calls connectHammerheadToData(true) to re-attach property hooks with the restored data.
When deleteCorruptedSessions: true (the default), RammerheadSessionFileCache catches JSON parse errors and automatically deletes the damaged file. The user will need to create a new session. This most often occurs when the Node.js process is killed while writing a session to disk.
Yes. GET /sessionexists?id=SESSION_ID returns "exists" or "not found" without touching lastUsed or loading the session into memory.

URL shuffling

How per-session URL encoding works and why it matters

JavaScript caching

Disk and memory caches for rewritten JS

Build docs developers (and LLMs) love