Witbitz docs HomeTrustAll docs

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.

  1. 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.
  2. 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.
  3. 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

javascript
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 pass

OAuth, 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).

javascript
// 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 })

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.

javascript
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:

javascript
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.

javascript
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

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:

javascript
await admin.hosts.declare(['api.todoist.com'])   // server-side; replaces the list, so removing one is declaring without it

A 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

Machine-readable source: connections.md · Generated 2026-10-27T07:05:42Z · build f301388c · every doc in one fetch: llms-full.txt (HTML)