# Security model

The system has three edges. Each has its own credential, and they are never mixed.

| Edge | Who | Credential |
|---|---|---|
| A | Human to dashboard (`webmcp.fast`) | Emailed 6-digit code, then a session cookie |
| B | Agent harness to endpoint (`<handle>.webmcp.fast/<device>/<server>/mcp`) | OAuth 2.1 access token, or a connector token |
| C | Daemon to gateway (`/connect` WebSocket) | Ed25519 device key |

## A. The human

The code is 6 digits, stored hashed, valid for 10 minutes, single use, bound to the requesting browser by a pre-auth cookie, burned after 5 wrong attempts and rate limited per email and per IP. The session cookie is `HttpOnly`, `Secure`, `SameSite=Lax` and host-only on the apex, so tenant hostnames never see it.

## B. The agent

The apex is an OAuth 2.1 authorization server: RFC 8414 metadata, dynamic client registration (RFC 7591) and Client ID Metadata Documents, PKCE S256, one-hour access tokens and rotating refresh tokens, stored hashed. The client sends the MCP URL as the RFC 8707 `resource`. The consent screen accepts it only if it names an endpoint on a handle the signed-in user manages. The grant is bound to that one URL. The scope for a relayed endpoint is `mcp`. The management MCP server on the apex uses the separate scope `manage`.

Every method, including `initialize`, answers `401` with a `WWW-Authenticate` header naming the RFC 9728 protected-resource metadata when the request carries no valid credential. Endpoints never read cookies.

A connector token (`wmf_` and 43 characters) is the fallback for a harness that cannot open a sign-in window. It is shown once, stored as a SHA-256 hash, and bound to one handle, one device and one server alias.

## C. The daemon

The key pair is generated on the device before pairing, so approval binds to that public key: another machine that learns the pairing code gains nothing. Every connection is authenticated by signing a fresh server challenge. The key file is mode `0600`. Revocation closes the socket, and the daemon stops retrying.

## Who decides what is exposed

Only the daemon's own config, changed with `webmcp attach` and `webmcp detach` on the machine, can add a server. The gateway keeps a policy per device and alias (`enabled`, `disabled`, `pending`, `removed`) and can only narrow what the device offers.

## Limits

1 MB per request, 8 MB per result, 60 seconds per relayed call, JSON-RPC batches refused, sessions idle out after 24 hours. Plan limits are enforced when a device pairs, when a server is advertised and when a connector is granted.

## What is recorded

Per relayed call: time, device, server, method, tool name, duration, outcome. No argument or result bodies. Kept 30 days. Relayed traffic is not stored. See [privacy](https://webmcp.fast/privacy) and the overview on the [security page](https://webmcp.fast/security).
