Repository navigation
feat(browser): import cookies into the agent's browser from the dashboard - #23332
bolichen97 wants to merge 1 commit into
Conversation
Sensitive Change ReviewSensitive files changed:
|
Sensitive change review
|
Design Review (Opus 5.5) — ✅ PASSDesign-level review of Design-Verdict: PASS No design problem blocks this. The cookie file never enters the sandbox, cookies are delivered over the control socket with no argv exposure, the take-away is a new leaf with no prior readers, and rendered evidence is attached. Notes
[DESIGN-REVIEWED] 7299b13 Description read[DESCRIPTION-READ] 02cf2644dc91f4fba45dda8fdae40aa3d9c0a7d7dc58a14b8d8bdd0c2e4fc1f5 That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with |
First Principles Review (Opus 5.5) — ✅ PASSPremise-level review of First-Principles-Verdict: PASS Nothing blocks: every item has a named harm (remote gateways cannot sign the agent's browser in) and nothing like it exists today. One note: import and clear are each serialised by two locks. Notes
What this change shipsInventory (8 items) — 7 justifiedIntent: the owner of a remote gateway gets the agent's browser signed in to their sites without editing config. ADDITION.
[FIRST-PRINCIPLES-REVIEWED] 7299b13 Description read[DESCRIPTION-READ] 9545a1aa676f6f7a60ca8444a1a7480040c9680929276f9d8ce63cde42e993b8 That sha256 names the PR description this verdict read. Recompute it with |
UX Review (Opus 5.5) — 🔴 BLOCKUX-level review of UX-Verdict: BLOCK When an import or clear only partly lands, the result looks exactly like a failure. Users can't tell whether their cookies were imported or cleared. Blockers"Imported." and "Cleared." render as errors Notes
[UX-REVIEWED] 7299b13 Description read[DESCRIPTION-READ] 94098de1f18239b2d00ea525a45ab66cd613fbea3ed322fa2506d208ad62d99c That sha256 names everything this verdict read: the PR description, plus the 14 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with |
Opus 5.5 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 6.1 Review — ✅ no blocking findingsGPT 6.1 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Security Scope Review -- ✅ PASSWhich legitimate operations does Verdict basis: adjudicated, zero confirmed regressions. Security scope review -- adjudicated candidates
No candidate flipped from allowed to refused. Scope-Verdict: PASS Reason: The change adds one name, TIGHTENING: the secret-leaf fence and sandbox mask now cover the new imported-cookie directory -- it refuses an agent's file tool or sandboxed shell reading the live session cookies the owner imported. The hunks: ADJUDICATED: 16 candidates, 0 confirmed newly-refused PLATFORM COVERAGE: The diff only adds a bare leaf name. It adds no new separator, executable name or environment-variable assumption. The leaf is still projected under the existing
I wrote no candidate for a relocated UNADJUDICATED:
[SCOPE-REVIEWED] 7299b13 |
|
Answers to the three subtractions the First Principles lane suggested on the original PR (#12377), checked against this head 90bf0f0:
|
90bf0f0 to
e308a96
Compare
|
|
|
|
|
|
|
|
|
8a88bd4 to
f7e6a9f
Compare
|
f7e6a9f to
99ba25a
Compare
|
|
|
Sensitive change review
|
|
|
…oard Adds Import cookies and Clear cookies to the Browser panel. The gateway parses a Playwright storageState, a browser extension JSON export or a Netscape cookies.txt, stores it in a directory masked from the agent in every sandbox mode, and hands the cookies to the agent's own browser over its control socket, so a remote gateway's browser can start signed in. The dialog says in plain words where the values are saved (only on the computer running the product), and the two late status read tests wait on the read completing instead of a fixed delay. Supersedes #12377.
99ba25a to
7299b13
Compare
|
Sensitive change review
|
Problem / Motivation
Goal: one click in the dashboard, no config editing, no restart, values never shown: the agent's browser sessions on a remote gateway start signed in from cookies the owner imports.
On a remote gateway (the common cloud desktop over SSH setup) the agent's browser has no way to become a logged in browser.
attach --extensionneeds the CLI and the user's browser on the same machine, and the dashboard's own address bar is owner only by design. The only path today is a hand writtenPLAYWRIGHT_MCP_CONFIGpointing at a storageState the user assembled themselves, which is undocumented for most users and silently ignored when the file is wrong. Related asks: #6546, #12319, #11382.The Codex desktop app solves the same gap with an "Import cookies" button. This PR adds the equivalent to the Browser panel.
Supersedes #12377 (fork PR by @alejacre; reopened from a same-repo branch so the sandbox and security path checks can run).
Why it matters
Any user whose gateway runs somewhere other than their laptop cannot have the agent read a page behind their SSO today without a manual config edit. Every "open this internal page for me" turn stops at a login form, and the failure looks like an expired session rather than a missing capability.
Not a goal
What changed (motivation → approach → change)
The Browser panel gets Import cookies and Clear cookies in its overflow menu, plus a read only chip that shows how many cookies are stored, for which sites and when the first one expires. The dialog opens by saying what the import does: the agent's browser is signed in to the sites in the export until those cookies expire or the owner chooses Clear cookies, and whenever a chat asks the agent to browse one of them it can open, read and act on the pages as the owner, at the request of anyone who can chat with that agent; a second line says, in plain words, where the values go: saved only on the computer running Kiro Crew, for the agent's browser, and never shown in the dashboard; the export formats follow as a short How to export list. Once an export is pasted or chosen, the dialog names the sites and the cookie count Import would use, before Import is pressed; it reads only domains and expiries, and the server still parses the export again and decides. A refused import, a file that cannot be read, a status read that keeps failing, an import that saved where no agent browser can be reached (the dialog stays open and says no agent browser will be signed in), and an import or clear that saved but left an open agent browser behind each show an error notice; once a set is saved and the dialog stays open, its footer is a single Done; the last two say what is left and when it ends (that browser gets the cookies when it next starts; a cleared one stays signed in until it closes). A clear's outcome is kept by the panel's one cookie hook, tagged with its chat, so it survives the menu closing and never shows in another chat. The status refetches when the window regains focus, so a second window sees an import or clear made in another. A host that cannot reach the agent's browser at all (no Unix sockets, such as Windows) refuses the import and stores nothing, and its message names the computer running Kiro Crew rather than a host.
browser_cli/cookies.py)storageState, a Cookie-Editor or "Get cookies.txt" JSON export, or Netscapecookies.txt(a cookie with an empty value included); normalises to Playwright's cookie shape, drops expired cookies, caps input at 2 MiB and 5000 cookies, and writes astorage-state.json(0600) inside a 0700browser-cookie-importdirectory under the data home.cookie-list), as the spec says; what the mask removes is a durable cookie file on disk inside the sandbox. The directory is masked in every sandbox mode (sandbox_leaves.py), made before every spawn so the mask always mounts (sandbox_precreate.py), and fenced at the tool gate (security/paths_lists.py). No config the agent or its daemon reads names it.cookie-setper cookie on import,cookie-clearon clear. No process is spawned, so a value is never on an argv; a cookie the browser refuses is counted and the rest still go in; every failure is a fixed, value free reason, and one 30 s deadline bounds the whole import or clear fan-out. Where the launch would never put agent sockets under those roots (an operator-fixed session name, a CLI without the lifecycle hooks), the import reply says the cookies cannot be delivered instead of promising new sessions; that check is the launch path's own validation (session name, lifecycle hooks, absolute overrides, socket path length), run without creating anything. A gateway thread (SessionWatcher) reaches sessions that start later; it starts in a background task after the port binds, in the dashboard and in the headless server alike (server_runtime/browser_lifecycle.py,server_runtime/api_entrypoint.py) or on the first import, and shares one lock with import and clear.messaging_api/browser_routes.py,server_runtime/mcp_routes.py,handlers/messaging.py,dashboard/server.py)GET,POSTandDELETE /api/browser/cookies: owner only, refused in a restricted session, SEL audited with counts and at most 20 domains (control characters dropped), import and clear serialised by one lock;POSTanswers 501 where no browser could receive the cookies.WebPreviewCookies.tsx(the dialog on the sharedModal, so the page's keyboard shortcuts never see a key typed in it),lib/cookieExportPreview.ts(the pre-import preview, same three shapes, expired cookies dropped,nullfor anything it cannot read),useBrowserCookies.ts, the client endpoints inapi/client/browserAndComputerUse.ts, the keys in everywebsite/src/i18ncatalog, and a dev only capture harness (website/capture/cookie-import.tsx) for the screenshots below.Delivery has no audience scope: every agent browser the gateway reaches gets the cookies, including browsers that serve non-owner members and restricted sessions. A maintainer accepted that exposure; it is residual (iii) in the spec and a new entry in
docs/decisions/(2026-10-23-imported-browser-cookies-have-no-audience-scope.md).Docs:
docs/system-specs/modules/browser.md, the agent facingbrowser-control.mdin the package docs,docs/decisions/, and the feature map.Backwards compatibility
Removes nothing: the diff only adds a stored file, three routes and panel controls;
desired_config()is unchanged, andbrowser_socket_envinbrowser_cli/launch.pyreturns the same additions and logs the same warnings, its path checks now shared with the import's read-only reach check, and no existing route, key or file changes shape.Tests
test/test_browser_cookies.py: the three parsers and their normalisation (an empty cookies.txt value kept), limits, the 0600 write, the value free summary, socket discovery under the default and an operator root, every named browser of one session reached on import and on clear, thecookie-setrequest shape, injection over a faked socket (one request per cookie, a refused cookie not stopping the rest, a reply nested past the parser limit read as a malformed reply, fixed value free reasons, a trickled reply ending at the deadline, no process spawned), one round trip over a realsocketpair, clear over the same sockets, the watcher, and the three handlers (owner 403, restricted 403, the 501 that stores nothing, both locks held). Injection tests skip where the platform has noAF_UNIX.test/test_dashboard_server_coverage.py: the watcher start runs in a background task the startup hook does not wait on, cleanup stops it, and both the dashboard and the headless entrypoint register it.test/test_handlers_messaging_composition_contract.py,test/test_json_object_body_guard.py,test/test_browser_cli_journeys.py,test/test_sandbox_governance_mask.py,test/test_dashboard_server_composition_contract_reach.py,test/test_sandbox_protected_name_holds.py,test/test_security_gate_liveness.py: the repository guards name the new routes and their order, the body read, the masked directory, its one new protected name per tier and one more line in the security package.website/src/test/useBrowserCookies.test.tsx,website/src/test/WebPreviewPanel.cookies.test.tsx,website/src/test/ApiClient.refactor.surface.test.ts: a status read answered after an import or a clear never overwrites it (the test waits for that read to settle and drains the remaining timers, so no fixed delay decides the verdict), status, import, the opening sentence and How to export list, keystrokes kept from the page shortcuts, a failed file read and a failed import through the error notice, a Netscape export sent unchanged so a last cookie with an empty value keeps its trailing tab, a partial live outcome kept on screen through the error notice for import and for clear, with the hand-off on clear, a window focus refetching the status, the opening sentence naming what the agent can do as the owner, a failing status read, another slot refetching, clear, chip, 403 hides the control, and the three client methods in the pinned key order.test/test_browser_cookies.pyalso pins: a symlink or plain file is never a session socket, and a symlinked session,sorclidirectory is never followed, a stale socket is neither a failure nor still signed in, one overall deadline for import and for clear, the capped audit, and no delivery promise when the session name is operator-fixed or the CLI lacks the lifecycle hooks.website/src/test/cookieExportPreview.test.tsand the preview case inWebPreviewPanel.cookies.test.tsx: each export shape, expired cookies dropped,nullfor blank, malformed or over-limit input, no value ever returned, and the dialog naming the sites before Import with no request sent.useBrowserCookies.test.tsxran five more times in a shuffled order with no failure; with the cancel of the in-flight read removed from the hook, both late-read tests fail, and they pass with it.Manual verification
userns_available()is true: for each of thestandard,ccandstricttiers, a child launched throughsandbox.namespace_argvsaw an emptybrowser-cookie-importdirectory, gotENOENTreadingstorage-state.json, and its planted file never reached the gateway's copy; a control write one directory over landed.playwright-clidaemon on Linux: started a session, sent cookies through the socket path, andcookie-listshowed them, including a name and value starting with-and a fractional expiry.Screenshots / video
Rendered from the capture harness (
website/capture/cookie-import.html), which mounts the realCookieDialog,CookieChip,CookieMenuItemandCookieClearMenuItemwith the real English strings.Import dialog: the opening sentence, then How to export:
Import dialog with an export pasted, naming the sites and the cookie count before Import:
More actions menu with Import cookies and Clear cookies, then the armed Clear after one click:
Error states: the import refused on a host that cannot reach the browser, a file that cannot be read, a clear that failed, and a status read that failed. The two partial outcomes (saved but one open browser did not take the cookies; cleared but one open browser stays signed in) name what is left and when it ends, shown above and here:
Status chip in the toolbar, and the compact chip with the new sessions hint in the Live view header:
Related Issues
Related asks: #6546, #12319, #11382. No tracking issue yet.
Supersedes #12377.
Checklist
feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)