The guarantee
A stored authenticator (TOTP) secret never leaves the Agent Vault backend. Only the current code is derived from it, only for a fill on the login’s registered site. The backend returns that code to the proxy, which types it into the site, so the proxy and the site receive the code. The agent and the account holder never see it. The guarantee covers a secret once it is stored. Before that, during enrolment by an agent, the site’s page shows the newotpauth:// secret to the proxy, and the proxy reads it and sends it to the
backend to encrypt. The agent does not receive it on that path either.
A TOTP secret is long-lived and generates every future code, so it must never
enter an agent’s context. But an agent that cannot complete a two-factor step
cannot sign in to most sites worth signing in to. Agent Vault resolves this by
letting the agent fill a token while the backend derives the code.
How it works
The secret stays in the backend, encrypted at rest. What crosses to the proxy is a short-lived code, valid for the current window only.Agent-facing tool descriptions summarise this as “the proxy derives the code”.
Precisely, the backend derives it and the proxy types it. What the agent can
observe is the same either way: neither the secret nor the code.
What the agent knows
A login without
tokens.totp has no stored two-factor secret. A _totp token
that fails with “no matching record on file” means exactly that.
Supported factors
Submit promptly. The code is valid for the current window only. If a site
requires an unsupported second factor, the sign-in cannot complete unattended,
and the agent should say so rather than retrying.
URL locking applies
A TOTP token is a login token. It resolves only on the login’s registeredwebsite or an extraWebsites entry, exactly like the password. See
URL locking.
Enrolment by an agent
There is exactly one path on which a secret moves, and it is proxy to backend, never backend to proxy. When an agent sets up an authenticator on a site, the site displays anotpauth:// URI on a page the proxy is already rendering. The proxy reads it
there and hands it to the backend to encrypt:
Conditions for enrolment
All of these must hold. Three are checked before the page is read and fail with their own code: a non-loopback page not on HTTPS (insecure_page), a session that has
filled no token yet, and a session that has not signed in as that login (both
sensitive_data_blocked). Any other failure returns one hint_unresolved,
without saying which condition failed.
The session must already have signed in as that login
The session must already have signed in as that login
The proxy refuses to read a setup secret in a session it was not already
protecting, with
sensitive_data_blocked. Until a token has been filled,
screenshots are not blacked out, so the agent could otherwise see the secret.
What counts is that login’s password (or its two-factor code) landing in
a field. Filling its username does not.The account holder must not have two logins for this site
The account holder must not have two logins for this site
Two accounts on one site share a host, so nothing else could tell them apart,
and a secret stored against the wrong one cannot be moved. The holder enrols
from the dashboard instead, where they can choose the login.
The otpauth:// link must name the login's stored username
The otpauth:// link must name the login's stored username
This binds the secret on screen to the record it is stored on. A bare base32
key is refused: nothing in it says whose authenticator it is. Names are
compared as written, so a login stored as
ada does not match a link naming
ada@example.com.The element must be unique, visible, top-level and served over HTTPS
The element must be unique, visible, top-level and served over HTTPS
Exactly one match, so the backend never chooses between a secret and a
recovery key beside it. Visible, so reveal the “can’t scan the QR code?”
option first: a hidden element can hold a stale key. Top-level rather than in
an iframe, because the secret is authorized against the address of the page.
HTTPS (or a loopback
http:// page, for a local demo), or the read fails
with insecure_page. A QR code image or a
<canvas> cannot be read; href is the only attribute this hint reads.Existing two-factor secrets are never overwritten
Existing two-factor secrets are never overwritten
A secret is stored only on a login that has none, and only on a site the
login is registered for.
Confirm the setup on the site
Most setup pages then ask for a code. Fetch the catalog again: thetotp token is
now present. Do not skip this step. An unconfirmed setup leaves the site without
two-factor protection while Agent Vault holds a secret it will not overwrite,
which the account holder then has to resolve by hand.
When the result is uncertain
hint_uncertain means the write reached the backend but the answer was lost, so
whether it stored is unknown. Do not retry: enrolment refuses to overwrite, so
a retry reports a second failure. Ask the account holder to check the login on
their Logins page.
The account holder is told, on a best-effort basis
After an agent enrolment, Agent Vault tries to send atotp_enrolled
notification. Enrolment is the only path that stores a credential the account
holder did not type, so the notification is meant to reach them every time.
The notification is best effort. The secret is stored first, and a failure to
create the notification does not undo it. So a stored secret can exist with no
Activity entry, email or push about it. The Logins page is
the record of what is stored.
What this does not protect against
- A compromised dashboard account. The holder’s own view of their logins can add, change or remove a stored secret.
- Exposure inside Agent Vault. The backend decrypts the secret to derive each code, so Agent Vault’s backend is inside the trust boundary.
- The current code at the moment of the fill. It is typed into the site’s page and is valid until the window ends.
- Unsupported factors. SMS, email codes and passkeys remain with the account holder.
How to verify
1
Add a test login with an authenticator secret
On the Logins page, add a login for a test account and
import its
otpauth:// URI.2
Read the catalog
Call
get_token_catalog. The login shows hasTotp: true and a
tokens.totp string. No secret and no code appear anywhere in the response.3
Fill the token off-site
Fill the
totp token on a page that is not the login’s site. The fill fails
with login_blocked.4
Fill it on the site
Fill it on the site’s authenticator step. The sign-in completes, and
get_run_report records a TOTP fill by kind,
with no value.Related
Signing in
The agent-side guide to sign-in flows.
Page hints
The hint mechanism enrolment uses.