Repository navigation
feat(preview): fill login forms from rbw without exposing the secret to agents #557
Description
What: Agents can sign in to a site in a Chromeria browser tab with a credential from the user's Bitwarden vault (rbw), and the password and one-time code never pass through the agent's transcript or tool results.
Why: Today an agent can only type a password through preview_type, which writes it in plain text into the session log; the server browser refuses clipboard paste (NotAllowedError) and is driven over --remote-debugging-pipe, so no outside helper can fill the field either.
So what: A worker designs and builds a credential-fill preview tool; the planner reviews it and deploys it after the user approves the design.
Outcome
An agent driving a Chromeria browser tab reaches a login form and calls one tool that names a vault item and a field (for example item Shopify, field password or totp) plus the target input. Chromeria reads the value from rbw on the machine that owns the vault, types it into that input, and returns only success or a specific error. The value never appears in the agent's tool arguments, tool results, transcript, logs, traces or screenshots.
Problem Statement
Observed 2026-10-19 on cavallo while logging an agent into admin.shopify.com in the server browser (Default profile):
navigator.clipboard.readText()in the page returnsNotAllowedError;Meta+Vinto the password input inserts nothing.- The headless browser runs with
--remote-debugging-pipe, so a local script cannot attach over CDP. - The only working route,
preview_type, puts the password into the tool call, which is stored in the agent session log.
The user wants agents to log in on their own with the vault entries on rocky (rbw get <item>, rbw code <item> for TOTP) when a session expires.
Solution
A preview tool that names a vault item and field instead of carrying the value. Chromeria resolves the value server-side from rbw (local, or on the vault machine over SSH), types it into the target input, and returns only success or a specific error.
#520 / PR #530 mirror the user's Chrome cookies into Chromeria, which covers sites where the user is already signed in on the Mac. This Issue covers what #520 deliberately excluded: passwords, and re-login when no human session exists (expired session, new machine, a site that signs out often). Both are needed.
Elon record
- Requirements and who asked: user, 2026-10-19, amazonads thread: "we need a robust way for agents to log into Chromeria web pages using rbw, not this one-time patch"; agents may log in with the rocky vault on their own.
- Deleted: clipboard paste and outside CDP helpers (both blocked, see Problem Statement); typing secrets through
preview_type(leaks into logs); storing passwords in Chromeria. - Bottleneck: no preview tool can put a secret into a page without the agent seeing it.
- Checked myself:
psof the server browser (--headless --remote-debugging-pipe, profile~/.t3/userdata/server-browser/profiles/default); clipboard readNotAllowedErrorand empty paste on accounts.shopify.com;rbw get --full Shopifyon rocky holds username, password and TOTP secret fields; spec: mirror Mac browser logins into Chromeria and out to bestia and Rocky #520 scope and PR feat(preview): mirror Mac Chrome logins to connected environments #530 comments.
Acceptance criteria
- A preview tool (name to be designed, for example
preview_fill_credential) takes a vault item reference, a field (username,password,totp) and a locator, and fills the input without returning the value. - The value is never present in tool arguments, tool results, renderer state, logs, traces, Issues or PRs; screenshots of password inputs stay masked.
- Works when the vault lives on another machine (rbw on rocky over SSH) and when rbw is local.
- Specific errors: vault locked, item missing, field missing, input not found; no silent fallback.
- Each use is recorded (item name, site origin, time, thread) without the value, so the user can audit agent logins.
User stories
- As the user, I let agents log in to my sites with my vault entries, so I am not interrupted when a session expires.
- As an agent, I fill a password and a one-time code without ever seeing them, so the transcript stays clean.
- As the user, I can see which agent logged in where and when.
Implementation decisions
- To be designed: tool name and arguments, where rbw runs (local versus the vault machine), per-environment vault configuration, and masking of password inputs in screenshots.
Design review
- Independent review: pending; required before implementation.
- User decisions: agents may log in with the rocky vault on their own (user, 2026-10-19).
Testing decisions
- Prove at the preview tool seam: a test page with username, password and TOTP inputs; assert the inputs are filled and that no tool result, log line or trace contains the value. Live proof against admin.shopify.com.
Non-goals
- Storing passwords in Chromeria.
- Syncing cookies (spec: mirror Mac browser logins into Chromeria and out to bestia and Rocky #520).
- Solving CAPTCHAs or email device confirmations; those stay human.
Blockers
- None.
Required proof
- An agent logs into admin.shopify.com (password plus TOTP) in the server browser on cavallo, and a search of the session log and server logs for the password finds no match.
Contribution to the Objective
Lets agents keep working on the user's sites (Shopify, Amazon Seller Central, Canva) without the user re-entering passwords, removing a recurring human interruption.
Activity
lukemaj commentedon Oct 19, 2026
What: Design proposal for
preview_fill_credential, checked against outside prior art; ready for the user's approval.Why: The Opus design review read the Chromeria code at
c8b69c6; two researchers (GPT-6.1, Grok 4.7) compared Steel, Browser Use, Playwright MCP, 1Password, Bitwarden, Skyvern, Hyperbrowser and Operator.So what: After the user approves, a worker implements it on a branch with the tests below; the planner reviews and runs the live Shopify proof.
Decision
One server-browser operation. The agent passes
item,field(username|password|totp) and a requiredlocator; the result is{ filled, item, field, origin }orPreviewCredentialErrorwith a fixed reason (vaultNotConfigured,vaultUnavailable,vaultLocked,itemNotFound,itemAmbiguous,fieldMissing,originMismatch,insecureOrigin). The value never enters the broker request or response.Flow inside
ServerBrowser: resolve the locator and its owning frame; https only;rbw unlocked;rbw get --field uris -- <item>and match the frame origin; fetch the value (rbw get --field …orrbw code); re-check the origin; fill; verify on the server; keep the value in memory on the tab only for redaction.This is the pattern Steel (origin-bound fill), Browser Use and Skyvern (placeholder resolved by the runner, TOTP minted in the runner) already ship. 1Password's agentic autofill adds a human approval per fill, which this Issue rules out; Bitwarden MCP and
aac connect --output jsonreturn secrets to the caller and fail the requirement.User decisions (2026-10-19, amazonads thread)
ssh rocky /opt/homebrew/bin/rbw, rocky local, edna none.Changes from research
shopify.comcoversaccounts.shopify.comandadmin.shopify.combut notevilshopify.comorshopify.com.evil.com.preview_evaluateis refused on the tab after a fill until the next cross-document navigation; the submit request body is never captured.rbwitself, nor from using the logged-in session afterwards.Security details kept from the design
preview/CredentialVault.tsspawnsrbworssh <target> <rbwPath> …itself, single-quotes every remote argument, puts--before the item, validates the item name (strict charset, ≤128 chars, no leading-), and never logs stdout (the sharedrunSshCommandlogs stdout on failure, so it is not used).DEBUGenables Playwright protocol logging.stateDir/credential-fills.jsonl(0600): time, environment, thread, project, provider, item, field, origin, tab, result. No value.Files
packages/contracts/src/previewAutomation.ts,packages/contracts/src/settings.ts,apps/server/src/mcp/toolkits/preview/tools.ts,handlers.ts, newapps/server/src/preview/CredentialVault.ts,apps/server/src/preview/ServerBrowser.ts,ServerBrowserPage.ts,packages/shared/src/t3McpToolPresentation.ts, tests for each.Proof
Unit tests for argument quoting and error mapping with a fake spawner; an origin matcher table; real headless Chromium with username, password, one-time-code and a cross-origin iframe, a fake vault returning sentinel values, asserting the sentinel is absent from every result, snapshot, console entry, network URL and action timeline, evaluate refused while armed, and wrong-origin fills refused after only the
urislookup; MCPCallToolResultcontains no sentinel; live login to admin.shopify.com (password + TOTP) on cavallo withgrep -cFof the session and server logs returning 0.lukemaj commentedon Oct 19, 2026
What: Design simplification: Chromeria only ever runs a local
rbw; the SSH hop to rocky lives in the machine wrapper.Why: The user asked for an
rbwwrapper like the existinggog/himalayaones; it is now installed on cavallo and bestia (runbookf9e0962,shared/scripts/rocky-tool), sorbwthere runs rocky's rbw over SSH with every argument single-quoted for the remote shell.So what: The worker builds the design above with this change.
credentialVaultbecomes{ kind: "none" }(default) or{ kind: "local", rbwPath }. Planned: cavallo and bestia~/.local/bin/rbw(the wrapper), rocky/opt/homebrew/bin/rbw, edna none.sshkind,target/port/identityFile, and Chromeria-side remote quoting.CredentialVault.tsspawnsrbwPathdirectly with an argument array (no shell), still with--before the item and the strict item-name check, and still never logs stdout.rbw unlockedexits 0 andrbw get --list-fields Shopifylists username, totp, uris, password through the wrapper on cavallo and bestia. Wrapper exit 69 means rocky was unreachable; map it tovaultUnavailable.feat(preview): agents fill logins from an rbw vault without seeing th…
lukemaj commentedon Oct 19, 2026
What:
preview_fill_credentialis implemented in PR #561 (head2c716703aa), built to the approved design with the local-rbwPathsimplification.Why: Agents need to sign in without the value passing through their transcript, results or logs.
So what: The planner runs independent review and the live admin.shopify.com proof on cavallo; merging needs the user's approval.
Handoff:
feat/557-preview-fill-credential, rebased onorigin/maine760e6af8b. Commits:3f1a12741e(feature),2c716703aa(registry PR link, docs only).3f1a12741e: focused suites 568 passed / 1 skipped across 23 files, including real headless Chromium sentinel tests, the fake-spawner vault tests and the MCPCallToolResult+ audit test; typecheck clean for server, contracts, shared, web, mobile, desktop, client-runtime; lint clean;fork-features checkandfork-check.shOK. Commands and details are in the PR body.protocolLoggingandfillFailedadded;tabIdin the result and error; the DEBUG refusal also coverspw:api; the evaluate refusal usesPreviewAutomationExecutionError.rbw get/rbw codewere never run against the real vault."credentialVault": {"kind": "local", "rbwPath": "<path>"}in that server'suserdata/settings.json(cavallo and bestia~/.local/bin/rbw, rocky/opt/homebrew/bin/rbw); this is a settings change I did not make.fix(preview): keep filled credentials out of pixels, encoded output, …
lukemaj commentedon Oct 19, 2026
What: The five P1 findings from review round 1 on PR #561 are fixed on head
290cb2704d.Why: Round 1 (pullrequestreview-5578161274) reproduced leaks through screenshot pixels, encoded results, session state, username/TOTP echoes and a popup's
opener.So what: The planner runs independent review round 2 on
290cb2704d, then the live admin.shopify.com proof; merging needs the user's approval.Handoff:
feat/557-preview-fill-credential, 0 behindorigin/mainat push. Commits:3f1a12741efeature,2c716703aaregistry link,290cb2704dreview fixes.screenshot.datais exempt.290cb2704d:PLAYWRIGHT_BROWSERS_PATH=/tmp/pw-browsers pnpm exec vp test run apps/server/src/preview apps/server/src/mcp/toolkits/preview apps/server/src/mcp/McpHttpServer.test.ts apps/server/src/mcp/PreviewAutomationBroker.test.ts apps/server/src/serverSettings.test.ts packages/contracts/src/settings.test.ts packages/shared/src/t3McpToolPresentation.test.ts: 22 files passed, 1 skipped; 524 tests passed, 1 skipped. Typecheck (apps/server,packages/contracts,packages/shared) and lint clean;fork-features checkandfork-check.shOK. The new server-level regression fails with the session scrub reverted and with the guard narrowed to one tab.rbw get/rbw code, no settings change, no merge.fix(preview): hold credential protection until related documents are …
lukemaj commentedon Oct 19, 2026
What: The four findings from review round 2 on PR #561 are fixed on head
100cc53e40.Why: Round 2 (pullrequestreview-5578341412) showed the password survived a GET navigation, a racing popup recording and mixed encodings, and that global scrubbing broke other threads' responses.
So what: The planner runs independent review round 3 on
100cc53e40, then the live admin.shopify.com proof; merging needs the user's approval.Handoff:
feat/557-preview-fill-credential, 0 behindorigin/mainat push. Round-2 fix commit:100cc53e40.+, HTML named and numeric entities, JSON escapes, two layers), with whole-line replacement.100cc53e40:PLAYWRIGHT_BROWSERS_PATH=/tmp/pw-browsers pnpm exec vp test run apps/server/src/preview apps/server/src/mcp/toolkits/preview apps/server/src/mcp/McpHttpServer.test.ts apps/server/src/mcp/PreviewAutomationBroker.test.ts apps/server/src/serverSettings.test.ts packages/contracts/src/settings.test.ts packages/shared/src/t3McpToolPresentation.test.ts: 22 files passed, 1 skipped; 539 tests passed, 1 skipped.serverSettings.test.ts(an untouched file-watch test); it passed 3 of 3 alone and in the rerun.apps/server,packages/contracts,packages/shared), lint andgit diff --checkclean; fork checks OK.rbw get/rbw code, no settings change, no merge.lukemaj commentedon Oct 19, 2026
What: Planner redesign after three review rounds: replace scrubbing with a login quarantine.
Why: Each round closed the reported leaks and the next round found new ones in the same machinery (lazy release, release races, group splits, entity tables); the bottleneck is the attempt to let agents keep reading a page that holds a secret, not any single bug.
So what: The worker rebuilds PR #561 on this model; the reviewer then reviews the quarantine instead of the scrubber.
Elon record
Quarantine model
preview_fill_credentialquarantines the tab's whole opener group, including popups opened later from it.preview_fill_credentialcalls, click/press/type by locator (results carry no page content),preview_wait_forreturning only matched or timed out, and a newpreview_end_logintool.preview_end_logincloses every tab in the group. The agent continues in a fresh tab; the profile's cookies keep the session. Quarantine also ends if the user closes the tabs. There is no other exit.refactor(preview): replace credential scrubbing with a login quarantine
feat(preview): agents sign in from an rbw vault inside a login quaran…
lukemaj commentedon Oct 19, 2026
What: PR #561 now implements the login quarantine from the planner's redesign, on head
836ba184cc(one squashed commit on1e83a3169f).Why: Review round 3 (pullrequestreview-5578524056) found four more P1 leaks and two P2 failures in the scrubbing machinery; the redesign removes read access instead.
So what: The planner runs independent review of the quarantine on
836ba184cc, then the live admin.shopify.com proof; merging needs the user's approval.Handoff:
feat/557-preview-fill-credentialwas squashed to one commit and rebased onmain1e83a3169f. Conflicts with feat(research): share page reads and web search across environments #560 were resolved:readPageis kept and refused in a login, andpreviewAutomation.tsis now owned by feat(research): opt-in logged-in web_read without enforced pacing #446 and shared by this feature. Force-pushed with lease from3c1020d7f1.PreviewCredentialErrorloginInProgress; status and session state showLogin in progresswithout URL or title.preview_end_logincloses the login's tabs.836ba184cc.PLAYWRIGHT_BROWSERS_PATH=/tmp/pw-browsers pnpm exec vp test run apps/server/src/preview apps/server/src/mcp/toolkits/preview apps/server/src/mcp/McpHttpServer.test.ts apps/server/src/mcp/PreviewAutomationBroker.test.ts apps/server/src/serverSettings.test.ts packages/contracts/src/settings.test.ts packages/shared/src/t3McpToolPresentation.test.ts: 22 files passed, 1 skipped; 525 tests passed, 1 skipped.apps/server,packages/contracts,packages/shared,apps/web,apps/mobile) clean; lint clean;pnpm run knip:checkexit 0; fork checks andgit diff --checkOK.serverSettings.test.tsfile-watch flake; it passed 3 of 3 alone and in the reruns.rbw get/rbw code, no settings change, no merge.feat(preview): agents sign in from an rbw vault inside a login quaran…
fix(preview): logins outlive tab objects, results are checked as they…
lukemaj commentedon Oct 19, 2026
What: The five round-4 findings on PR #561 are fixed through three invariants on head
c74169f59d(rebased onmain5898397a65).Why: Round 4 (pullrequestreview-5578803129) showed the quarantine could be lost across popup attachment, failed follow-up fills and desktop reconnects, read by an in-flight status, or leaked through the fill's live origin.
So what: The planner runs independent review of
c74169f59dand the live admin.shopify.com proof; CI has started on this head; merging needs the user's approval.Handoff:
feat/557-preview-fill-credential, two commits on5898397a65:eb8b035129(feature) andc74169f59d(round-4 invariants). Force-pushed with lease from836ba184ccafter the rebase. The fix(desktop): Chrome login mirror no longer blocks startup #545 conflict inscripts/fork-upstream-edits.txtwas resolved by keeping both blocks.preview_end_loginor session close. Popups inherit when the browser reports them.site), and refusals carry no origin once the group holds a value.c74169f59d.PLAYWRIGHT_BROWSERS_PATH=/tmp/pw-browsers pnpm exec vp test run apps/server/src/preview apps/server/src/mcp/toolkits/preview apps/server/src/mcp/McpHttpServer.test.ts apps/server/src/mcp/PreviewAutomationBroker.test.ts apps/server/src/serverSettings.test.ts packages/contracts/src/settings.test.ts packages/shared/src/t3McpToolPresentation.test.ts: 23 files passed, 1 skipped; 533 tests passed, 1 skipped.CredentialLogin.integration.test.ts: the reviewer's five probes as real-Chromium regressions, plus a three-step sign-in. Each regression fails when its fix is mutated away.apps/server,packages/contracts,packages/shared,apps/web,apps/mobile), lint,pnpm run knip:check(exit 0), fork checks andgit diff --checkclean.CI,ForkandMobile Fingerprint Checkin progress onc74169f59dat handoff.rbw get/rbw code, no settings change, no merge.fix(preview): a login never lifts, covers new tabs in its profile, an…
lukemaj commentedon Oct 19, 2026
What: PR #561's round-5 findings are fixed on head
40c519b3adby deleting the racing mechanisms, and CI is green on that head.Why: Round 5 (pullrequestreview-5578999598) found races in fill rollback, popup inheritance and refusal origins, and CI couldn't launch Chromium under Ubuntu AppArmor.
So what: The planner runs independent review of
40c519b3ad, then the live admin.shopify.com proof; merging needs the user's approval.Handoff:
feat/557-preview-fill-credential, three commits on5898397a65; the round-5 fix is40c519b3ad.preview_end_loginor session close ends it.T3CODE_SERVER_BROWSER_SANDBOX=0, like the repo's other real-browser tests, which launch unsandboxed.pnpm run knip:checkand fork checks clean.Checkand all 8 server shards; Test Server 1 ran the 11 real-Chromium tests.Label PR sizepassed on re-run after a GitHub API error.rbw get/rbw code, no settings change, no merge.fix(preview): a login covers its whole server browser profile and dra…
lukemaj commentedon Oct 20, 2026
What: PR #561's round-6 findings are fixed on head
9131aa6639with a server-browser-only, profile-level login that drains on end, and CI is green on that head.Why: Round 6 (pullrequestreview-5579139789) found leaks through popups still attaching at end-login, desktop context wrappers, and popup URLs published before locking.
So what: A round-7 review has already posted
review/independent=failureon9131aa6639; the planner decides the next round. The live Shopify proof and merge approval remain.Handoff:
feat/557-preview-fill-credential, four commits on5898397a65; the round-6 fix is9131aa6639.serverBrowserOnly).preview_end_loginmarks the profile ending, closes its tabs and any that finish attaching, and lifts only when no tabs or attaching popups remain.pnpm run knip:checkand fork checks clean.rbw get/rbw code, no settings change, no merge.fix(preview): end a login by closing its profile's browser context
lukemaj commentedon Oct 20, 2026
What: PR #561's round-7 finding is fixed on head
f2613fbe88. CI is green, and independent review round 8 passed on that exact head.Why: Round 7 (pullrequestreview-5579292619) showed the login could lift while pages were still closing or tabs still attaching. Ending a login now closes the profile's browser context and lifts only after that close resolves.
So what: The planner runs the live admin.shopify.com proof on cavallo; then the user decides the merge.
Handoff:
feat/557-preview-fill-credential, five commits on5898397a65; the round-7 fix isf2613fbe88.preview_end_logincloses the profile's context throughServerBrowserContexts.closeContext, which waits; new tab requests wait for the close and get a fresh context from the same directory.pnpm run knip:checkand fork checks clean.review/independent=success(round 8).preview_end_login, so the Shopify proof should check whether its admin session persists into the fresh tab.rbw get/rbw code, no settings change, no merge.feat(preview): agents fill logins from an rbw vault without seeing th…