Burrowbox Blog
← All posts

Let an AI agent log in with 2FA without ever seeing the password

· The Burrowbox team · 7 min read

#How an AI agent can log in with 2FA

Put the login and its TOTP secret in the machine's vault with one PUT request from your server. Give the agent the browser_login tool. The tool fills the form, handles the one-time code and submits it. The model gets the tool's result back, but never the password or the secret.

There's nothing to redact from prompts, transcripts or traces afterwards, because the password was never in them.

#Why the TOTP secret has to stay out of the prompt

An authenticator-app code is a TOTP: a short number computed from a shared secret and the current time, in 30-second steps by default (RFC 6238). Anyone who has the secret can make valid codes for as long as it's enrolled. If the seed sits in a system prompt, it also sits in every transcript, log and trace that prompt passes through. Keep it on the machine that runs the browser. On Burrowbox, that's the vault.

#What the agent sees, and what it doesn't

Each machine has an encrypted credential vault (AES-256-GCM). The key is held by the platform, not stored on the machine's disk. Three machine MCP tools work with it (MCP):

  • browser_login fills and submits a login form, matching the credential by URL, and handles TOTP codes.
  • vault_type_secret types a username, password or TOTP code into the focused desktop app.
  • vault_list returns names, URLs and usernames only.

None of them returns a secret to the model. This is the same idea as the "secrets it can't see" part of Meta's Muse, built from parts you control.

#Step 1: Put the login in the vault

Create a dedicated account for the agent on the site and turn on authenticator-app 2FA for it. The setup screen shows a QR code, and usually a text setup key as well (something like JBSWY3DPEHPK3PXP). That key is the TOTP secret. Store it once, with the password:

curl -X PUT https://burrowbox.dev/api/machines/$MACHINE_ID/vault/crm \
  -H "Authorization: Bearer $BURROWBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://crm.example.com/login", "username": "agent@acme.com", "password": "…", "totpSecret": "JBSWY3DPEHPK3PXP"}'

The same call from a Node server, for example in the handler where a customer connects their account:

await fetch(`https://burrowbox.dev/api/machines/${machineId}/vault/crm`, {
  method: "PUT",
  headers: { Authorization: `Bearer ${process.env.BURROWBOX_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://crm.example.com/login", username, password, totpSecret }),
});

The vault also has notes, and PUT both creates and updates, so the same request rotates a password later.

An agent can store a credential itself with the vault_set tool (name, url, username, password, totp_secret). Those values are arguments the model writes, so they pass through the model. For secrets you already have, the REST call from your server keeps them out entirely.

#Step 2: Let the agent sign in

Connect your agent to the machine's MCP endpoint (POST /api/machines/{id}/mcp) with the machine's mcpToken. Any MCP client works. A web login then takes two tool calls:

{ "tool": "browser_navigate", "arguments": { "url": "https://crm.example.com/login" } }
{ "tool": "browser_login",    "arguments": { "credential": "crm" } }

credential is optional. Without it, browser_login matches the vault entry by the page's URL. After that, browser_snapshot shows the page text and interactive elements, so the agent can check it landed on the dashboard rather than an error.

If the agent needs to know what it can sign in to, vault_list answers with each credential's name, URL and username. Passwords and TOTP secrets aren't in the response.

Desktop apps work the same way. Focus the app's login field (with click or activate_window), then call vault_type_secret with name and field set to username, password or totp. The value is typed into the app and doesn't come back to the model.

One line in the system prompt saves a lot of back-and-forth: "To sign in, use browser_login. Never ask the user for a password or code."

#Step 3: Stay signed in, and hand off what the vault can't do

Most runs shouldn't need a login at all. Stopping a machine snapshots its whole filesystem, including the vault and the browser's cookies, session cookies too. The next start is usually still signed in, so the 2FA prompt comes back only when the site ends the session (Machines). If you're weighing a throwaway sandbox against a machine that keeps this state, see what an agent needs to keep between runs.

Pick the browser mode with this in mind. Both modes keep cookies across a stop and start. When the Burrowbox agent on the machine updates live, the full browser (Chromium) keeps its sign-ins and the light browser forgets them. For long-lived logins, create the machine with "browser": "full".

Some logins can't come from a stored secret: SSO that redirects to a company identity provider, push approvals on someone's phone, SMS codes. For those, hand a person a short-lived interactive live-view link and let them finish the login in the machine's browser:

curl -X POST https://burrowbox.dev/api/machines/$MACHINE_ID/live-view \
  -H "Authorization: Bearer $BURROWBOX_KEY" -H "Content-Type: application/json" \
  -d '{"mode": "browser", "interactive": true, "ttlSeconds": 600, "allowedOrigins": ["https://app.example.com"]}'

The agent can create this link itself with its mcpToken, so it can ask for help when it reaches a login it can't finish (Embed the live view). Anyone holding the link can use it until it expires, so keep the TTL short. Once the person is through, the cookies stay with the machine. This is the takeover pattern from ChatGPT agent, used only for the logins that need it.

#Keep it tidy

  • One account per agent. A dedicated login with its own 2FA enrollment can be revoked without locking out a person.
  • One machine per customer. Store each customer's credentials in that customer's machine. They never leave it (Machines for your customers).
  • Scoped tokens. Hand the agent the machine token (tmm_…), not your API key. It only works on that machine's MCP endpoint (Authentication).
  • Rotate and remove. PUT the same name to rotate. DELETE /api/machines/{id}/vault/{name} or the vault_delete tool removes one credential. Deleting the machine deletes its vault with everything else.

For a full example of an agent working behind a login, see the QA agent that tests behind your login.

#FAQ

#Can an AI agent use an authenticator-app code?

Yes, if the TOTP secret is in the machine's vault. Navigate to the login page and call browser_login. It fills the form, handles the TOTP code and submits, without returning the secret to the model. For a desktop app, vault_type_secret with field: "totp" types the code.

#Does the model ever see the password or the TOTP code?

Not when it uses browser_login, vault_type_secret or vault_list. Those tools never return secrets. It does see them if it stores a credential with vault_set, because the values are tool arguments, so store secrets from your server with the REST API instead.

#What about SSO, push approvals or SMS codes?

A stored TOTP secret can't answer those. Create an interactive live-view link with a short ttlSeconds and let a person complete the login. The machine keeps the session cookies afterwards, so a person usually has to step in again only when the site's session expires.

#What happens to the vault when the machine stops or is deleted?

A stop snapshots the vault along with the files and browser cookies, and it's all there on the next start. DELETE /api/machines/{id} permanently deletes the machine, including its vault.

#Key takeaways

  • The password and TOTP secret go into the vault from your server. The agent only gets tools that use them.
  • browser_login handles web logins and vault_type_secret handles desktop apps.
  • Stop/start keeps cookies, so most runs skip the login. Use full browser mode for long-lived sign-ins.
  • For SSO and push approvals, hand a person a short-lived interactive link.

Create an account to try it.

#Sources