> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibely.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Auth & secrets

> OAuth flows, API keys, where secrets live, and how scopes work

# Auth & secrets

Connectors talk to other people's services. That requires credentials — and credentials must never end up in your app's source, in the model's context, or in plaintext on disk. This page is the contract for how Vibely handles all of that.

## Where secrets live

| Secret type                               | Stored in                                   | Visible to                                   |
| ----------------------------------------- | ------------------------------------------- | -------------------------------------------- |
| OAuth access + refresh tokens             | Vibely's managed OAuth vault                | Vibely backend at execution time             |
| Connector API keys (you paste them)       | Vibely's `secrets` table, encrypted at rest | Vibely backend at execution time             |
| Stripe / Razorpay / Resend / Inngest keys | Supabase Edge Function secrets              | Edge function runtime                        |
| Backend env keys (E2B, LLM keys)          | Vibely server's `.env`                      | Vibely backend; never injected into your app |
| Custom secrets you add                    | Supabase Edge Function secrets              | Edge function runtime                        |

Three places. None of them are your app's source code.

## What the agent never sees

The agent operates inside the sandbox. It can read and write your project files, but **it cannot read raw connector secrets**. When it generates a wrapper like `sendSlackMessage`, the call goes through Vibely's backend, which injects the live OAuth token before forwarding to Slack. The agent's prompt window never contains your Slack token.

If a tool you're using needs a key (e.g. `web_scrape` requires `FIRECRAWL_API_KEY`), that key is set on the Vibely server, not handed to the model.

## OAuth scopes and per-permission policy

When you start a new connection, expand **Advanced settings** on the connector's panel — you'll see the real OAuth scopes the provider exposes (Gmail's `gmail.readonly` / `gmail.send` / `gmail.compose`, Slack's `chat:write` / `channels:read`, Linear's read/write classes, and so on). Identity plumbing (`openid`, `email`, refresh tokens) is requested silently and isn't shown in the list.

Each scope has a three-way toggle:

| Choice    | What happens                                                                                                                                                   |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Allow** | The scope is requested at connect time and the agent can use it whenever it needs to.                                                                          |
| **Ask**   | The scope is granted, but the agent has to prompt you in chat the first time an action would exercise it.                                                      |
| **Deny**  | The scope is **stripped from the OAuth request**. The agent's permission gate then blocks the matching class — `deny` wins over `allow` inside the same class. |

Scopes marked with a **Required** pill are non-negotiable — they're either identity plumbing or the minimum capability the connector advertises (e.g. Gmail `gmail.readonly`). You can't deny them; Vibely keeps their class allowed even if you Deny another scope in the same class.

The three buckets the permission gate enforces at runtime are **read / write / send**. Your per-scope choices collapse into one policy per class on connect — you don't have to manage that mapping by hand.

You can re-open Advanced settings to audit (or revoke) scopes per connector at any time from the **Connectors** drawer. Revoking is immediate.

### Verifying credentials before connecting

For API-key connectors, Vibely makes a probe call against the provider before saving the key, so a typo or expired secret fails loudly on the spot instead of breaking the first agent turn that tries to use it.

### Bring your own OAuth client

A handful of MCP-class connectors require a Dynamic Client Registration step (Figma, for example). The new-connection panel surfaces a **Bring your own OAuth client** block where you paste `client_id` (and `client_secret` where applicable) — Vibely uses that pair for the handshake instead of a shared Vibely-owned app. The block is marked *required* when the provider mandates it, *advanced* when it's optional.

## API-key connectors

Some services don't support OAuth (e.g. an internal API, a niche SaaS). For these:

1. You paste the key in the **Connectors** drawer when you add the connector.
2. Vibely encrypts it at rest with a workspace-scoped key.
3. At runtime, the connector layer decrypts the key in memory and uses it for the call. The decrypted value never lands on disk or in logs.

Rotation: paste a new key in the drawer; the old one is overwritten and invalidated. There's no separate rotation UI because there's no separate stored history.

## MCP auth

[MCP / Chat connectors](/docs/connectors/mcp) authenticate independently of App connectors — connecting Linear as an MCP server doesn't give your deployed app Linear access, and vice versa.

| MCP connection type          | How it authenticates                                                     | Where the credential lives                             |
| ---------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------ |
| Default-URL service, OAuth   | OAuth dance handled by the MCP server (Notion, Linear, Slack, Figma, …)  | Vibely's managed OAuth vault, scoped per workspace     |
| Default-URL service, API key | Paste the key in the **Chat / MCP** tab                                  | Vibely's encrypted secrets store, scoped per workspace |
| **Custom** MCP server        | Paste the endpoint URL + optional bearer token in the **Chat / MCP** tab | Vibely's encrypted secrets store, scoped per workspace |

For custom MCP servers, the bearer token (when set) is sent as `Authorization: Bearer <key>` on every probe and tool call.

**MCP tokens are scoped per workspace, not per project.** Every project in the workspace can use a connected MCP server. Revoking a connection from the Connectors drawer invalidates it for all of them, immediately. There's no per-project MCP override today — if you need a service connected differently for one project, fork the workspace.

## Workspace scoping

Connectors are scoped to your **workspace**, not your project. That means:

* Once you connect Slack to your workspace, every project in that workspace can use it.
* Different workspaces have independent connections — connecting Slack in workspace A doesn't expose anything to workspace B.
* Removing yourself from a workspace removes your access to its connectors but not the connectors themselves (other workspace admins can re-grant).

## App-side secrets vs build-side secrets

Some apps need a secret at **runtime** that isn't a connector — e.g. a private API your app talks to directly, a webhook signing secret, a third-party HMAC key.

Add these via the **Secrets** panel (project → Backend → Secrets). They land in Supabase Edge Function environment, accessible via `Deno.env.get("MY_SECRET")`. The agent will reference them by name in code:

```ts theme={null}
const apiKey = Deno.env.get("INTERNAL_API_KEY")
```

The agent **never** writes the literal value into a file. If you spot a hard-coded key in the diff, that's a bug — please file an issue.

## What if a secret leaks?

A few defenses, in order:

1. **Vibely detects common secret formats** (Stripe `sk_live_…`, AWS keys, Slack `xoxb-…`) in any file the agent writes and refuses the write. This catches the majority of accidental commits.
2. **Pre-publish security scan** runs `security_scan` automatically before you publish. It re-scans for keys, leaked PII, and unsafe patterns.
3. **GitHub secret scanning** is enabled on exported repos by default — GitHub will tell you (and the issuing service, where supported) if a key sneaks past us.

If a secret does leak, rotate it at the source first (Stripe dashboard / Slack admin / etc.), then update the value in Vibely's drawer. The compromised key is invalidated immediately on rotation.

## Self-hosting / BYOK

For workspaces with stricter compliance needs:

* **Bring your own LLM keys.** Set `FIREWORKS_API_KEY` / `GOOGLE_AI_API_KEY` / `ANTHROPIC_API_KEY` on your Vibely deployment. Calls to LLMs go directly with your billing.
* **Bring your own E2B.** Self-host E2B (`E2B_DOMAIN=https://your-control-plane`) so sandboxes never leave your network.
* **Bring your own Supabase.** Vibely never needs the managed Supabase Cloud; pointing at a self-hosted Supabase is a config swap.

This is what makes the [Configuration → Self-hosting](/docs/configuration/self-hosting) path possible — every external dependency can be replaced with one you control.
