Skip to content

Latest commit

 

History

History
125 lines (104 loc) · 7.19 KB

File metadata and controls

125 lines (104 loc) · 7.19 KB

Environment authentication

The environment issues its own sessions and enforces their capabilities. Cloud identity and relay credentials belong to a separate trust boundary, described in T3 Connect. A relay token is never an environment login.

Authority survives transport changes

Pairing delegates a set of scopes. Exchanging a bootstrap credential can narrow that grant but cannot widen it. Ordinary pairing does not grant access-management or relay-management authority. Creating another pairing link requires both access:write and every scope being delegated. The auth handlers enforce this at issuance; client labels and device metadata have no authorization role.

The access read model contains pairing metadata, never recoverable pairing secrets. Only the creation response returns the raw credential. Otherwise read access to the connections list would become a way to acquire another client's authority.

Browser cookies, bearer tokens, and DPoP tokens adapt the same scoped session model. DPoP binds a token to a client's proof key; an invalid proof must fail rather than fall back to bearer authentication. The OAuth token-exchange vocabulary gives these grants a familiar meaning.

MCP clients are a separate audience

Agents T3 Code did not launch sign in to /mcp through a narrow OAuth authorization-code server (McpOAuth). It accepts loopback redirect URIs for agents on the user's machine and any HTTPS redirect for hosted agents (ChatGPT, bots). An HTTPS redirect means a link someone else sends the owner can deliver access to that someone, so the approval page names the host access goes to and approving stays the owner's call. Every client is public and proves itself with PKCE; a client that asks for a secret is registered without one. Client registration is stateless, so an unauthenticated caller cannot grow server state. Approval spends a one-time pairing code, or uses a browser session with access:write; proof-bound T3 Connect codes are refused without being spent.

The user grants either read-only access or a runtime-mode ceiling, not a scope list: MCP tools are all orchestration, and orchestration:operate alone would let an agent start a thread in full access and act through it. The result is an ordinary session with subject mcp-client. A read-only grant holds orchestration:read alone. On /mcp it passes only tools declared as reads in McpToolAccess, where every tool must declare who may call it to compile. Any other grant adds orchestration:operate and a signed ceiling. Only /mcp accepts these sessions. Every other HTTP and WebSocket path rejects that subject, because the RPC surface would let the agent act above its ceiling. Inside MCP the credential sets the limits and tool parameters only pick targets; see threadAccess.

Issuer and resource URLs come from the request's Host and X-Forwarded-Proto, so one server answers over loopback, Tailscale Serve and a T3 Connect tunnel. A proxy that rewrites Host or drops the protocol header breaks sign-in.

Bearer and DPoP clients obtain short-lived WebSocket tickets through authenticated HTTP so long-lived tokens stay out of socket URLs. Browser sessions can authenticate the upgrade with their cookie. A successful handshake grants no extra authority: every RPC declares a required scope, and the WebSocket RPC group's RpcScopeAuthorization middleware checks it before any handler runs.

Scope changes must not prevent older clients from connecting. Token exchange intersects recognized requests with the pairing grant; retired and unknown names are dropped. A request with no granted scopes fails before consuming the link. Stored credentials are never expanded when scopes split.

Auth responses keep scopes within the original wire vocabulary and include permissions for the exact grant. New clients use permissions when present, even if empty. Older servers omit it, so clients use legacy parent checks for features those servers already support. These client checks never change server authorization. Permission errors likewise retain a legacy requiredScope and add the exact requiredPermission, so a denied RPC stays decodable by old clients. Unknown response permissions are ignored; grant inputs stay strict.

Desktop restarts forget the previous local bearer token, so its reusable bootstrap grant replaces earlier sessions for the same subject and method. Revocation and insertion share a database transaction; a failed replacement must leave the old credential usable. Pairing and browser sessions do not follow this replacement rule.

Reusable dev credential

Web development environments can accept one T3CODE_DEV_AUTH_TOKEN across worktrees and ports on one hostname. The token and startup URLs that contain it grant administrative access. Desktop and non-development servers ignore it. See the development runbook for setup.

Each environment hashes the value and seeds its own database record at startup. Environments do not share SQLite data, signing keys, environment IDs, session records, pairing grants, or revocation state. Local revocation persists after restart and does not affect another worktree. Removing or rotating the value and restarting invalidates the old credential and its WebSocket tickets.

Normal credentials keep precedence. A rejected normal credential never falls back to the reusable credential. OAuth exchanges create ordinary local bearer or DPoP children with normal expiry and revocation. The reusable cookie expires after 30 days.

The environment is the filesystem boundary

Projects are organizational boundaries, not filesystem sandboxes. filesystem:read permits reading files the server account can read, including absolute paths outside a project. This lets clients display artifacts that an agent writes in a temporary directory. Relative paths and writes still follow the workspace path rules.

Signed asset URLs are bearer credentials. A URL for media on the host grants access to one canonical file and its device/inode identity, not its containing directory. Asset access rechecks the opened file's identity when serving it, so atomic replacement requires a new URL while editing the same file in place does not. An HTML file authorized this way cannot load sibling assets; directory-scoped workspace previews are a separate grant. Clients should share the authored file reference so they do not disclose the temporary URL's credential. filesystem:read is checked when the URL is minted, not when it is served: a URL issued before the grant was revoked keeps working until it expires, and it is not bound to the session that minted it.

Host videos can change in place. Their HTTP responses omit cache validators because file metadata cannot prove byte-for-byte identity for If-Range. Adding weak validators would turn native-player seeks into full downloads.