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

# Arquitetura do Vault

> Uma referência detalhada de como o Supabase Vault é integrado à Switchbord — abrangendo o modelo de armazenamento, os caminhos de leitura e gravação, criptografia, controle de acesso e a camada de RPC SECURITY DEFINER.

# Arquitetura do Vault

Esta página é uma referência técnica para operadores e colaboradores preocupados com segurança que desejam entender como a integração do vault da Switchbord funciona de ponta a ponta. Para instruções de configuração, veja [Configuração do Vault](/pt-BR/operations/vault-configuration).

***

## Modelo de Armazenamento

A Switchbord usa duas camadas de objetos de banco de dados para armazenar segredos de workspace:

**`vault.secrets` (nativo do Supabase)**
Os valores dos segredos efetivamente criptografados residem aqui. Cada linha contém uma coluna `secret` criptografada com pgsodium (AES-256-GCM), uma coluna `name` para identificação legível por humanos, e um `key_id` referenciando a chave pgsodium usada para a criptografia. As linhas são identificadas por um UUID.

**`app_private.workspace_secret_bindings` (personalizado da Switchbord)**
Esta tabela mapeia um workspace aos seus segredos:

```
workspace_secret_bindings
  id              uuid PRIMARY KEY
  workspace_id    uuid NOT NULL  -- referencia workspaces(id)
  secret_kind     text NOT NULL  -- 'meta-access-token', 'openai-api-key', etc.
  vault_secret_id uuid NOT NULL  -- referencia vault.secrets(id)
  created_at      timestamptz
  updated_at      timestamptz
  UNIQUE (workspace_id, secret_kind)
```

A tabela de bindings não contém, ela própria, nenhum dado sensível. É puramente um ponteiro: para o workspace X e o tipo de segredo Y, procure o segredo do vault Z. A descriptografia efetiva ocorre em `vault.secrets`.

***

## Por que o Schema `app_private`

O schema `app_private` é usado para tabelas e funções que não devem ser expostas via PostgREST (a API REST auto-gerada do Supabase). Por padrão, o PostgREST expõe apenas objetos do schema `public` (e schemas explicitamente listados em `db_schema`). Colocar `workspace_secret_bindings` em `app_private` garante que:

* Nenhum cliente anônimo ou autenticado possa executar `SELECT` diretamente na tabela de bindings via a biblioteca cliente do Supabase
* Nenhum endpoint REST para `/app_private/workspace_secret_bindings` seja gerado
* O acesso só seja possível por meio de funções explicitamente definidas que executam como `SECURITY DEFINER`

A segurança em nível de linha (row-level security) também está habilitada em `workspace_secret_bindings` como uma medida de defesa em profundidade, mas a colocação no schema `app_private` é a camada de isolamento primária.

<Info>
  O padrão de schema `app_private` é uma convenção padrão do Supabase para objetos de schema sensíveis. Ele é documentado nos próprios guias de "Row Level Security" e "Functions" do Supabase como a forma recomendada de proteger dados que nunca devem ser acessíveis diretamente.
</Info>

***

## Arquitetura do Caminho de Leitura

O caminho de leitura foi projetado para evitar exigir uma conexão direta ao banco de dados a partir do serviço worker. Todas as leituras sensíveis passam por funções RPC do Supabase definidas com `SECURITY DEFINER`.

```
Serviço worker
    |
    | supabase.rpc('sb_read_workspace_secret', { p_workspace_id, p_kind })
    |
Supabase PostgREST (camada de API REST)
    |
    | chama a função wrapper pública
    |
public.sb_read_workspace_secret(p_workspace_id uuid, p_kind text)
    |   [SECURITY INVOKER, verifica se o chamador é service_role]
    |
    | chama a função interna
    |
app_private.read_workspace_secret(p_workspace_id uuid, p_kind text)
    |   [SECURITY DEFINER, executa como o papel proprietário]
    |
    | SELECT decrypted_secret FROM vault.decrypted_secrets
    | JOIN app_private.workspace_secret_bindings ...
```

### Funções `SECURITY DEFINER`

Para garantir que os itens do vault nunca sejam expostos via o schema `public` padrão ou endpoints REST auto-gerados, a Switchbord usa o padrão de schema `app_private`.

**`app_private.read_workspace_secret`**
Junta `app_private.workspace_secret_bindings` com `vault.decrypted_secrets` para retornar o valor em texto puro. Executa como `SECURITY DEFINER`, permitindo acesso ao schema `vault`, que normalmente é restrito.

**`app_private.resolve_workspace_by_verify_token`**
Usada durante a verificação de webhook da Meta para rotear requisições ao workspace correto sem expor tokens ao cliente.

### Wrappers RPC Públicos

Como o PostgREST expõe apenas o schema `public`, fornecemos wrappers finos que aplicam a autorização:

* **`public.sb_read_workspace_secret`**: Valida `auth.role() == 'service_role'` antes de delegar à função privada.
* **`public.sb_resolve_workspace_by_verify_token`**: Segue o mesmo padrão de segurança para resolução de token.

<Warning>
  Esses wrappers rejeitam explicitamente chamadas feitas com a chave anon ou um JWT escopado ao usuário. Somente a chave de service role (`SUPABASE_SECRET_KEY`) pode invocá-los. Isso é aplicado no nível SQL por meio de verificações de `auth.role()` dentro das funções wrapper.
</Warning>

***

## Caminho de Gravação

O caminho de gravação passa pela aplicação web da Switchbord e não envolve o worker:

```
UI de Configurações (navegador)
    |
    | POST /api/settings/secrets  { kind, value }
    |   com JWT de sessão do usuário
    v
Rota de API Next.js
    |
    | valida a sessão, resolve o workspace_id
    | chama storeWorkspaceSecret(workspaceId, kind, value)
    v
storeWorkspaceSecret()  (utilitário do lado do servidor)
    |
    | Passo 1: supabase.rpc('vault_create_secret', { value, name })
    |   retorna vault_secret_id
    |
    | Passo 2: INSERT INTO app_private.workspace_secret_bindings
    |   (workspace_id, secret_kind, vault_secret_id)
    |   ON CONFLICT DO UPDATE  (upsert — substitui o binding existente)
    v
Segredo armazenado e vinculado ao workspace
```

O caminho de gravação usa uma conexão direta ao Postgres (`SUPABASE_DB_URL`) ao chamar `vault.create_secret()` de fora do servidor da aplicação, mas a rota de API Next.js trata isso usando o cliente de service role do Supabase. Quem faz self-hosting não precisa de `SUPABASE_DB_URL` no worker — apenas na aplicação web, se gravações no vault forem necessárias a partir de código do lado do servidor.

***

## Criptografia em Repouso

O Supabase Vault usa [pgsodium](https://github.com/michelp/pgsodium), uma extensão do PostgreSQL que envolve a libsodium.

* **Algoritmo:** XChaCha20-Poly1305 (uma cifra de fluxo com autenticação)
* **Gestão de chaves:** o pgsodium usa uma chave raiz derivada de um segredo armazenado fora do banco de dados (na infraestrutura de gestão de chaves do Supabase para Cloud, ou configurada separadamente para self-hosted)
* **Escopo de criptografia:** cada segredo em `vault.secrets` é criptografado com uma chave derivada da chave raiz. O texto cifrado e o ID da chave são armazenados juntos; o texto puro nunca é persistido.
* **Descriptografia:** a view `vault.decrypted_secrets` realiza a descriptografia de forma transparente usando `pgsodium.crypto_aead_det_decrypt()`. Somente papéis de banco de dados com permissão de SELECT nessa view podem ler valores em texto puro.

<Info>
  Para implantações Supabase Cloud, a chave raiz do pgsodium é gerenciada pelo Supabase e nunca é exposta aos proprietários do projeto. Para Supabase self-hosted, você é responsável por proteger a chave raiz — veja o [guia de self-hosting do Supabase](https://supabase.com/docs/guides/self-hosting) para configuração de chaves.
</Info>

***

## Resumo do Controle de Acesso

| Camada                             | Mecanismo                                  | Efeito                                                                          |
| ---------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------- |
| Schema `app_private`               | Não exposto pelo PostgREST                 | Tabelas e funções internas não podem ser acessadas via REST                     |
| RLS de `workspace_secret_bindings` | Segurança em nível de linha habilitada     | Defesa em profundidade: bindings escopados ao workspace                         |
| Wrappers `public.sb_*`             | Verificação `auth.role() = 'service_role'` | Somente o JWT de service role pode invocar leituras do vault                    |
| `vault.decrypted_secrets`          | Papel privilegiado exigido                 | Acesso direto bloqueado para papéis anon e authenticated                        |
| Funções `app_private.*`            | `SECURITY DEFINER`                         | Funções executam como o proprietário, não como o chamador — elevação controlada |

O worker autentica todas as chamadas RPC usando o JWT de service role (`SUPABASE_SECRET_KEY`). Essa chave deve permanecer secreta e nunca deve ser exposta a clientes de navegador ou registrada em logs.

***

## Referência de Migração

A integração do vault é composta por duas camadas:

**Nativo do Supabase (nenhuma ação necessária)**

* Schema `vault` e tabela `vault.secrets`
* View `vault.decrypted_secrets`
* Função `vault.create_secret()`
* Extensão pgsodium

Esses objetos estão presentes em todos os projetos Supabase Cloud e podem ser habilitados em Supabase self-hosted via `CREATE EXTENSION supabase_vault`.

**Personalizado da Switchbord (aplicado via migrations)**

* Criação do schema `app_private`
* Tabela `app_private.workspace_secret_bindings`
* Função `app_private.read_workspace_secret()`
* Função `app_private.resolve_workspace_by_verify_token()`
* Wrapper `public.sb_read_workspace_secret()`
* Wrapper `public.sb_resolve_workspace_by_verify_token()`

Esses objetos são criados pelas migrations de banco de dados da Switchbord em `supabase/migrations/`. Executar `supabase db push` aplica todas as migrations pendentes e garante que esses objetos existam.

Se você precisar inspecionar as definições de função diretamente:

```sql theme={null}
-- Verifica se o wrapper público existe
\df sb_read_workspace_secret

-- Exibe o código-fonte da função
SELECT prosrc
FROM pg_proc
WHERE proname = 'sb_read_workspace_secret';
```

***

## Considerações de Segurança para Quem Faz Self-Hosting

**Exposição da chave de service role**
A `SUPABASE_SECRET_KEY` pode chamar qualquer função RPC e contorna o RLS. Trate-a com o mesmo cuidado que uma senha raiz de banco de dados. Rotacione-a imediatamente se suspeitar de exposição.

**Chave pgsodium do Supabase self-hosted**
Se estiver executando o Supabase localmente ou em sua própria infraestrutura, a chave raiz do pgsodium deve ser definida antes que qualquer segredo seja armazenado. Se a chave for alterada ou perdida, todos os segredos do vault armazenados se tornam irrecuperáveis — não há custódia de chave (key escrow).

**Integridade das migrations**
As funções `SECURITY DEFINER` em `app_private` são tão seguras quanto o papel proprietário do banco de dados. Garanta que somente partes de confiança tenham a capacidade de redefinir ou substituir essas funções via migrations.

***

## Guias Relacionados

* [Configuração do Vault](/pt-BR/operations/vault-configuration) — configuração de self-hosting, variáveis de ambiente e solução de problemas
* [Segredos e Criptografia](/pt-BR/security/secrets-and-encryption) — política de segurança, hash de chave de API e cabeçalhos de segurança HTTP
* [Multi-Tenancy](/pt-BR/security/multi-tenancy) — modelo de isolamento de workspace
* [Autenticação](/pt-BR/security/authentication) — tratamento de JWT e modelo de sessão
