@volter/almostcdp
v0.1.33
Published
A useful Chrome DevTools Protocol subset over an existing live DOM surface
Readme
AlmostCDP
AlmostCDP is a browser-side implementation of a useful Chrome DevTools Protocol subset over an existing live DOM surface.
It lets standard CDP clients connect to a page that is already running in a browser, webview, preview frame, or simulated browser. The client still speaks CDP. The surface adapter supplies DOM, navigation, input, capture, network, and console primitives available in that environment.
AlmostCDP is a protocol product with one viewer for any CDP endpoint
(@volter/almostcdp/viewer). It does not launch Chrome, retain browser
profiles, record sessions, or define an agent action vocabulary.
Intended clients
- Playwright through
chromium.connectOverCDP() - Puppeteer
- Chrome DevTools
- CDP-based agent and inspection tools
@volter/almostcdp/viewer, or another CDP viewer chosen by the embedding product
Intended surfaces
- a same-realm document
- an iframe reached through a host bridge
- Browser Substrate's preview inspector
- browser and webview hosts that can implement the adapter contract
The project reports support method by method. Unsupported commands return CDP errors; they never succeed as no-ops. Compatibility is measured against pinned client versions and published in the repository.
Download policy needs a host download adapter: pass
downloadHost: { write(contextId, path, bytes) } to the server or dispatcher.
contextId is undefined for the default context and path is
<downloadPath>/<guid> (allowAndName) or <downloadPath>/<suggested
filename> (allow), with downloadPath as the client sent it; resolving that
path is the host's job. With it, Browser.setDownloadBehavior accepts every
policy, and anchor downloads (a[download], including blob:/data: hrefs)
are intercepted in the page, fetched there and written through the adapter.
A document that arrives as an attachment is the client's download too when the
embedding carries documents: the network door's responded answers
{ download: true }, the embedding keeps the frame where it was, and the body
it passes to finished is written through the adapter. Otherwise the host
browser downloads it itself.
Without the adapter the command refuses every policy, including deny, and
pinned Playwright 1.63 must attach with { noDefaults: true }.
Replacing a capture stream suppresses images still pending from the previous stream; stopping that previous stream cannot erase the replacement's pending acknowledgement. Capture remains adapter-provided, with reconstruction limits reported separately from protocol delivery.
See ARCHITECTURE.md for ownership boundaries and ROADMAP.md for the first compatibility target.
Current development build
The first executable slice is on the build/mvp branch. It provides:
- Chromium discovery routes and browser/page WebSocket endpoints
- target discovery and flattened sessions
Runtimeevaluation, handles, calls, properties, bindings, and release- DOM documents, queries, node resolution, box models, and content quads
- page/frame lifecycle and layout metrics
- Playwright locator click and fill through synthetic
Inputevents - styled-DOM screenshot and acknowledged screencast capture via
html-to-image - host authorization hooks and server-enforced view-only casting
- a transport-independent dispatcher exported from
/core - an rrweb event WebSocket with reverse
Input.*messages - explicit CDP errors for commands the surface cannot perform
An acceptance test opens an ordinary page in the existing normal Chrome containing a same-origin iframe,
registers that iframe as the surface, connects unmodified Playwright 1.63 with
chromium.connectOverCDP(url, { noDefaults: true }), and evaluates, clicks, fills, captures, receives
a screencast frame, consumes a cast event, and sends input back.
Embedding a DOM surface
The package is published to npm as @volter/almostcdp; this repository stays
private. Run npm run verify:package inside a World to pack and install
an isolated consumer, reinstall from its lock offline, import the core entry
points without optional peers, and check server discovery. Install rrweb
separately for @volter/almostcdp/rrweb, or html-to-image for
@volter/almostcdp/capture. The root entry does not import those adapters.
// Node host
import { createAlmostCdpServer } from "@volter/almostcdp/server";
const server = await createAlmostCdpServer();
console.log(server.httpUrl, server.surfaceWebSocketUrl);Pass surfaceWebSocketUrl across the embedding application's existing
configuration or frame bridge. In the page bundle:
import { connectDomSurface } from "@volter/almostcdp/dom";
import { createRrwebCast } from "@volter/almostcdp/rrweb";
import { createDomCapture } from "@volter/almostcdp/capture";
// Run this bundle in the document being recorded (including inside an iframe).
connectDomSurface({
endpoint: configuredSurfaceWebSocketUrl,
id: "preview",
cast: createRrwebCast(),
capture: createDomCapture(),
});Back in the Node host:
// Standard CDP:
await chromium.connectOverCDP(server.httpUrl, { noDefaults: true });
// rrweb events out; JSON CDP Input.* messages back:
server.castWebSocketUrl("preview");Two optional host hooks complete the page surface: close() answers
Page.close, and files.read(path) supplies the bytes for
DOM.setFileInputFiles. Without them those commands refuse by name. A server
whose clients name paths on its own disk passes the dispatcher a fileHost
instead: read(path) gives a file's bytes, name, type and time, and the
optional list(path) a directory's files by their relative paths, so a folder
upload arrives with each file's webkitRelativePath. The surface is given the
bytes alone.
An embedding that carries the page's own requests through the surface's
network door (a service worker's fetches, reported and routed) passes
carriedDocuments: true, and Page.navigate then answers as Chromium's does:
once the document's response or failure is known, with errorText for a
navigation that failed (net::ERR_ABORTED for a 204, the carried failure
otherwise). A relative URL is refused ("Cannot navigate to invalid URL").
A host that serves the page under a different URL than its clients use passes
urls: { toSurface, fromSurface }. toSurface(clientUrl) returns the frame's
real URL (or undefined to refuse) and is applied to every URL a client sends,
including Page.navigate before its same-origin check; the navigation adapter
then receives the real URL. fromSurface(realUrl) returns the client-visible
URL and is applied to every URL the surface reports (target, frame tree,
navigation and network events, history, console and exceptions). A URL the
mapping cannot relate should pass through unchanged. Page script keeps seeing
the frame's real location. Cookie domains are hosts, not URLs: optional
hostToSurface/hostFromSurface map them, and without them the host is mapped
as http://<host>/.
The server and DOM agent normally live on opposite sides of a process or frame boundary, so an embedding application passes the surface endpoint into its page bundle. The bare server binds to loopback by default. A host that publishes it remotely must add authentication around both CDP and cast WebSockets.
An embedding can instead pass transport to connectDomSurface, omitting
endpoint. MessagePortTransport from @volter/almostcdp/message-port wraps an
owned, transferred port and works with both the DOM agent and core dispatcher.
This lets a sandbox carry protocol messages through its existing Host bridge
without opening a network connection from the preview. The embedding owns port
transfer, origin validation, and closure; unexpected peer disappearance still
requires host lifecycle notification. The acceptance harness exercises the same
public Playwright/CDP behavior over direct WebSocket and transferred MessagePort
paths. Browser Substrate integration itself remains W52 work.
A host with no Node server uses createSocketEndpoint from
@volter/almostcdp/socket instead. It runs the same dispatcher and serves
Chromium's HTTP discovery and DevTools WebSockets over any accepted byte socket
({ onData, onClose, write, close }), so a listener inside the tab publishes
the endpoint an unmodified client reaches at http://<address>.
attachSurface(peer) registers a surface transport, such as a
MessagePortTransport, without a surface WebSocket. A host that received the
transport from its own platform, and so knows which target it belongs to,
passes attachSurface(peer, { id }): the surface is refused unless it
announces that id, and for a successor document the host's word replaces the
succession token, so a page cannot claim another tab's target by presenting its
id and token. With requireExpect: true in the endpoint's options, every top-level
surface must be one its host named this way. A subsurface (a dedicated worker a
page runs) is always named by the dispatcher, <parent id>.<n>, and is always a
worker target, whatever its agent announces. A successor document resumes
with only the init scripts and bindings clients registered on the target through
their own commands (Page.addScriptToEvaluateOnNewDocument, Runtime.addBinding),
never ones the previous page reported; a host that also vouches for each
document's origin, attachSurface(peer, { id, origin }), has the surface modules'
state (emulation, permissions, interception) dropped when the origin changes, so
no page can seed another origin's document. connectDomSurface({ automation:
false }) leaves navigator.webdriver as the browser reports it (by default a
driven page reports true, as Chromium launched for automation does). address is the
host:port advertised in discovery; authorize sees each request's method,
path and headers.
An agent can also run inside the document it controls, as a browser extension
instruments every document of a tab. Such a document is replaced on navigation,
agent and all. With succession: { token } (at least 16 characters, held by the
embedding host) each document's agent connects with the same id and token:
connectDomSurface({ endpoint, id: tabId, succession: { token: tabToken } });The first hello binds the token to the target. When a document goes, the server
suspends the target instead of closing it: sessions and client sockets remain,
commands the old document never answered fail as Chromium fails them on
navigation, and new commands wait. The next document's hello with the same id
and token resumes the target; its agent restores the state clients set that
outlives a document (enabled domains, init scripts, bindings, isolated world,
emulation, permission grants, request interception, published by each agent as
it changes) and announces itself as a navigation, with the loaderId a client's
Page.navigate was promised. A host that calls preloadSuccession() as soon as
a document's script executes (before connectDomSurface) runs the client's init
scripts there, without waiting for the resume, when the predecessor was of the
same origin: the predecessor hands them over in the tab's session storage, and
binding calls they make wait for the resume, which announces their context.
Without a successor within successionGraceMs
(default 15 s) the target is destroyed. Cross-origin Page.navigate is allowed
for these surfaces. The token authorizes a successor, not a client: a page that
can read its agent's options can claim its own target, so publish the endpoint
only under the host's access policy. test/succession.test.mjs drives one
normal-Chrome tab with unmodified Playwright across two origins, by goto and
by a link click in the tab; its receipt is test/results/acceptance-succession.json.
The optional navigation adapter supplies navigate(url) and reload() so an
embedding can retain its routing checks. DOM surfaces without succession reject
cross-origin navigation before invoking that adapter, including non-HTTP opaque destinations;
inherited-origin srcdoc frames retain their embedding origin. Protocol control
still grants JavaScript execution in the live page; these checks do not replace
the embedding's sandbox and network policy.
Watching and taking over: the viewer
mountViewer(element, { endpoint, … }) from @volter/almostcdp/viewer fills
an element with a live view of one page and returns { state, takeOver(),
release(), close() }. It works against any CDP endpoint: real Chrome, a
Volter Browsers publication, or AlmostCDP. It is one ES module with no runtime
dependency; rrweb's Replayer is imported only in DOM-cast mode.
endpoint: an HTTP discovery URL (the/json/listbase, such as Volter Browsers'cdpUrl), a page or browser WebSocket URL,{ url, headers, target }, or an already-connected CDP session (anything withsend(method, params)andon(event, listener), as Playwright's and Puppeteer's are).headersgo to discovery. A browser WebSocket cannot send headers: a query string on the discovery URL (Volter Browsers'?token=) is carried to the socket, and asocketfactory can open it with headers where the platform allows.- Pixel mode (always available):
Page.startScreencastframes drawn to a canvas, each acknowledged after it is drawn, sized to the element and restarted when it is resized. Use it for canvas, WebGL and video pages. - DOM-cast mode: when the endpoint is AlmostCDP (
Browser.getVersionanswers revision@almostcdp, or refuses on a direct page socket) and its/casts/<target>socket answers withincastTimeoutMs, the rrweb cast is replayed instead: crisp at any scale and lighter than frames.castnames the cast socket explicitly or disables it;mode: 'pixel' | 'dom'forces one. - Input: the viewer starts watching. A badge offers Take over; while taken
over the view is outlined and the badge reads "You are controlling this
browser" with Release. Input goes through
bindCastInputto CDPInput.*(mouse, wheel, keys, text and IME, touch), mapped from the displayed rectangle to the page's viewport.control: falsemakes a watch-only viewer;takeOver: truestarts in control. controls: trueadds back, forward, reload and an address field throughPage.*; the address field accepts onlyhttp(s)URLs.- The viewer never records. A dropped socket it opened is reopened with backoff; a session it was given stays its caller's.
The minified module, with cast-input and cast-client, is about 17 KB (7 KB gzipped) without rrweb.
Playwright in the page
@volter/almostcdp/playwright is unmodified playwright-core 1.63.0, client
and CDP half in-process, bundled for a browser document (3.8 MB minified,
about 970 KB gzipped). It drives an AlmostCDP endpoint with no Node process:
import { connectPlaywright, evalPlaywright, pageFor } from "@volter/almostcdp/playwright";
const browser = await connectPlaywright(port); // a MessagePort, a transport, or a createSocketEndpoint() endpoint
await evalPlaywright("async (page, args) => { await page.getByLabel('Name').fill(args.name); return page.title(); }", { name: "Ada" });
const page = await pageFor(browser, "tab-7"); // the page whose surface id is tab-7connectPlaywright attaches with noDefaults and writes nothing to disk;
the browser it returns is evalPlaywright's default page source unless a
{ page } is passed. evalPlaywright compiles its source with eval, so it
runs in a realm that allows it: a plain page, or a browser extension's
sandboxed page (Manifest V3 forbids eval in content scripts and ordinary
extension pages). The surface a script drives answers Runtime.evaluate in
the page's main world, under the page's own Content-Security-Policy.
The bundle also exports Playwright's own chromium, errors, devices,
selectors and request. A bundler that aliases playwright-core to
@volter/almostcdp/playwright runs code written against Playwright on this
one instance. Node builtins are replaced at build time
(scripts/bundle-playwright.mjs): events, buffer and path-browserify,
small shims for os, util, crypto, async_hooks and process, and
no-op modules for the builtins Playwright loads but never calls over a
connectOverCDP transport.
playwrightMcpTools() and createPlaywrightMcpBackend(context, tools) are
Playwright's own MCP browser tools, the core set @playwright/mcp serves,
with the schemas it publishes: a host that speaks MCP answers tools/call
with the backend over a context this realm drives, serving the subset of
tools it chooses. There is no file system: actions answer without the page
snapshot @playwright/mcp would save to a file (browser_snapshot answers it
inline), and nothing a tool would save is written.
Verification
The acceptance harness uses the existing normal-Chrome eval server through
ALMOSTCDP_BROWSER_EVAL_URL; it never launches a separate browser. Run tests
inside a World. test/world/config.json selects no vendors because these
fixtures use only local HTTP and WebSockets. Supply an empty temporary live-env
file to the World operator; generated env files are ignored.
For a real rrweb/capture proof, build and run node test/browser/serve.mjs
inside the World, register its printed app URL, and open it in normal Chrome.
Then run node test/browser/drive.mjs <printed-cdp-url> in the same World.
The driver checks actual decoded pixels, acknowledgement pacing, late-join
snapshots, masked input, and reverse input. The page displays an upstream rrweb
replayer beside the source; it is a test consumer. The product viewer is
@volter/almostcdp/viewer (above).
For the embedded Chromium DevTools fixture, run
node test/browser/install-devtools.mjs inside the World, then set
ALMOSTCDP_DEVTOOLS_ROOT to the printed directory when starting the fixture.
The installer verifies the selected upstream archive and its documented patch against test/browser/devtools-candidate.json; the historical unpatched snapshot remains in test/browser/devtools.json.
This older frontend is pinned for compatibility testing; a maintained production
frontend selection and distribution-notice review remain release work.
There is no Browserless service or dependency.
The rrweb consumer sets triggerFocus: false: replayed focus otherwise steals
native focus from an embedded DevTools canvas or another input controller.
On 2026-09-23, the pinned embedded frontend displayed live frames, delivered a
click to the source input, and typed devtools through its canvas; the source
value became private devtools, and the rrweb reconstruction remained masked.
This was a manual normal-Chrome check, recorded in
test/results/embedded-viewer.json; DevTools scroll and navigation remain
outstanding. A subsequent rrweb viewer check exercised overlay clicks, typing,
half-scale coordinates, privacy masking, and rendered reconnect; its receipt
is test/results/rrweb-viewer.json.
To repeat the rendered reconnect check, use the fixture's Drop cast and change
fixture button. It closes only the fixture's cast socket and adds ten to the
synthetic source count while disconnected. Inspect the replayed count and the
viewer body's data-cast-epoch and data-cast-state: a new epoch must reach
ready with the changed count and masked input. The fault route requires a
same-origin POST and exists only in the local test server.
RuntimeDomain from @volter/almostcdp/core owns remote values and object
handles independently of DOM access. Embedders supply realm evaluation,
invocation, descriptions, and property descriptors. The Node VM acceptance
test exercises this adapter through the public CDP endpoint; it is not yet a
complete second implementation of Page, DOM, or Input.
Remote object handles and object groups belong to one CDP session. Console objects receive separate handles for each subscribed session; disconnecting or detaching releases that session's handles and pending commands. Sessions still share the live page and its JavaScript realm. Frontend DOM node IDs belong to one session; backend node IDs identify the same node across sessions. Navigation invalidates both, and disabling DOM invalidates that session's frontend IDs. Execution-world isolation still needs work before full session conformance can be claimed.
Rejected or timed-out domain enable/disable commands restore event routing, including overlapping commands whose responses arrive out of order. When a surface disconnects, its direct page sockets close; an old connection cannot silently start controlling a replacement surface that reuses the target ID.
A failed or timed-out screencast start releases its ownership and requests
surface cleanup. Screencast frame events must carry the internal audience from
the start command's sessionId; the dispatcher strips it from direct-client
events and rejects frames from a previous owner. DOM capture callbacks also
carry a generation check so stopped or superseded work cannot emit late frames.
For an embedded viewer, AlmostCDP retains the target's logical input focus when the viewer takes native browser focus to collect keys. Input remains synthetic; this does not grant trusted events or browser-level keyboard privileges.
The rrweb recorder must run in the document it records. Input values are masked by default; consumers can explicitly override rrweb recording options. New cast connections request a fresh snapshot. Cast data is not retained on the server; slow consumers are disconnected once queued output exceeds 8 MiB.
bindCastInput from @volter/almostcdp/cast-input binds a viewer-owned overlay
to the cast input wire. Supply a sender (method, params) and a viewport callback
with the displayed rectangle and original source dimensions. It maps mouse, wheel and
touch coordinates through display scaling and forwards keyboard and text input.
Keys carry their legacy key code, as Chromium's editing keys need; Enter carries
\r; a soft keyboard's deletions become Backspace and Delete; the paste
shortcut is left to the overlay's paste event, which carries the viewer's
clipboard.
The returned function removes listeners and restores the overlay's editable and
tab-index attributes. test/browser/viewer.ts uses this with the rrweb replayer.
Scaled click and typing are covered through the public cast endpoint. Dragging
beyond the viewport and full viewer IME behavior remain acceptance work.
connectCast from @volter/almostcdp/cast-client manages the viewer connection.
It calls onReset on each new connection so the consumer can discard the old
replayer, then waits for rrweb metadata and a full snapshot before enabling
sendInput. Early incremental events are dropped; offline input is rejected
without queueing. Retries use bounded exponential backoff. Policy failures stop
retrying, and missing targets are explicitly transient. The rrweb fixture uses
this client; the endpoint test proves recovery after surface replacement. Full
rendered-replayer recovery still needs browser acceptance.
createAlmostCdpServer({ authorize(request) { ... } }) accepts a host callback
returning "control", "view", or false. It gates discovery and WebSocket
upgrades. View-only sockets can receive casts but cannot send input or open CDP
control sockets. Hosts own credential storage and origin policy.
connectDomSurface({ inputPolicy({ method, element }) { ... } }) is the
embedding's policy for what a control client's input may act on. It is asked
before a pointer press, a wheel turn, a touch start, a key down or text is
dispatched, and before a file input is given files, with the element the input
lands on (the one under the point, or the focused one for keys and text); and
before Page.navigate, Page.reload or Page.navigateToHistoryEntry, with no
element and the address when the command names one. A returned reason refuses
the command with that message and nothing is dispatched. An extension
that lends a person's own tab uses it to keep password fields and controls that
pay, send or delete out of a client's reach.
connectDomSurface({ dialogs: "person" }) leaves the page's alert, confirm and
prompt to the person while a client observes the page: the browser shows each one,
the page gets the person's answer, and the client hears Page.javascriptDialogOpening
(with hasBrowserHandler: true) and Page.javascriptDialogClosed with that answer;
Page.handleJavaScriptDialog is refused. By default ("client") a surface dismisses
them at once and reports them.
Chromium debugging-pipe bridge (in progress)
The private package installs almostcdp-chromium. Set ALMOSTCDP_ENDPOINT to
an existing AlmostCDP HTTP discovery base or browser WebSocket URL; optionally
set ALMOSTCDP_TOKEN for bearer authentication. The host must already be running.
The executable accepts Chromium's --remote-debugging-pipe, reads NUL-delimited
JSON on descriptor 3, and writes responses/events on descriptor 4. It preserves
protocol messages and errors. Discovery redirects and foreign-origin sockets
are refused. Messages and queued upstream traffic are bounded to 16 MiB.
Pinned Playwright can launch this transport using executablePath pointing to
node_modules/.bin/almostcdp-chromium. No Chrome is launched and no profile is
created by the executable. Other Chromium launch flags do not configure the
existing host: profile, network, rendering, and isolation remain host-owned.
Browser.close ends only this relay and its CDP connection; it leaves the host
and existing surfaces running. Pipe EOF and process termination also detach.
This is a transport proof, not yet support for unchanged page tests.
browser.newContext() still rejects Target.createBrowserContext; creating
pages, enforcing downloads and applying launch options need host lifecycle
adapters. test/pipe.test.mjs exercises
the public endpoint with the pinned unmodified Playwright launcher and records
that explicit limitation.
For the outstanding DevTools navigation proof, the same fixture server serves
/devtools-proof. Its host keeps the AlmostCDP connection alive while the
600×350 source iframe navigates between /navigation-first and
/navigation-second; capture reconstructs that iframe's current document.
Open the embedded frontend with Open upstream DevTools. This fixture is
separate from the rrweb proof. The manual checks in
test/results/devtools-navigation.json passed canvas click, keyboard input,
wheel scrolling, address-bar navigation, and canvas clicking on the new document
against revision 825e079. The host retained its protocol connection across
navigation. This remains a manual proof on the pinned historical frontend;
automatic reruns and a maintained production frontend are still outstanding.
The receipt also preserves earlier intermittent controller timeouts. Thirty
capture probes completed in 32–179 ms while the source remained responsive,
and timeouts also occurred without a screencast; capture cost was not established
as the cause. capture-probe.js exports measureCapture(targetWindow) for a
single measurement without starting a stream. No browser restart or source
reload was used to resolve those observations.
Current DevTools candidate
The selected fixture is recorded in test/browser/devtools-candidate.json, which
pins Chromium snapshot 1703215, its exact DevTools source revision, and SHA-256
digests for both upstream archives. Install it inside the World with:
node test/browser/install-devtools.mjs --pin test/browser/devtools-candidate.jsonFor offline installation, pass the frontend ZIP as the positional argument and
--source-archive <source.zip>. Both archives must match their pinned digests.
The installer retains the source archive's license, notice, copyright and
README.chromium files in the cache's notices/ directory, with an inventory
in artifact.json. Redistributing the frontend must include these notices;
the printed frontend directory alone is not a complete distribution.
The unpatched native failure is preserved in
test/results/devtools-current-native.json. DevTools supplied the DOM keycode
as a platform-native keycode; the original r keydown stalled isolated macOS
Chrome. The selected artifact omits that field, as Playwright does. The pin
records the exact one-line patch and hashes before/after; a patch-specific cache
key keeps it separate from the pristine archive. No Volter Browsers protocol rewriting
is involved.
The patched native and AlmostCDP walks passed click, full rapid typing, wheel,
address-bar navigation and post-navigation input through authenticated Volter Browsers
publication. Native Chrome uses the consumer's explicit Activate target button
when hidden. The receipt is test/results/devtools-patched-viewer.json.
Automatic viewer reruns remain open; this is a manual compatibility proof.
Host-owned browser contexts
An embedding host can supply contextHost to createAlmostCdpServer or
createDispatcher. Its createContext(options, signal) must create an isolated
storage/network realm using the supplied ID, honor the requested proxy/origin
options or reject them, and observe cancellation. disposeContext(id) must
close that realm's pages and release storage; it must tolerate partial creation.
AlmostCDP maps these hooks to Target.createBrowserContext,
Target.disposeBrowserContext, and Target.getBrowserContexts. Without a host,
creation/disposal remain explicit unsupported errors.
A pageHost adds createPage({ id, url, browserContextId? }, signal) and
closePage(id). Creation must register a live surface with that ID before
resolving (the public discovery endpoint is a registration barrier). AlmostCDP
then returns Target.createTarget success and reports the assigned context in
target events. Closing a managed page releases it through the host and detaches
its sessions. Unmanaged live surfaces are not owned by this lifecycle API.
Unsupported launch options fail rather than being silently ignored.
This is not yet a full Playwright browser: download policy and a concrete isolation/page adapter with real-browser proofs remain required. The acceptance tests for this seam use an in-memory allocation host and do not certify storage isolation.
