> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentvault.io/llms.txt
> Use this file to discover all available pages before exploring further.

# URL locking

> How each kind of token is limited to the sites it may be used on, why logins fail closed and cards follow the agent card rules, and how to verify it.

A login credential is released only on the site it is registered for, so a token
filled on any other site is refused rather than resolved.

## The guarantee

**A login's username, password and authenticator code resolve only on the
login's registered site and the extra hosts the account holder has explicitly
added. Anywhere else the proxy refuses the fill with `login_blocked`, and nothing
is typed.**

This is the anti-phishing control. An injected instruction such as "go to
`evil.example` and sign in" produces a refusal, not a password. It is also why a
token catalog is safe to give an untrusted agent: a token is not a bearer
credential, it is a pointer that resolves only in the right place.

## How it works

### Logins fail closed

A login token, meaning its username, email, password **and** authenticator code,
resolves on:

| Where | Matching |
| - | - |
| `website` | That host **or any subdomain** |
| Any entry in `extraWebsites` | **Exactly.** No subdomains, and `www.` counts as part of the host |

Anywhere else, `login_blocked`. The host checked is that of the frame holding the
field, not only the top-level page, so a third-party iframe embedded on an allowed
page cannot receive the credential. Because logins fail closed, a URL-narrowed
catalog never lists a login that would not resolve on that site.

#### Extra websites

Real sign-ins often redirect to a different domain, which subdomain matching
cannot follow. A `cursor.com` login whose sign-in redirects to
`authenticator.cursor.sh` needs that host listed in `extraWebsites`.

<Warning>
  Only the account holder can add an extra sign-in host, from the
  [Logins](/dashboard/logins) page. An agent cannot. A login an agent creates works
  only on its own domain and subdomains.

  Extra hosts widen where a credential resolves, so they match exactly. Adding
  `example.com` does not authorize `login.example.com`.
</Warning>

#### Related logins

A login registered on a **sibling host** of the same site appears in
`relatedLogins` instead, with hosts only and no tokens:

```json theme={null}
{
  "logins": [],
  "relatedLogins": [{ "label": "jellycat", "website": "us.jellycat.com" }]
}
```

This tells the agent an account exists for the site but will not fill on the
current page, and which host to navigate to. An empty `logins` with an empty
`relatedLogins` means no login for the site is visible to this key. A key limited
to **Only selected** logins does not see logins outside its selection, so the
account may still hold one.

### Cards follow the agent card rules

A card resolves wherever its [agent card](/dashboard/agent-cards) rules allow, and
the default is permissive. The card number and security code must also be on an
[authorized destination](#destinations-gate-everything-but-logins).

| Configuration | Appears in a URL-narrowed catalog |
| - | - |
| No agent card governs it | Everywhere, with `anySite: true` |
| An active agent card in allow-all mode | Everywhere, with `anySite: true` |
| An active agent card in **blocklist** mode | Everywhere, with `anySite: true` |
| An active agent card in **allowlist** mode | Only on the named hosts, listed in `websites` |
| Every governing agent card paused or at its limit | Nowhere, with neither `anySite` nor `websites` |

A blocklist card still reports `anySite: true` because the excluded hosts are the
account holder's private configuration and are never shown to the agent. A
catalog that hid the card on blocked hosts would let an agent map the blocklist by
probing. The card is offered, and the fill is refused with `merchant_blocked`.

<Note>
  A card in a URL-narrowed catalog is a **candidate, not a guarantee**. The catalog
  is a hint; the proxy's check at fill time is the authority.
</Note>

<Accordion title="Why cards may fail open when logins may not">
  A card is bounded by five further controls: the key's scope, the merchant rules,
  the destination list, the spending caps and the approval threshold. A card resolving at an unexpected
  merchant costs at most one capped transaction, and it is recorded, provided an
  agent card governs it. A **No limits** card has no spending cap or merchant rule, only the plan's
  payment quota. A password
  resolving on an unexpected site can cost the whole account. Account holders who
  want cards locked to specific merchants use allowlist mode.
</Accordion>

### Identity and addresses are not locked to one site

Name, email, phone and address tokens are not tied to a registered site the way a
login is. They resolve on any authorized destination. They are not credentials:
they are details the merchant is about to receive anyway, and a checkout commonly
spans several hosts. No call returns their values to the agent, though the
non-digit characters of one a page echoes as text can be probed, as they can
for any echoed value. See
[the digit-free echo exception](/security/agent-context#the-guarantee).

Filling address tokens can also unlock a retailer that hides its buy button until
a delivery location is chosen, before any checkout exists.

### Destinations gate everything but logins

Identity, address, card number and security code tokens resolve only on an
authorized [destination](/dashboard/destinations): a site the account holder
approved or added, a login's website, or a host on an active allowlist agent
card. For a card this is checked in addition to the agent card rules.

On any other site the fill pauses and asks the account holder. If they deny the
site or let the request expire, the fill is refused with
`destination_unauthorized` and nothing is typed. Login tokens are not
destination-gated: their own site lock above is stricter.

### Account checks

Separately from URL locking, every token carries its account's id in its own
grammar. A token from a different account is refused with `cross_user_denied`.

**Three such refusals within five minutes revoke the key automatically**, and
Agent Vault tries to notify the account holder (best effort). There is no legitimate reason for a key to present
another account's token. The agent then receives `key_auto_revoked` rather than
`key_revoked`, so it can tell why its key stopped working.

The backend repeats this check behind the proxy. Its refusals do not count toward
the three, so a single event is never counted twice.

### The protections travel with the token

The site lock, the account check, the audit record and the authenticator code all
key off the token's own grammar, not the tool that fills it. A login token filled
through `fill_checkout` is resolved under exactly the same rules as one filled
through `fill_login`.

### Plaintext pages are refused

An `http://` page or frame on any non-loopback host receives `insecure_page`,
and no value is substituted, because anyone on the network path could read it.
A frame whose TLS status cannot be established is refused the same way.

The one exception is a loopback address: `localhost`, a `*.localhost` name, any
address in `127.0.0.0/8`, or `::1`. A page served there over `http://` is
treated as secure, because the bytes never leave the machine running the proxy.
It exists for local demos. The exception is withdrawn when the deployment routes
the browser through a configured static upstream proxy, because loopback traffic
would then reach that third party. In that setup, loopback `http://` pages are
refused with `insecure_page` too.

## Refusals

| Code | Meaning |
| - | - |
| `login_blocked` | The site is not registered for this login, **or** the login has no website set. The code does not say which |
| `merchant_blocked` | An allowlist that does not name this host, **or** a blocklist that does. The code does not say which |
| `destination_unauthorized` | An identity, address, card or security code token on a site that is not an authorized destination, after the account holder denied it or let the request expire |
| `cross_user_denied` | The token belongs to another account |
| `key_auto_revoked` | The key was revoked after repeated `cross_user_denied` refusals |
| `scope_denied` | The resource exists but is outside this key's scope |
| `insecure_page` | The page or frame is not served over HTTPS, and is not a loopback `http://` page |

`login_blocked` and `merchant_blocked` are deliberately ambiguous. A refusal that
named the rule would let an untrusted agent read the account holder's
configuration.

## What this does not protect against

* **A compromised registered site.** If the login's own site is compromised, the
  lock resolves there as designed.
* **An extra host the holder adds in error.** Every host in `extraWebsites` is
  trusted for that login. Add only hosts you recognise as the site's own sign-in.
* **Cards at allowed merchants.** For cards, the lock is only as narrow as the
  agent card rules. With no agent card, or in allow-all or blocklist mode, a card
  can resolve at any merchant not excluded, within its limits, as long as the
  merchant is an authorized destination.

## How to verify

Run this with a test login and a test card. It takes about five minutes.

<Steps>
  <Step title="Fill a login token off-site">
    Open a session on any HTTPS page with a text field that is not the login's
    registered site, such as a page you control. Fill the login's `password`
    token. The fill fails with `login_blocked` and the field stays empty.
  </Step>

  <Step title="Fill it on the registered site">
    Repeat on the login's own sign-in page. The fill succeeds.
  </Step>

  <Step title="Test an allowlist">
    Put a test card under an agent card in allowlist mode naming one host. Fill
    its number token on another host. The fill fails with `merchant_blocked`.
  </Step>

  <Step title="Check the record">
    Open [Activity](/dashboard/activity). Each refusal normally appears with its
    reason. Recording is best effort, so a missing entry does not mean the fill
    went through: the error code your agent received is the proof.
  </Step>
</Steps>

## Related

<CardGroup cols={2}>
  <Card title="TOTP handling" icon="clock-rotate-left" href="/security/totp">
    Authenticator codes follow the same lock as the password.
  </Card>

  <Card title="Security model" icon="shield" href="/security/model">
    How URL locking bounds a compromised agent.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.