# How camofox-browser Session Isolation Separates Cookies and Storage Between Users

> Discover how camofox-browser session isolation secures user data by creating separate Playwright browser contexts, preventing cookie and storage access between users.

- Repository: [jo/camofox-browser](https://github.com/jo-inc/camofox-browser)
- Tags: deep-dive
- Published: 2026-04-15

---

**camofox-browser enforces strict per-user isolation by creating a separate Playwright browser context for every userId, ensuring that cookies, localStorage, and cache from one user are never accessible to another.**

The jo-inc/camofox-browser project implements robust multi-user browsing automation by leveraging Playwright's BrowserContext API. Each user receives a completely isolated browser environment that maintains its own cookie jar, storage mechanisms, and cache, eliminating the risk of cross-contamination between sessions.

## Architecture Overview

### The Session Map

At the core of camofox-browser's isolation strategy lies a centralized session registry implemented as a JavaScript Map. Located in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js) at line 250, this structure maintains the association between users and their private browser environments:

```javascript
// server.js – line 250
// userId -> { context, tabGroups: Map<sessionKey, Map<tabId, TabState>>, lastAccess }
const sessions = new Map();

```

The map uses a normalized `userId` as the key, ensuring consistent lookups regardless of input formatting variations.

### Playwright BrowserContext Isolation

The architecture relies on Playwright's native `BrowserContext` abstraction, which provides complete state separation at the browser level. Each context maintains independent:

- **Cookie jars** (HTTP and JavaScript cookies)
- **`localStorage`** and **`sessionStorage`** namespaces
- **Browser cache** and **IndexedDB** storage
- **Proxy configurations** and **geolocation settings**

Because Playwright enforces these boundaries at the browser process level, no JavaScript execution or network request can breach the isolation between contexts.

## Implementation Details

### Context Creation via getSession()

The `getSession(userId)` function, spanning lines 701-756 in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js), serves as the gateway to per-user isolation. When a request targets a specific user, the function either retrieves an existing context or initializes a fresh one:

```javascript
// server.js – lines 701-756
async function getSession(userId) {
  const key = normalizeUserId(userId);
  let session = sessions.get(key);
  // ... validation logic ...
  if (!session) {
    const b = await ensureBrowser();
    const contextOptions = { /* proxy, locale settings */ };
    const context = await b.newContext(contextOptions);
    session = { 
      context, 
      tabGroups: new Map(), 
      lastAccess: Date.now(), 
      proxySessionId: sessionProxy?.sessionId || null 
    };
    sessions.set(key, session);
    log('info', 'session created', { userId: key });
  }
  session.lastAccess = Date.now();
  return session;
}

```

This implementation guarantees that each `userId` maps to exactly one `BrowserContext` instance, with optional per-user proxy settings applied during creation.

### Cookie Import Endpoint

The `POST /sessions/:userId/cookies` endpoint (lines 166-242 in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js)) validates and imports cookies exclusively into the requesting user's context. The server sanitizes input objects before injecting them via the context-specific `addCookies` method:

```javascript
// server.js – lines 166-242
app.post('/sessions/:userId/cookies', express.json({ limit: '512kb' }), async (req, res) => {
  // ... validation and sanitization ...
  const session = await getSession(userId);
  await session.context.addCookies(sanitized);   // ← isolated per-user context
  // ... response handling ...
});

```

Because `addCookies` operates on a specific `BrowserContext` instance, imported data remains confined to that user's session jar.

### Automatic Cleanup and Context Recreation

The system implements robust error handling to maintain isolation integrity even during browser crashes. Lines 705-712 in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js) contain dead-context detection logic that identifies failed contexts before they can corrupt user data:

```javascript
// server.js – lines 705-712 (dead-context detection)
try {
  session.context.pages();   // throws if context is dead
} catch (err) {
  log('warn', 'session context dead, recreating', { userId: key, error: err.message });
  session.context.close().catch(() => {});
  sessions.delete(key);
}

```

When a context failure is detected, the system immediately removes the stale entry from the session map. Subsequent requests for that user trigger `getSession` to spawn a fresh, empty context, ensuring no residual data persists from the failed session.

## Verification Through Code Examples

### Importing Cookies for a Specific User

The following API request demonstrates how cookies are injected into an isolated user session:

```bash
curl -X POST http://localhost:9377/sessions/alice/cookies \
  -H "Content-Type: application/json" \
  -d '{
    "cookies": [
      {"name":"sessionid","value":"abc123","domain":"example.com","path":"/"},
      {"name":"pref","value":"dark","domain":"example.com"}
    ]
  }'

```

The server routes this request through `getSession("alice")`, ensuring the cookies are added only to Alice's context via `session.context.addCookies()`.

### Verifying Cross-User Isolation

This pseudo-code from the test suite confirms that user data remains segregated:

```javascript
// client.js – test suite helper
await client.post('/sessions/alice/cookies', { 
  cookies: [{ name: 'a', value: '1', domain: 'ex.com' }] 
});
await client.post('/sessions/bob/cookies', { 
  cookies: [{ name: 'b', value: '2', domain: 'ex.com' }] 
});

const aliceCookies = await client.get('/sessions/alice/cookies'); // → [{name:'a',…}]
const bobCookies = await client.get('/sessions/bob/cookies');     // → [{name:'b',…}]

```

Despite targeting the same domain (`ex.com`), Alice's context contains only cookie `a`, while Bob's contains only cookie `b`, proving complete isolation between the `BrowserContext` instances.

### Automatic Context Recreation

When a context becomes unresponsive, the system transparently recreates it without user intervention:

```javascript
// Simulate context destruction
await client.delete('/sessions/alice');          // destroys Alice's context
await client.post('/sessions/alice/cookies', {   // triggers getSession
  cookies: [{ name: 'new', value: 'data', domain: 'test.com' }]
});

```

The new context starts with an empty cookie store and fresh storage backends, maintaining the isolation guarantee.

## Summary

- **Per-User BrowserContext**: camofox-browser creates a separate Playwright `BrowserContext` for every `userId`, providing hardware-level isolation of cookies, storage, and cache.
- **Centralized Session Map**: The `sessions` Map in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js) (line 250) tracks each user's context and metadata, keyed by normalized user identifiers.
- **Strict API Boundaries**: The `getSession()` function and cookie import endpoint ensure all browser state operations target exactly one user's context.
- **Self-Healing Isolation**: Dead context detection automatically removes failed sessions and creates fresh, empty contexts to prevent data leakage or corruption.
- **No Cross-Contamination**: Playwright's native architecture ensures that even under error conditions, no code path exists that could share a context between different `userId` values.

## Frequently Asked Questions

### How does camofox-browser prevent cookie leakage between users?

camofox-browser prevents cookie leakage by instantiating a separate Playwright `BrowserContext` for each `userId` via the `getSession()` function in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js). Each context maintains its own independent cookie jar, localStorage, and cache at the browser process level. When the `POST /sessions/:userId/cookies` endpoint receives data, it calls `session.context.addCookies()`, which injects cookies only into that specific user's isolated storage.

### What happens when a browser context crashes unexpectedly?

When a browser context crashes, the `getSession()` function detects the failure by invoking `session.context.pages()` (lines 705-712), which throws an error if the underlying context is dead. The system immediately closes the corrupted context, deletes the entry from the global `sessions` Map, and creates a fresh `BrowserContext` with empty storage. This ensures that subsequent requests for that user operate in a clean, isolated environment without residual data from the failed session.

### How can existing cookies be imported into a specific user's session?

Clients import cookies by sending a POST request to `/sessions/:userId/cookies` with a JSON payload containing cookie objects. The server validates and sanitizes these objects before calling `session.context.addCookies(sanitized)` (lines 166-242 in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js)). Because this operation targets a specific user context retrieved via `getSession(userId)`, the cookies are stored exclusively in that user's browser state and remain inaccessible to other users.

### Where is session state stored within the CamoFox server architecture?

Session state is stored in an in-memory Map declared at line 250 of [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js), keyed by normalized `userId` strings. Each Map entry contains the Playwright `BrowserContext` instance, a nested Map of tab groups, a timestamp of last access, and optional proxy session identifiers. This design keeps all user-specific browser state—including cookies, HTML5 storage, and cache—encapsulated within the Playwright context object referenced by the Map entry.