Open Environment → Browser. Tabs remain alive when another Environment tab is selected or the panel closes. The compact tab strip and navigation row follow the supplied desktop browser reference and use Aiden's appearance tokens.
The browser is adapted from the local T3 Code reference. Its selector runtime is Playwright's injected runtime; notices are in THIRD_PARTY_NOTICES.md and main/services/browser/PLAYWRIGHT-LICENSE.
| Surface | Behavior |
|---|---|
| Navigation | Tabs, address field, back/forward, reload/stop, hard reload, loading/failure state, favicon, mute, ten recent URLs per workspace, and detected terminal dev-server URLs. Recents and open tabs are kept in memory for the app session. |
| Viewport | Fill, editable dimensions, the 17 reference device presets, rotation, ratio lock, drag handles, zoom, and system/light/dark page appearance. Presets change layout, not the browser user agent. Guest pages use a Chromium user agent with Electron and Aiden product tokens stripped and the Chrome version reduced to its major (Chrome/150.0.0.0), as shipping Chrome reports it. Google's sign-in host rejects embedded Chromium even so, so requests to https://accounts.google.com alone carry a current Firefox user agent and no Client Hints, the approach other Electron browsers use; every other host, including other Google hosts, keeps the Chromium identity. Google's check runs server-side and is confirmed only by signing in on a real Mac, not by CI. Electron sends no UA Client Hint headers itself, so requests to secure and loopback origins carry only the default sec-ch-ua, sec-ch-ua-mobile, and sec-ch-ua-platform hints, derived from the same Chromium brand list the page sees in navigator.userAgentData; no "Google Chrome" brand or high-entropy hint is forged. |
| Capture | Screenshot copy/save, recording start/stop with a local H.264 MP4 artifact (WebM when H.264 encoding is unavailable) that the chat shows as a playable card with Show in Finder and that paired phones can play (Remote revision 46), a draggable/resizable browser over chat, and a separate picture-in-picture window. Native page views stay on screen when chat popovers, hover cards, or agent-step details do not cover the browser slot; full-window dialog overlays still hide the guest so it cannot intercept clicks, including when the modal's accessibility isolation marks its own overlay aria-hidden. Linux keeps a hidden tab in its window as an invisible view; macOS takes it out, and puts it back the same invisible way while an agent action or capture runs on it, so a page it loaded while hidden can still be captured and clicked. |
| Profiles | Default, memory-only Incognito, and named persistent profiles. Profile changes apply to new tabs. Cookies/cache can be cleared separately. A Bot's own browser profile is never listed here and no workspace tab can use it (see A Bot's own browser). |
| Cookie import | Explicit one-time import from supported locally discovered browsers. Quit the source browser first. Import copies cookies only, skips unsupported partitioned/encrypted records, and reports partial results. Safari requires the OS file permission. |
| Links/files | Chat and terminal web links follow Browser settings; Cmd/Ctrl-click opens the system browser. Links to local files in a chat reply (workspace-relative paths, absolute paths, file:// URLs, and name.ext:12 file-and-line forms) open in place: HTML and PDF preview in the Aiden browser, other files open in the Files editor, and folders expand in the Files tree. A file outside the workspace shows a notice instead. Right-clicking a file link offers Open in Aiden browser (HTML/PDF) or Open in Files, Open in system browser for workspace HTML (Open in default app for PDF), and Copy path. Files offers Open in Browser for HTML/PDF. A scoped loopback server serves approved workspace documents and web assets without enabling unrestricted file:// navigation. |
Browser settings choose new-tab defaults, where links open, global Aiden browser access, and whether automation reveals it. Each workspace can inherit, enable, or disable browser access separately. Incognito is never the persistent default. Websites cannot read the OS clipboard, request geolocation, or display notifications. Only sanitized clipboard writes from an owned, same-origin main frame can receive automatic permission; third-party frames and unknown permissions are denied.
Choose Annotate or press ⌘. while the browser is focused. Select an element on the live page, then add other elements, regions, drawings, a comment, or per-element style changes. Text, colors, borders, sizing, and spacing preview live; originals are restored when you cancel or add the annotation. Erase removes selected elements, regions, or drawings at a point. Available React development-source information is included when the page exposes it; annotation also works on non-React pages.
Add to chat or ⌘Enter appends the annotation to the existing draft. The context includes the URL, text, selectors, bounds, available source metadata, and the user's comment. A marked screenshot crop accompanies it when the current model supports images and attachment limits permit. Text context remains available for text-only models or capture failure. Page-derived content is labeled untrusted evidence. Nothing is sent until the user sends the draft. If the current composer is unavailable (for example during a questionnaire), the editor keeps the annotation for retry.
An eligible foreground desktop workspace agent initially receives only browser, a discovery tool. Calling it installs fourteen executable tools at the next model-turn boundary: browser_status, browser_open, browser_navigate, browser_resize, browser_set_appearance, browser_snapshot, browser_click, browser_type, browser_press, browser_scroll, browser_evaluate, browser_wait_for, browser_recording_start, and browser_recording_stop. The gateway returns their names and usage guidance; it does not duplicate their schemas in the transcript.
The initial added static context is 85 estimated tokens (337 serialized characters), versus the original 2,679. Once activated, the gateway, fourteen schemas and browser guidance add 3,032 estimated tokens, including the new local-file arguments. These numbers use Aiden's estimateStaticContextTokens characters/4 heuristic, not a provider tokenizer or billing measurement. Unrelated responses pay only the gateway cost. Effective disabled access removes even the gateway on the next generation. Host exclusions are respected before discovery.
Discovery state lasts for the current generation, including compaction and emergency continuation. A new generation starts with the gateway again. Pi receives the actual executable tool registry through its next-turn context update; the outbound transform and durable compaction estimator use the updated registry and prompt. Compaction summarizes conversation history and retains recent call/result pairs; it does not remove active tool schemas or close browser tabs. Outgoing context keeps the newest three image-bearing browser snapshots, independently of Computer Use's three-image pool. Older screenshots remain in the private journal, with text and error semantics preserved. Model-facing snapshot text is capped at 32,000 serialized characters, with intact selected locators, failure-prioritized diagnostics and explicit omission counts. Text-only models receive no screenshots.
These tools operate on the user's workspace tabs. Browser access authorizes navigation and page inspection in the selected profile, including background tabs when open=false or automatic reveal is disabled. Ask mode gates interaction/evaluation; it does not add a prompt for every navigation or read. Background tabs remain listed in Environment → Browser, and their calls remain visible in chat activity. Calls bind to the generation and workspace, serialize against the selected tab, and revalidate permissions and cancellation. Ask-mode interaction/evaluation approvals bind to the exact tab, page revision and arguments; navigation or human takeover invalidates them. A failed action invalidates actions already waiting in the same generation's queue. Later recovery can inspect a fresh snapshot. Keyboard expectations require the native Chromium debugger-source flag exposed by the pinned Electron build, plus matching keys/modifiers. Physical keyboard input, including identical or repeated keys, interrupts immediately; missing keyboard source flags fail closed. Electron omits that flag from mouse-down/up events, so a matching physical mouse event can consume a pending same-button expectation; a subsequent unmatched event interrupts. Mouse expectations are bounded and removed when dispatch settles. Electron upgrades must preserve the keyboard event contract or keyboard automation will stop safely. The private Assistant, headless generations and read-only subagents do not receive these desktop-only tools. A Bot never receives them either: it has its own browser, described next.
browser_open and browser_navigate accept a local HTML/PDF path (or a local file URL) through the managed preview service. Every preview serves only its entry document and a finite declared asset set, with a separate origin for each distinct grant. Agent opens require explicit assetPaths. Undeclared sibling files are blocked, including inside the workspace. Workspace entries and declared workspace assets use existing workspace authority. Direct user file opens discover static HTML, CSS and JavaScript-module dependencies inside the owning workspace, including relative and workspace-root-relative asset paths, without executing page code. Discovery is limited to 64 files, eight dependency levels, 1 MiB per parsed source and 4 MiB total parsed source. It never grants directory access or follows computed imports, dynamic fetches, external URLs, or unrelated documents. Literal module imports, including literal dynamic imports, remain statically discoverable. Parsed sources are pinned by identity, content, size and modification time. Reopen the document after editing or touching a parsed source, including a same-content save, so Aiden can rebuild the asset grant; existing previews deliberately fail closed with 409. Missing or out-of-scope references found during discovery remain blocked and appear in browser diagnostics. Discovery does not resolve import maps, new URL(..., import.meta.url) worker/asset references or scripts injected at runtime; those unrecognized patterns do not produce a discovery warning. Pages that need them should run through their own development server, or use explicit assetPaths for supported ordinary asset requests. Worker requests do not receive implicit preview authorization. Explicit assetPaths bypass discovery and retain the exact supplied set. An outside-workspace document requires explicit approval for that exact file and any declared assetPaths, including in Full access mode. The grant is a main-only object with pinned file identities; model-supplied flags cannot authorize access. All local files are served individually, with no directory listing or mount of all /tmp.
Managed previews bind to loopback on an OS-assigned port and use capability protection. Initial navigation uses a bootstrap token; subsequent declared-file requests use a main-injected header restricted to the owned workspace, exact destination origin, and committed requesting-frame origin. Preview cookies are not used, legacy preview cookies are stripped, and cross-origin redirects receive no internal header. Requests without an owned frame, including worker requests, receive no implicit authorization. Tabs, pending navigation and reads own their lifetime. Closing or navigating away from the final tab revokes access and drains existing requests for at most five seconds; hidden and floating tabs remain consumers. Failed opens, cancellation, owner/workspace disposal, changed root identity and shutdown release resources. No external server process or staging copy is created, so crashes leave no server child or staging files to recover. Original documents and user-owned development servers are preserved. Capability URLs and known tokens are redacted from textual tool results and diagnostics; screenshots remain visual page evidence.
A Bot whose access includes Browser (Full access includes it whenever agent browser access is on in Browser settings) gets its own browser, separate from the person's browsing.
| Surface | Behavior |
|---|---|
| Profile | Profile id bot:<Bot id> in the persistent Electron partition persist:aiden-bot-<Bot directory>. Its cookies, storage and cache never mix with the Default, Incognito or named profiles, it is not in the profile menu, and cookie import into it is refused. The Bot's managed workspace is admitted to the browser service only after main registers it; renderer input never registers one. Deleting the Bot closes its tabs and clears the partition's storage, cache and sign-ins as a delete step, so a delete that stops part-way keeps the Bot listed and clears them on retry or restart. |
| Where it shows | A Browser tab in the Bot panel, shown once the Bot's browser has a tab and kept while the person is on it after the last tab closes; a saved Browser tab shows Activity until there is a tab. When the Bot opens a tab to show, the panel opens on Browser without moving keyboard focus. The Bot variant of the browser panel has navigation, viewport, appearance and Open in system browser, but no profile menu, Annotate (or ⌘.), DevTools, screenshot capture, recording, floating, picture-in-picture, cookie or cache clearing, or Browser settings, and its empty state never lists the person's recent pages. Links in the Bot's chat messages never open in its browser; only a hand-off or sign-in card's Open does. Web links go to the system browser. Links to local files don't open, because they open only in a workspace's Environment browser or Files, which a Bot's page doesn't show; the Bot panel's Files tab doesn't take chat links. |
| Owner | The window that shows the Bot owns its browser: opening the Bot in another window moves it there. With no window shown, the focused or most recent main window owns it; with no window at all, the Bot's call fails with "Open Aiden on your Mac to let use its browser." |
| Tools | Only on attended turns of the durable Bot runtime, from the Mac or a phone (the browser itself runs on the Mac): never in routine runs, Telegram turns or the legacy generation path. A request first declares only the browser gateway; once the transcript holds its successful result, the next request also declares eleven tools: browser_status, browser_open, browser_navigate, browser_resize, browser_set_appearance, browser_snapshot, browser_click, browser_type, browser_press, browser_scroll and browser_wait_for. A Bot never gets browser_evaluate or recording, and browser_open/browser_navigate take web URLs only, without path or assetPaths. Main also refuses page scripts, recording, local files, annotation, screenshot capture and DevTools for any Bot command, and the preview server refuses Bot workspaces. As in workspace chats, each request carries the screenshots of only the three newest snapshots; an older one becomes a short note in that request, and the Bot's stored conversation keeps every screenshot. |
| Approvals | Navigation and reads don't ask. Every click, typing and key press asks the person on the Bot's approval card, including in Full access. The approval binds the exact tab, page revision, control revision and arguments, and is checked again right before the effect: a navigation, a takeover, another tab or changed arguments expire it. The Bot's rules see a probe of the targeted element from Aiden's isolated world (tag, type, autocomplete, role, accessible name, form action host), never field values or typed text (an accessible name that could include what a field holds, such as a checkbox label that wraps a card field, is left out), so a rule can ask or hand off, for example, before the key that sends a web message. The card leaves typed text out for password, code and card fields, and for fields that couldn't be read. |
| Credential boundary | Main masks every snapshot before it leaves main. Password, one-time code (one-time-code) and card (cc-*) fields lose their accessibility value and rendered text, and their boxes, read before and after capture, are painted gray in the screenshot. A field stays covered once the tab has shown it as one of these, so a page's "show password" toggle, which turns it into plain text, doesn't uncover it: main remembers each such field for the life of the page (per tab and renderer process), reads a held tab's fields about every half second while the person uses it, and the typing guard, the rules' probe and the Bot's locators (a wait, a click, type or scroll target, the snapshot's own locators) treat a remembered field as a password field. A field is not remembered when the page turns it into plain text before Aiden first reads the page (for example within half a second of loading on a held tab), or when a toggle replaces it with a new plain-text field instead of changing its type. The screenshot is left out, with a note that says why, when a visible frame can't be inspected (such as a cross-site iframe), the page is pinch-zoomed, or it navigated or scrolled during capture. The Bot can't type into those fields, by locator or into the focused element, can't press a printable key into one, and can't use a selector that could tell what a field holds: one that matches on value or on a field's state (:checked, :default, :placeholder-shown, :empty, :blank, :valid, :invalid, :user-valid, :user-invalid, :in-range, :out-of-range, :indeterminate, :autofill, :dir(), :state()), anywhere in the locator (inside :has() or a nested locator too), or any XPath, which main refuses and the page refuses again with Playwright's own parser. The page also refuses a CSS or internal:attr test of a selected or checked attribute, nested ones included, because a page rendered on the server marks a saved choice that way (<option selected> for the saved card month), and a role locator's [checked] or [selected] test never matches one of those fields itself. A Bot's locator can't contain a CSS comment, a backslash escape or an attribute name in quotes (["checked"], which Playwright's attribute engines read as [checked]), since each could hide one of these tests. Playwright's attribute engines get the same checks as CSS: internal:attr and internal:testid (which reads any attribute, not only data-testid) can't test value, selected or checked, while getByPlaceholder, getByTitle and getByTestId locators still work. States that don't depend on the value (:required, :optional, :read-write, :enabled) and a role locator's own [checked] and [selected] still work. Text and label locators (text=, :has-text(), internal:label and the rest) never see the text inside those fields. Role locators (role=, internal:role) never match an element whose accessible name or description, when the locator tests one, could include what a field holds: Playwright reads a field's live value, a password's too, into the name of a table cell, row, checkbox label or button around it. A role locator follows an element's own content only for the roles Playwright names from content (a cell, row, button, checkbox, link, heading and the like), so a dialog, form or group named by its heading, aria-labelledby or legend still matches by that name when it holds a field (unless the heading or legend itself holds one), and on a page with none of these fields role locators match as Playwright's own do. Elements named by their own aria-label, and fields named by their label, still match by name; the options inside a card-expiry list never match a role locator, and a contenteditable code field never matches one by name. So what a card, code or password field holds can't decide whether a locator matches the field or any element around it, and a locator that matches several elements is refused with their count, without describing them. A Bot's locator runs only on the bundled Playwright runtime's own selector engines; a runtime with any other engine refuses every Bot locator. The snapshot's locators are generated in this same view of the page, so none of them quotes or depends on what a field holds, and each still finds its element for the Bot. In the snapshot's accessibility tree, a name, description or value that could include a field's content is blank (a name from the element's own aria-label, title or placeholder stays, as does a field's name from its label), Chromium's text for a related element that reaches a field is left out, and nodes inside a field are dropped. When another element covers the Bot's target, the error says so without describing that element. Not covered yet: copies a framework keeps of a choice in other attributes (data-*, or aria-checked and aria-selected on a custom list), a field the page or the person turns into plain text during a single long wait or scroll (it is remembered from the next call), and the page text, which still includes what a contenteditable code field shows (browser_wait_for's text condition reads that text too). |
| Sign-in | A Bot asks the person to sign in with the sign_in tool, offered with Browser in its access (or with a Bot computer, where it signs in on the Bot's own computer instead; see Bot computers) and never on routine or Telegram turns. The site is normalised to its public https:// origin: http: and other schemes, a user name or password in the address, IP addresses in any notation, localhost and *.localhost, single-label hosts and ports other than 443 are refused with "Use an https site name like github.com." The card reads "Sign in to for " with Open sign-in, Skip and I’m signed in, and says the Bot never sees the password; while it waits the composer takes no note, so a password typed in the chat can't reach the Bot. Open sign-in opens the origin in a new tab of the Bot's browser, brings the panel's Browser tab forward, and pressed again selects that same tab. When the window is too narrow for the panel to sit beside the chat, the panel floats over the card and shows the card's title and buttons at its top, so I’m signed in stays in reach. From a phone, the Mac brings the Bot's chat forward and its panel shows the held tab. I'm signed in tells the Bot "The person says they signed in to . Check with browser_snapshot; never ask for or type their password."; a sign-in is never answered Done from a phone. The card's wait id is a memo of the tool call, so Resume after a quit or crash shows the same card. |
| Holds | A sign-in opens in a new tab, shown to the person and held for them. An action hand-off's link opens in the Bot's tab already on that page, else a new one, when the Bot has the browser, and in the system browser otherwise or when the Bot's browser can't open it. For a rule that hands over a browser step, Open shows and holds the exact tab the Bot acted on. Closing the tab, navigating it or reloading it makes that card’s Open unavailable, even if another tab has the same URL. Phones and paired Macs send the card’s wait ID to the host, which checks the document there. A held tab refuses the Bot ("The person is using this tab. Wait for them to finish.") but not the person, and browser_status, the browser gateway and command results list it with no address and the title "In use by the person"; it is released when the card settles or is withdrawn. A card asked again under the same wait (its stale invocation withdrawn) is the same card and keeps the tab, and a tab two waiting cards opened (both on the same page) stays held until neither waits. The pages it loads and what it logs while held are not recorded, so the Bot's snapshots after the release never show the person's sign-in steps. |
npm run test:browser covers contracts, action policy, profiles, cookie decoding/import, preview-server confinement, annotation context/capture, live-style reversion, and UI state. The three browser Playwright files cover actual Electron page interaction, a deterministic model's tool loop and Ask-mode approvals, live annotation, profile isolation, responsive and floating layout, native-view visibility, local HTML/PDF documents, a valid MP4 recording that decodes in the app, input cancellation, and closing picture-in-picture during capture. Shared activity labels are covered by the desktop, Android, and iOS model tests.
Validation includes focused browser/context/runtime suites, actual Electron agent discovery and execution, exact temporary-file approval and asset loading, shared listener lifetime, stale approvals, hover interception, cursor cleanup, owner reload and workspace/file-identity revocation. Desktop, Android and iOS activity labels include discovery. Initial feature validation passed: npm test, test:preflight, 118 browser tests, 266 compaction/runtime tests, all 15 original Electron browser cases, TypeScript, E2E TypeScript, lint, build and focused Android tests. iOS app/test targets pass generic-device build-for-testing; XCTest execution requires a physical device under repository policy.
PR review follow-up validation passes: 140 browser tests (including real Chromium cross-port credential isolation), 20 Electron browser cases with no flaky retries, TypeScript, E2E TypeScript, lint and production build. Direct user-link coverage verifies discovered CSS/module assets and a separate strict agent grant. Recording coverage decodes three immediate start/stop outputs as video; input coverage includes native keyboard source flags, same-input collisions, and mid-action workspace/global access revocation.
npm run test:bot-browser covers the Bot tool list, gateway disclosure, approvals that expire before the effect, held tabs, typed-text redaction on cards, value, state and XPath selectors refused before the page is probed, only the newest three screenshots in a request, a hand-off's held tab and its release when the card settles or is withdrawn (not when it is asked again, nor while another waiting card opened the same tab), the owner main mints from a window, and the Bot panel's Browser tab; npm run test:browser covers the field walker (including a real Chromium page), masking and the boundary. tests/e2e/bot-browser.spec.ts runs the real browser service in Electron: separate sign-ins between a Bot and a workspace profile, gray pixels over password, code and card fields before a sign-in hold and after its release (on a Bot tab hidden behind the person's own browser), refused typing and printable key presses into a password field, a password field turned into plain text (by the Bot's click on Show, or by the page while the person held the tab) still painted gray, left out of the text, refused for typing and printable key presses, and probed as a password field, a remembered password field the person typed in and showed as plain text, which a wait and a scroll can't read through the table row around it and the snapshot's locators don't quote (each checked on a page no earlier step had marked), main refusing page scripts, recording, local files, value selectors and unbounded agent commands, text and label locators that can't tell a card textarea's text apart, XPath refused, a locator matching both card fields refused without quoting them, role locators that can't tell a password, code or card field's live value apart through a table cell, row, checkbox label or button around it while a button's and a cell's own names still match, a snapshot of that page whose text, elements and accessibility tree hold none of the values and whose every locator finds its element, the rules' probe of the checkbox leaving its name out, a covered target's error that doesn't describe the card field over it (a workspace agent still gets Playwright's message), a dropped image for a cross-site frame, a held tab whose sign-in steps never reach the Bot's later snapshots, and delete clearing the Bot's sign-ins. credential-fields.test.ts checks in real Chromium that each refused selector form really tells a field's value or state apart (a chosen card-expiry month, an empty, valid or in-range field, the direction of its text) while the allowed states don't, that text, label, role and XPath locators that tell a password, card or code field's content apart with the bundled Playwright runtime (a cell, row, checkbox, button name or description around the field, live typed values included) look the same to a Bot while ordinary ones find the same element (a dialog named by its heading and a group named by its legend, both holding a card field, included), that a runtime with an unknown selector engine refuses every Bot locator, that Playwright never names an element from its own content for the roles a Bot's role locator doesn't follow into, that a large page without these fields leaves nothing out, that the snapshot's generated locators don't depend on a field's content and still find their element, that the snapshot's accessibility tree names no element with what a field holds while names from elsewhere stay, and that a remembered field stays sensitive to the walker and the typing guard after a "show password" toggle. npm run test:bot-sign-in runs the sign_in tool in the durable harness: the card and its origin, refused sites, I’m signed in and Skip results, the same card after Resume and after a killed process, the held tab hidden from browser_status and the gateway, Open again selecting the same tab, release on answer and Stop, the tool offered only with Browser or a Bot computer and never on routine or Telegram turns, and, for a sign-in on the Bot's computer, Open taking the computer over before anything opens there and giving it back when the card settles or is withdrawn. tests/e2e/bot-sign-in.spec.ts drives the real tool, card, opener and browser service in Electron: Open sign-in opens the origin in a held tab of the Bot's browser, the Bot's snapshot and navigate are refused and its status shows no address while held, and after I’m signed in the Bot's browser has the sign-in while the person's browser doesn't; tests/e2e/bots-starter.spec.ts answers a sign-in card in the shipped renderer, from the card with the panel docked and from the floating panel. Signing in to a real site in a Bot's browser is checked on a Mac, not by CI.
Real installed-browser import acceptance depends on available profiles and OS permissions. Native-window captures are required for visual checks: a renderer-only Playwright screenshot does not include Electron's native browser view.
| Case | Handling / remaining consideration |
|---|---|
| Browser is closed, selected tab disappears, or user selects another tab | Open explicitly; generation selection stays pinned, and stale IDs fail instead of retargeting another page. |
| Discovery repeated, excluded, disabled, cancelled, or compacted | Idempotent turn-boundary registration; excluded tools stay excluded; disabled new generations have no gateway; compaction preserves active inventory and correct budgets. |
| Human takeover or earlier parallel action fails | Release held inputs, invalidate already queued actions, and inspect fresh evidence before retrying. Native debugger-source flags distinguish identical physical keyboard input; missing keyboard flags fail closed. Mouse-down/up source metadata is unavailable, leaving a bounded same-button collision ambiguity. |
| Approval waits while a tab navigates, reloads, or changes | Bind page/control revisions and arguments; reject expired approval before input. Hiding an inactive picker does not count as takeover. |
| A target moves or an overlay appears on hover/down | Recheck the resolved element and intercept hit targets across trusted click events; avoid clicking the covering element. |
| Local file URL, temporary document, missing asset, or duplicate preview tab | Use managed path previews, exact grants for every document and declared asset, bounded static dependency discovery for direct user opens, and approval for external files; share leases until the last consumer leaves. |
| Traversal, symlink swap, file replacement, workspace-root change or revoked access | Confine paths and pin identities; reject new bytes, revoke affected leases, preserve source files. |
| Failed navigation, cancelled open, active reader, close/reopen race, owner crash | Reservations and tab references prevent premature release; final cleanup has a bounded drain. No stale child process is created. |
| Large page/AX tree, noisy console/network, old screenshots, text-only model | Bounded textual projection, explicit omissions, failure priority, independent screenshot retention and modality filtering. Request fresh focused evidence when needed. |
| Page repeats a capability token or supplies instructions | Redact known textual secrets; treat page content as untrusted evidence. Do not interpret page instructions as user authorization. |
| Cursor, held key/button, recorder, picker or PiP teardown | Matching-world cursor removal, bounded input release and recording/capture lifetime tests cover cancellation and early closure. |
| Ambiguous/hidden/disabled selectors, readiness timeout, unsupported key or target | Return actionable errors; no blind retry or automatic fallback to another browser. Inspect again and use a supported target. |
| Site authentication, third-party cookie import and OS permission prompts | Profile isolation remains; installed-browser import acceptance depends on real profiles and OS permission. No credentialed import acceptance is claimed by fixture tests. |
| Cross-frame interactions, native dialogs, uploads/downloads and desktop-only controls | The existing fourteen-operation surface is preserved. There is no added dedicated upload/download/native-dialog tool; unsupported workflows must be explained or use separately authorized capabilities. |
This is a tested risk inventory, not a claim that arbitrary websites have no further edge cases.