Connections — credentials the agent can spend but never see
A connection lets a Space's assistant call an outside service — GitHub, Google Calendar, an MCP server — with your user's credential, while no one can read that credential: not your page, not the room's members, not the model, not Witbitz. The credential lives in an attested enclave that can use it but never hands it out.
Status: Production. The SDK surface (
wb.connections,space.connections) is part of the SDK, in public beta.
How it works
Credentials live in the standing Sentinel: an always-on AWS Nitro enclave whose measurement is published. Each credential is sealed to a key only that measured image can open (AWS KMS, pinned to its PCR0), stored sealed, and spent only inside the enclave.
- Attaching checks the enclave first. Before sealing anything, the SDK verifies a fresh Nitro attestation against the pinned measurement. A Sentinel running any other image is refused and nothing is sent.
- Your page keeps a handle, not the secret. After attaching, the page holds only the connection's id, label and host, plus a call key. The secret never comes back.
- A call names the connection, not the credential. The room says "call host X with connection Y". The enclave attaches the secret, makes the request, strips anything credential-like from the reply and returns the body. Every call carries a proof of spend — an HMAC with a per-connection key the enclave issued — so a captured request cannot be replayed onto another connection.
The same path serves confidential and standard rooms: a confidential room's turn enclave reaches the Sentinel over an attested hop of its own.
Who owns a connection
A connection belongs to an account: the verified email on the user's sign-in token, never a field your page names. A sign-in made on your site is scoped to your app — connections attached there live under an account of your app's own, and your page cannot see or spend the user's connections from other apps. Your app needs no accounts of its own; the SDK signs users in with a one-time email code (sign in).
Three kinds of connection
| Kind | What it holds | How it is attached |
|---|---|---|
| API key | A bearer token for one host | wb.connections.attach({ label, host, secret }) |
| OAuth | A refresh token from your OAuth app; refreshed inside the enclave | startConnect → consent screen → finishConnect |
| MCP server | A server's own sign-in, run inside the enclave, or a token you hold | startMcp → consent → finishMcp, or attachMcp |
An API key
const gh = await wb.connections.attach({ label: 'GitHub', host: 'api.github.com', secret: token })
await wb.connections.list() // [{ id, label, host }] — never a secret, never a call key
await wb.connections.detach(gh.id, { spaces: [space] }) // everywhere at once, and out of each Space you passOAuth, through your own OAuth app
The OAuth app is yours, not ours. Your users consent to your app, on your consent screen. Witbitz never asks a provider for a scope on anyone's behalf, so Witbitz needs no provider verification — and neither do you, unless your scopes are sensitive (Gmail, for one, needs Google's assessment, for your app).
// your server, once (the tenant's secret key — never in a page)
await admin.providers.attach({ kind: 'google', clientId, clientSecret, redirectUri, scopes })
// your page
const { authUrl, state } = await wb.connections.startConnect({ providerId })
location.assign(authUrl) // the user consents
// on your callback page:
const conn = await wb.connections.finishConnect({ state, code })- The enclave keeps the secrets. It mints the PKCE verifier and holds your client secret, so the code your callback receives is useless to anyone else. Only the account that started a consent can finish it.
- The callback page is yours to host. There is no Witbitz-hosted one.
- The consent URL and the hosts a provider may call are measured. They are a table inside the enclave image, so no
provider record can send a user to an unmeasured consent screen or open a host. Today it holds Google, whose
connections may call
www.googleapis.com. A new provider is a new enclave image. - Refresh is safe under load. One refresh per connection at a time, a rotated refresh token is always stored, and a refresh that fails to save leaves the previous record intact. Revoking the provider ends every connection under it.
An MCP server
The server's own sign-in runs inside the Sentinel, which registers with the server and keeps the PKCE verifier. Your page gets a URL and a state; the code it receives travels sealed back to the Sentinel. The server's token never reaches your page.
const st = await wb.connections.startMcp({ url: 'https://mcp.linear.app/mcp', label: 'Linear' })
let conn
if (st.needsSignIn) {
location.assign(st.authUrl) // your callback page (default <your origin>/mcp-callback) receives ?code=&state=, then:
conn = await wb.connections.finishMcp({ state, code, label: 'Linear', host: st.host })
} else {
conn = await wb.connections.attachMcp({ url: 'https://mcp.deepwiki.com/mcp', label: 'DeepWiki' }) // or { token }
}
await wb.connections.mcpTools(conn.id) // { tools: [{ name, title, description, readOnly, … }], server }Most MCP servers register a client during sign-in (Linear, Notion, Sentry, DeepWiki) and need nothing from you. Some
need an OAuth app registered with the service — GitHub, Asana, Box, and Google's own MCP servers. For those the SDK
signs in with Witbitz's registered app today. You can file your own now, with your page's /mcp-callback as its
callback; the SDK will use it once it supports it:
await admin.providers.attachMcp({
issuer: 'https://github.com/login/oauth',
clientId: 'Iv1.…', clientSecret: '…', // sealed to the attested Sentinel
redirectUris: ['https://app.example.com/mcp-callback'], // one per origin your app runs on
label: 'GitHub',
})Granting a connection to a Space
Nothing is granted by default. A grant writes the connection into the Space's sealed configuration, and only that Space's assistant may spend it.
await space.connections.grant(gh) // an API key or OAuth connection
await space.connections.grant(conn) // an MCP server, with its recommended tools
await space.connections.grant(conn, { tools: ['list_issues', 'create_issue'], auto: ['list_issues'] })Only to a verified Space. In an open Space the link is the whole key: whoever holds it could start a turn and spend
the connection. So a grant needs an email-gated Space (otherwise gate_required). A private lane of a verified Space
is gated to its member automatically; gate a lane of an open Space yourself before granting:
space.privateLane({ admission: { emails: [me] } }).
Detach revokes everywhere. wb.connections.detach(id) revokes the record in the enclave, which stops every room at
once.
What the assistant gets
- An API key or OAuth connection becomes one tool,
connection_call: method, path, headers and body against the connection's host. The assistant names the connection by its label or host. - An MCP server becomes its tools, each named
<label>__<tool>.
A call through an API-key or OAuth connection is always a proposal a member approves. An MCP tool is too, unless it is in the
connection's auto set — chosen by whoever grants the connection to the room (the member in the Spaces app, or your page
with grant(conn, { auto })); by default it is the tools the server itself marks read-only, a claim the server makes and
Witbitz does not verify. An auto call still goes through the Sentinel: the model never sees the credential, and the
connection's host and scope still apply. Only after approval (or for an auto tool) does the enclave make the call (proposals, end to
end). The model sees the reply's status, content type and body (capped); it never
sees the secret or the call key.
Which hosts a connection may call
A connection may call a host on the enclave's measured list — GitHub, the Google APIs, TrustedRouter, Tinfoil and the package registries — or a host your tenant has declared:
await admin.hosts.declare(['api.todoist.com']) // server-side; replaces the list, so removing one is declaring without itA declaration is checked when a key for that host is attached, not later on the first call. Two firewalls enforce it — one inside the measured enclave, reading your declarations from its own sealed store, and one outside it that asks the enclave about any host its fixed list does not name. Both must agree and both deny by default, so a host nobody vouches for is refused.
Not yet
- One OAuth provider. Only Google is in the enclave's provider table; another provider is a new enclave image.
- Your own app for MCP sign-in. An app filed with
admin.providers.attachMcpis kept but not used by the SDK yet.
Read next
- The SDK — sign in and connections: the full surface, with sessions and vault mode.
- Outside agents: other AI agents talking to a member's assistant, and agents that take jobs — a connection the assistant calls to start an agent's run.
- Proposals, end to end: the approval a call goes through.
- The attested tier: how measurement and PCR pinning work — the same mechanism the Sentinel uses.
- Verify it yourself.
Machine-readable source: connections.md · Generated 2026-10-27T07:05:42Z · build f301388c · every doc in one fetch: llms-full.txt (HTML)