--- title: "Use authenticated browser sessions" description: "How a Worker acts inside a logged-in session, what Obol stores, what it honestly does not promise, and what is not wired yet." --- Some jobs only exist behind a login. A session profile is Obol's answer: opt-in browser or device state for **one** customer-owned account, sealed in the vault, mounted into an isolated environment for a run, and revocable. **The routes exist; the door they open does not, yet.** Since 2026-09-10 there are four shipped routes — capture, list, read and revoke — under `/workspaces/{ws}/worker-session-profiles`. Capture is not a login flow of its own: it seals state from an environment you are already holding through a live control lease on a live run. As of 2026-09-10 a run does not complete end to end ([Overview](/workers/overview)), so there is no live run to hold, and capture is unreachable in practice even though the route answers. Read this page as the model, and read the list and revoke routes as usable the moment there is something to list. ## The honest part first An authenticated browser must possess usable session state inside its environment. Obol does **not** claim these sessions never enter the sandbox: the contract requires `plaintext_present_in_environment: true`, as a required field with a fixed value, precisely so that no surface can quietly imply otherwise. That is a *different* boundary from the one that governs model and vendor API credentials. Those keep gateway-only plaintext handling and reach no environment and no log. A session profile is the narrow, reviewed exception, and it is scoped to a single named account. ## What is stored, and what cannot be The stored state lives sealed behind `sealed_state_ref`. The contract carries metadata only, and it has no field that could hold a password, a cookie value, a token, or a bearer string. There is no export of the profile bytes. The only supported way to acquire one is `user_assisted_login`: a person authenticates in a private session and the resulting state is sealed. A password is never placed in a model prompt, and no Worker instruction should contain one — control screens free text for recognisable credential material and refuses the request rather than saving it. ## The rules that follow from calling it a secret | Rule | Field | |---|---| | One workspace, one named customer account | `account_ownership`, `isolation.shared_across_workspaces: false` | | Never pooled across runs | `isolation.shared_across_runs`, `concurrent_mounts_allowed` | | A code environment can never mount one | `code_environment_access_allowed: false` | | Short retention, with idle expiry | `retention.max_retention_days`, `idle_expiry_days` | | Revocable, and a revoked profile fails a run rather than being ignored | `revocation.state` | | Recordings are masked | `recording` | | Scoped to named domains | `allowed_domains` | The structural refusal is worth stating plainly: a code environment cannot mount a session profile at all. That is enforced in the data model, not by convention, so a Worker that asked for one on a `code` environment would be refused before it ran. ## The routes, and what they do not certify | Route | What it does | |---|---| | `POST …/worker-session-profiles` | seals one user-assisted login into a profile; requires `change.apply` and a live control lease on a live run held by you | | `GET …/worker-session-profiles` | metadata only — id, class, named account, expiry, revocation state, which revisions reference it | | `GET …/worker-session-profiles/{id}` | one profile, the route you watch a fence land on | | `POST …/worker-session-profiles/{id}/revoke` | destroys the sealed address and fences every lease holding the profile | Two properties are worth knowing before you build against them. **No session state crosses this boundary.** `sealed_state_ref` is derived from the workspace and the profile id and is never accepted from a caller, so the capture body has no field a byte could travel in. Control records consent, custody metadata and the door check. It does not read the state back, and it does not certify the bytes. **Revocation is a fence, not a flag.** Read the response's `state`, not its HTTP status. `revoked` means control observed every holder stop. `revoking` means the record is unallocatable, the address is destroyed and the fence has been applied — and at least one lease has not yet been observed to stop, because a bumped fencing token is not evidence that a holder halted. Calling revoke again is safe, and it is also how you ask whether it has landed. Four things ADR-0098 asks for are not here yet, and none of them is implied by the routes answering: a control lease's `purpose` is not persisted, so capture verifies the door is open but not the sign on it; allocation does not re-read revocation state at the moment a lease is created; no scheduled sweep expires stale profiles, so expiry is enforced by the Doctor and on read; and no revocation signal reaches orchestration, which is what would turn `revoking` into `revoked` promptly rather than eventually. ## What the Doctor tells you `GET /workers-doctor?worker_id=…` checks every profile a Worker's revision references, and an unhealthy one is a `fail` and never a `warn`: ```json { "check": "session_profile", "subject": { "kind": "session_profile", "id": "ssp_acme_github_qa", "label": "GitHub QA account" }, "status": "ok", "message": "The saved browser session for the staging storefront is still valid.", "fix_action": null } ``` A revoked or expired profile is a failure because a run that mounted one would be driving an authenticated browser whose session Obol has already said it destroyed. ## Signing in as part of a run Where a login has to happen inside a live run — a step nobody could pre-seed — the mechanism is a control lease with `purpose: "assisted_login"`. A person takes the environment for a bounded window and authenticates themselves. That is the same door described in [Take over a browser](/workers/take-over-a-browser), and it is gated on `CHANGE_APPLY` for exactly this reason: it is the point at which a human types a password into an environment that then holds an authenticated session. ## What a run inside a session proves Less than it looks like. Being logged in changes what a Worker can reach; it changes nothing about what an observation is worth. A screenshot taken inside an authenticated session is an observation of a screen. What a receipt may claim about a call is decided by the route the call took, and the receipt names the class — see [Evidence](/receipts/evidence).