- Guides
- Security & isolation
Security & isolation
What is redacted before upload, how tenants are kept apart, how media links expire, and what an AI agent can and cannot reach.
You are about to record your users’ sessions — network requests with bodies, console output, clicks — and hand the recordings to a third party. The two questions that matter are what leaves the page and who can ever see it. This page answers both, and every claim on it is enforced in code, not policy.
Nothing uploads until someone reports
Section titled “Nothing uploads until someone reports”The SDK keeps a capped, in-memory ring buffer and sends it only when
report() is called — by your user, your code, or an
auto-report trigger you enabled. There is no
background trickle. data-mode="always" exists for the teams that want every
session, but it is opt-in, never the default.
Redacted at the source, not after arrival
Section titled “Redacted at the source, not after arrival”Redaction runs inside the browser, before anything is uploaded — so the sensitive value never crosses the wire at all, rather than being scrubbed server-side after we already received it:
- Headers:
Authorization,Cookie,Set-Cookieand friends are replaced with[redacted], and any header whose name containssecretortokengets the same treatment even if it is not on the exact list. - URLs: query parameters that carry credentials (
token,key,secret, …) are redacted inside recorded URLs. - JSON bodies: fields like
password,access_tokenandrefresh_tokenare redacted inside request and response bodies — logging in while being recorded must not turn the recording into a credential.
The exact rules live in @espejo/core’s redaction module and run for every
capture path — network, console and navigation alike.
One tenant can never see another
Section titled “One tenant can never see another”Every recording belongs to a project, and every project to exactly one tenant (your account). The walls between tenants are structural:
- Every query filters through the tenant relation. There is no code path that looks up a session by id alone: the tenant scope is part of the query itself, never a list of ids that could go stale.
- A miss is indistinguishable from a non-existent recording. Asking for
another tenant’s session returns the same
404as asking for one that was never created — in the console API and in the MCP tools alike. No403ever confirms that an id exists. - Support access does not impersonate. The internal operations key is refused wherever access is granted on a person’s behalf — it cannot be used to walk into a tenant’s account.
Media links are signed and short-lived
Section titled “Media links are signed and short-lived”Recording artifacts (events, DOM replay, video) are never served from guessable URLs. When a viewer — or an AI agent over MCP — asks for one, the API mints a link signed with HMAC that expires after 10 minutes. A leaked link goes stale before it goes far, and the signature covers the session, the artifact and the expiry, so none of them can be swapped.
What an AI agent can reach over MCP
Section titled “What an AI agent can reach over MCP”The MCP server uses OAuth 2.1 with PKCE, and the scope of a connection is fixed once, at the moment you approve it — from the account you were signed into, never from anything the agent sends later. All MCP tools are read-only, and every one of them goes through the same tenant-scoped queries as the console. Revoking the connection cuts access on the next call.
Retention is enforced, not promised
Section titled “Retention is enforced, not promised”Recordings are deleted automatically when they age past your project’s retention window or when the count/minutes caps evict the oldest — the sweep runs continuously on the server, and deleting a session cascades to every stored artifact. What the plan says is what the disk does.