One Chrome window, several isolated workspaces: how it actually works
Single-window isolation is three layers stacked: session and storage, network routing, lifecycle — not kernel virtualisation. It does not change the fingerprint.
Why opening more windows does not give you multiple accounts logged in at once
Start by dropping an intuition: a window is not a unit of isolation. Ten windows in one profile share one cookie store, one localStorage and one HTTP cache, and the site sees one session. Incognito does have a separate store, but there is only one of it and it dies with the window, so it cannot serve ten accounts. The real unit is a session container: an independent cookie set and storage namespace plus a stable ownership map. Chrome has no container API of its own — Firefox’s contextualIdentities is the exception — so an extension has to build one at the session layer: record which tab belongs to which container, then split requests and page storage along that map. CrispLogin is built this way: the container is an ownership record the extension keeps, not a Chrome primitive. The unit of isolation is the session, not the process.
Layer one: why cookies do not bleed
Accounts stay apart because of a virtual cookie jar plus per-tab request-header rewriting. Each container holds its own cookie records inside the extension. Before a request leaves, a declarativeNetRequest session rule matches the tab through condition.tabIds and sets the cookie request header to that container’s value; when the response comes back, webRequest observes Set-Cookie and writes it into the matching jar. The jar has to serialise its cookies in path-length then creation-time order, or the header will not match what the browser would have sent.
What people miss is lifetime: tabIds only works on session rules, and session rules are cleared when the browser shuts down or the extension updates. So the rules must be reinstalled on every start — skip that and the symptom is “after a restart every workspace is the same account”. Page storage is a second surface: document.cookie reads the real cookie store and localStorage has a single namespace, so a MAIN-world script has to proxy reads and writes per container. This is application-layer isolation: it separates two accounts on one site, but it does not touch the kernel fingerprint.
Layer two: how the proxy follows the workspace
Chrome has no per-tab proxy API. chrome.proxy.settings is scoped to the whole profile, and a PAC script’s FindProxyForURL(url, host) never receives a tabId; declarativeNetRequest can rewrite headers and redirect, but it has no “pick an upstream proxy” action. So what an extension can do is keep a workspace-to-egress map and rewrite that profile-wide configuration when the foreground tab changes.
PAC can split by domain, but its input is the URL, not the tab — the same site open in two workspaces gets one answer from PAC. So “ten workspaces each on its own tunnel” does not exist in an extension; what is guaranteed is “whichever workspace is in front, that is the egress”. It is also why switching workspaces shows a reconnect: change the proxy configuration and established connections are rebuilt. Boundary: this layer changes the egress IP and the request path, not the TCP/TLS fingerprint (JA3/JA4); WebRTC can be limited with chrome.privacy.network.webRTCIPHandlingPolicy to drop non-proxied UDP, but that is a browser-wide policy, not a per-workspace one.
Layer three: does the binding survive a closed tab or a restart?
The MV3 background is a service worker: 30 seconds without an event and it is terminated, taking module-level variables and any Map held in a closure with it. So “which tab belongs to which workspace” cannot live in memory; it goes into chrome.storage.session, which stays in memory while the extension is loaded and survives the worker being recycled — and is cleared when the browser restarts. Note that it is not exposed to content scripts by default; if the page side needs to read the binding, call setAccessLevel explicitly.
That splits the data in two: runtime bindings in session, and everything that must survive a restart — workspace list, proxy configuration, snapshot index — in storage.local. The causal chain is direct: because the binding lives in session rather than in a variable, a woken worker still knows which tab belongs to which workspace and will not pour one workspace’s cookies into another’s page. Keep it in memory only and the moment the worker sleeps, the next request has no owner to look up — which shows up as duplicated groups and snapshots restored into the wrong container.
Layer four: what a site snapshot stores, and what it restores
A snapshot answers a different question: portability of login state, not runtime isolation. It sits fourth in this list but is not part of isolation itself. It serialises the cookies of one site inside one container plus that site’s storage, and on restore pours them back into that container’s namespace. The two are orthogonal: isolation keeps two workspaces from interfering while both exist, a snapshot lets you return to a state without logging in again. The grain is the site, not the browser: two sites in one container are two unrelated records.
Which is why a snapshot must belong to a container — pouring A’s login state into B merges two accounts into one. It is equally important to say what it does not do: it restores application-layer session state, not a kernel-level fingerprint, and it does not promise to defeat a platform’s risk controls or device checks. Usage: Internal link: how site snapshots are used in practice.
How is this different from running several profiles?
Chrome’s multiple profiles are a physical cut at the storage-root level: each profile gets its own cookie store, storage, extensions and cache, so isolation is complete. The cost lands on four dimensions — memory, since each profile has its own network context and renderer processes, so three profiles is roughly three sets of resident cost; processes, since the process trees are separate; windows, one per profile, so switching means switching windows; fingerprint, since profiles share one kernel and one machine, so UA, Canvas and fonts are identical by default. There is a quieter cost too: bookmarks, extensions and history are not shared, so past three profiles they start to drift apart.
The extension route inverts this: one profile, one window, shared processes and kernel, in exchange for single-window parallelism at near-zero incremental cost — and the price is that it cannot reach the kernel fingerprint layer. They are not two grades of the same thing; they are two different cuts at isolation granularity. Side by side: Internal link: Chrome profiles vs. extension isolation.
Capability boundary: which layers an extension covers, and which it does not
The boundaries above, compressed into one table. Read it by asking what the platform checks: for session and egress, look at the middle column; for fingerprint consistency, look at the right one. It bears repeating: an extension does session-layer isolation, which is not the same grade of isolation as an antidetect browser — treating it as a lighter antidetect browser misjudges the risk.
| Layer | An extension can cover | Out of reach, or the price |
|---|---|---|
| Cookies and login state | Split per container, serialisable into a snapshot | The kernel does not know containers; headers are rewritten, and losing the rules means bleeding |
| Page storage | localStorage and IndexedDB can be prefixed or proxied per container | Service worker, SharedWorker and other separate contexts need their own coverage |
| Egress IP | Bound to the workspace; switching the foreground switches egress | One foreground egress at a time; no concurrent tunnels |
| WebRTC | Non-proxied UDP can be dropped (browser-wide policy) | Not per workspace; media paths can still bypass the proxy |
| TLS fingerprint | Nothing | JA3/JA4 come from the kernel and the network stack |
| Canvas / WebGL / Audio | Nothing | Rendering happens in the kernel; no extension API to rewrite it |
| Font enumeration | Nothing | Only page-level JS patches, which are detectable |
| HTTP cache | Nothing | The cache is profile-wide and shared across containers |
The table also shows where the ceiling is: what an extension replaces is the motive to buy a client just to log in separately, not the client. Which route to take is a separate question, compared point by point in Internal link: extension vs. antidetect browser.
Frequently asked questions
Is the cookie isolation an extension does the same thing as Chrome’s Partitioned Cookies?
No. Partitioned Cookies are keyed by top-level site, so one site gets one jar per top-level page; partitionKey in the cookies API is only the key for reading and writing those, and its sole component is topLevelSite — you cannot invent new partitions with it. A workspace jar is split by container, so one site can have any number of them, and the extension keeps the books. The first is browser privacy partitioning; the second is an extension-level session abstraction.
Does workspace state survive closing the tab?
Three cases. The tab is gone, so the binding goes with it. Workspace definitions — name, proxy, snapshot index — are the kind of data that has to survive a restart and normally land in storage.local. Login state is recoverable only if you saved a snapshot; otherwise you log in again. Anything in storage.session is cleared when the browser closes, so do not treat it as durable.
Does the single-window approach cost performance?
The increment comes from the tabs, not the isolation. No second browser is started and no kernel is copied; the overhead is rule matching and header rewriting, a constant amount of work per request. The real cost is the number of tabs and the pages themselves: with twenty workspaces, what stutters is rendering, not the isolation layer.
Can two workspaces use different egress IPs at the same time?
No. The proxy configuration is profile-wide, and an extension can only rewrite it when the tab changes, so at any moment only the foreground workspace uses its own egress; background workspaces are either blocked by rules or go direct. Genuine concurrent tunnels require a client with its own network stack.
Sources and last update
Where this article touches API behaviour, the official documentation governs. Extension capability boundaries move with Chrome versions — re-check before you commit.
- Chrome for Developers, chrome.declarativeNetRequest — session-rule lifetime, where condition.tabIds applies, and modifyHeaders on the cookie request header: developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest
- Chrome for Developers, chrome.storage — the session area lives in memory, is cleared on browser restart, and is not exposed to content scripts by default: developer.chrome.com/docs/extensions/reference/api/storage
- Chrome for Developers, chrome.cookies — partitionKey and the keying semantics of Partitioned Cookies (CHIPS): developer.chrome.com/docs/extensions/reference/api/cookies
- Chrome for Developers, chrome.proxy — proxy settings apply to the whole browser profile, and PAC only receives the URL and host: developer.chrome.com/docs/extensions/reference/api/proxy
- Chrome for Developers, The extension service worker lifecycle — termination after 30 seconds idle, and globals lost with the worker: developer.chrome.com/…/service-workers/lifecycle
Last updated: 11 November 2026. This describes the general implementation path for this class of extension; which APIs and storage locations a given product uses can differ, and its own documentation governs its internals.