Security and permissions
What a credential can reach, what refuses it, and where each decision is made. This is the operational picture; the security programme is at /security.
7 min
Two ways to authenticate
Prefer the first.
| Browser OAuth | Minted token | |
|---|---|---|
| How | The client opens a browser; you sign in and approve | A ck_mcp_… bearer you paste into a config |
| Best for | Any client that can open a browser | Headless — CI, a container, a server script |
| Revoke | Disconnect the client | Revoke the token in Settings → Developers |
| Lifetime | Refreshed automatically | Long-lived until revoked |
OAuth is the default and the recommendation, because a token is a long-lived secret somebody has to store and remember to revoke. The token form exists because a headless box cannot open a browser — not as a convenience.
Scopes
Three, and they nest.
| Scope | Grants |
|---|---|
rpc:read | Read the workspace — list, get, search. |
rpc:write | Create and modify. Includes read. |
rpc:admin | Operator surfaces — cross-workspace dispatch, and reaching the local machine. |
Scope is checked at the boundary, on every call. It is separate from the allowlist, which decides whether an action is reachable by an agent at all — both have to pass.
A credential is bound to one workspace
And, for MCP, to one agent.
https://relay.clearly.sh/mcp # the workspace this login belongs to https://relay.clearly.sh/mcp/w/<workspaceId> # a specific workspace https://relay.clearly.sh/mcp/w/<workspaceId>/a/<agent> # …acting as a specific agent
The workspace is in the URL rather than in ambient session state, because a client stores one credential per server entry — two workspaces behind a single entry collide, and authenticating the second silently overwrites the first. A distinct URL is a distinct credential key, which is what makes two live workspaces possible with no client change.
Asking for a workspace you are not a member of, and asking for one that does not exist, return the same refusal. Two different messages would make the endpoint an enumeration oracle for workspace ids.
Where the gate lives
One place, and it fails closed.
Membership is checked in the edge worker, against the same roster your account lists — not inside the workspace. That is not a preference: a workspace receives gateway calls without a caller identity attached, so it has no way to learn who is asking. The worker is the last layer that holds the real user, so if the check were not there, nothing downstream would catch it.
What refuses, and why
The boundaries worth knowing before you hit one.
| Boundary | Refuses unless |
|---|---|
Reaching /local — your own machine, through the daemon | the caller holds rpc:admin. A bearer token is long-lived and pasteable into a third-party client; an in-app agent driving your laptop is a person driving their own machine. Different risk, same verb. |
| Addressing another workspace | the caller holds rpc:admin — otherwise a write-scoped token would reach every workspace in the deployment. |
| The approval family | a person does it. An agent approving its own edit is a self-approval hole. |
| An agent holding a live session | never — an agent principal may not hold a socket. Membership confers identity and attribution, not a session. |
| An unbound MCP credential | it names an agent. One that did not was silently rebound to the workspace default, so the log said what happened and never who asked. |
Agents cost no seat
Which is exactly why the role is guarded.
An agent is a workspace member with its own role, and it is invisible to seat counting — otherwise a single-seat plan would spend its only seat on a robot and refuse the owner’s first real invite. Because that role is free and carries full write, it is unreachable from the human invite path: admitting a person under it, or an agent under a human role, is refused in both directions and at the chokepoint rather than at each call site.
Reporting something
If you believe you have found a vulnerability, email [email protected] rather than opening a public issue. The security programme, including what is delivered versus roadmap, is at /security.