Segurança
Os aplicativos nativos do agente são projetados para serem seguros por padrão. A estrutura fornece proteções automáticas em várias camadas: você obtém isolamento de dados no nível SQL, consultas parametrizadas, validação de entrada e autenticação prontas para uso.
O que você ganha de graça e o que você possui
Quando você desenvolve os padrões padrão, a estrutura já lida com a maior parte da superfície de ameaças para você:
- Isolamento de dados — o agente SQL foi reescrito para poder ver apenas as linhas do usuário atual (e da organização ativa). Consulte Data Scoping.
- Injeção SQL —
db-query/db-exece Drizzle sempre parametrizam. Consulte SQL Injection Prevention. - XSS — React escapa automaticamente, TipTap e
react-markdownhigienizam. Consulte XSS Prevention. - Auth & CSRF — todo
defineActioné protegido por autenticação; os cookies sãohttpOnly+SameSite=lax. Consulte Authentication. - Criptografia secreta — as credenciais e o cofre são criptografados em repouso. Consulte Secrets Management.
Isso deixa uma pequena superfície na qual você realmente precisa pensar:
- A. Marque suas tabelas para definir o escopo. Adicione
owner_email(eorg_idpara dados da equipe) por meio deownableColumns()e roteie leituras/gravações de Drizzle por meio de access guards. - B. Valide e roteie a entrada externa. Dê a cada ação um Zod
schema:e envie qualquer busca do lado do servidor de um usuário/agente URL por meio do SSRF guard.
Acerte esses dois e o resto será o padrão. O Production Checklist é a confirmação de uma página antes do envio.
Segurança desde o projeto
A arquitetura da estrutura evita vulnerabilidades comuns quando você usa os padrões padrão:
| Vulnerabilidade | Proteção de estrutura |
|---|---|
| Injeção SQL | Consultas parametrizadas em db-query/db-exec e Drizzle ORM |
| XSS | React escapa automaticamente de JSX; TipTap limpa rich text |
| Vazamento de dados | Escopo no nível SQL por meio de visualizações temporárias (owner_email, org_id) |
| Ignorar autenticação | A proteção de autenticação protege automaticamente todos os endpoints defineAction |
| Injeção de entrada | Validação do esquema Zod em defineAction |
| CSRF | Biscoitos SameSite=lax + httpOnly |
| Exposição secreta | .env gitignorado; credenciais e cofre criptografados em repouso (AES-256-GCM) |
| SSRF | ssrfSafeFetch bloqueia alvos internos/metadados + religação de redirecionamento |
A estrutura cria o contexto da solicitação a partir da sessão e aplica a propriedade com accessFilter() e assertAccess() nas leituras e gravações do aplicativo. O SQL do agente recebe visualizações temporárias filtradas para a identidade e organização ativas. O RLS ainda não está habilitado, portanto consultas diretas do aplicativo devem continuar usando esses auxiliares.
Validação de entrada
Use defineAction com um Zod schema: para cada ação. A estrutura valida a entrada automaticamente antes da execução do código:
import { z } from "zod";
import { defineAction } from "@agent-native/core/action";
export default defineAction({
description: "Create a note",
schema: z.object({
title: z.string().min(1).max(200).describe("Note title"),
content: z.string().optional().describe("Note body"),
}),
run: async (args) => {
// args is guaranteed valid — invalid input never reaches here
},
});Entrada inválida retorna mensagens de erro claras (400 para HTTP, erro estruturado para chamadas de agente). O formato herdado parameters: não fornece validação de tempo de execução.
Prevenção de injeção SQL
As ferramentas db-query e db-exec da estrutura usam consultas parametrizadas. A entrada do usuário é passada como argumentos, nunca interpolada na string SQL:
// SAFE — parameterized query (framework default)
await exec({ sql: "INSERT INTO notes (title) VALUES (?)", args: [title] });
// SAFE — Drizzle ORM (always generates parameterized queries)
await db.insert(notes).values({ title, ownerEmail: email });
// DANGEROUS — string concatenation (never do this)
await exec(`INSERT INTO notes (title) VALUES ('${title}')`);Prevenção XSS
React escapa automaticamente de todas as expressões JSX. Diretrizes adicionais:
- Nunca use
dangerouslySetInnerHTMLcom conteúdo controlado pelo usuário - Nunca use
innerHTML,eval()oudocument.write() - Para edição de rich text, use TipTap (framework dependency) — ele limpa através de seu esquema
- Para renderizar markdown, use
react-markdown— ele converte em elementos React com segurança
Busca no lado do servidor (SSRF)
Qualquer fetch do lado do servidor de um URL controlado por usuário ou agente deve passar pela proteção da estrutura SSRF ou pode ser apontado para metadados de nuvem (169.254.169.254), localhost ou serviços internos:
import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
const res = await ssrfSafeFetch(userProvidedUrl, {}, { maxRedirects: 3 });ssrfSafeFetch bloqueia alvos privados/internos, verifica novamente o IP resolvido no momento da conexão (religação DNS) e revalida cada salto de redirecionamento para que um URL público não possa redirecionar para a rede privada. O proxy iframe de extensão, upload-image, e o importador de token de design passam por ele. Para uma verificação apenas antes do voo, use isBlockedExtensionUrlWithDns(url) com redirect: "manual".
Escopo de dados
Na produção, a estrutura restringe automaticamente as consultas do agente SQL aos dados do usuário atual. Isso é aplicado no nível SQL – os agentes não podem ignorá-lo. Esta seção é a referência canônica para o pipeline de escopo; os documentos Authentication e Multi-Tenancy têm link aqui para a mecânica.
O pipeline de escopo
O escopo flui da sessão autenticada até o SQL que o agente executa:
session.orgId → AGENT_ORG_ID → SQL row scopingA sessão conectada carrega email e (quando uma organização está ativa) orgId. A estrutura estabelece o contexto de solicitação dessa sessão, expõe a organização ativa ao agente SQL como AGENT_ORG_ID e reescreve cada consulta para que possa ver apenas as linhas que a identidade atual possui. O mesmo caminho se aplica quer a consulta venha do UI, de uma ação ou do agente — o agente não pode ler dados de uma organização da qual o usuário não é membro.
Escopo por usuário (owner_email)
Toda tabela com dados específicos do usuário deve ter uma coluna de texto owner_email. Use o nome da propriedade camelCase Drizzle — accessFilter lê resourceTable.ownerEmail:
import {
table,
text,
integer,
ownableColumns,
} from "@agent-native/core/db/schema";
// Minimal: just the owner column
export const notes = table("notes", {
id: text("id").primaryKey(),
title: text("title").notNull(),
content: text("content"),
ownerEmail: text("owner_email").notNull(), // REQUIRED — camelCase property
});
// Or use ownableColumns() to add owner_email + org_id + visibility in one call
export const notes = table("notes", {
id: text("id").primaryKey(),
title: text("title").notNull(),
content: text("content"),
...ownableColumns(),
});A estrutura cria visualizações SQL temporárias que filtram consultas automaticamente:
CREATE TEMPORARY VIEW "notes" AS
SELECT * FROM public."notes"
WHERE "owner_email" = 'alice@example.com';As instruções INSERT são injetadas automaticamente em owner_email quando a coluna ainda não está presente.
As ferramentas db-query, db-exec e db-patch verificam os limites dos tokens SQL e confirmam, dentro da transação, que cada nome de relação é resolvido para uma das visualizações temporárias acima. Elas recusam nomes qualificados por esquema ou banco de dados (public.<table>), relações que as visualizações não cobrem, nomes que correspondem a funções ou operadores fora de pg_catalog (incluindo extensões) e determinadas funções integradas que executam texto SQL ou acessam o estado do servidor, da sessão ou de sequências. A continuação de strings de escape do PostgreSQL não é compatível e é recusada. db-query é executado em modo somente leitura. Os agentes usam nomes de tabelas simples. O escopo é aplicado automaticamente. Essas verificações limitam o escopo das leituras e gravações SQL diretas e recusam chamadas explícitas de rotinas e conversões de tipos não aprovadas. Os valores padrão do esquema, as restrições, os gatilhos, os casts e a entrada e saída de tipos que foram revisados continuam sendo comportamentos confiáveis do banco de dados. Os apps podem reutilizar essas verificações por meio de @agent-native/core/agent-sql. Sua função readAgentSqlQuery() identifica as tabelas de origem e seus intervalos exatos no texto em consultas SELECT/WITH compatíveis, e rewriteAgentSqlQuerySources() substitui esses intervalos. O código que as chama deve limitar as origens permitidas e garantir a execução somente leitura.
Escopo por organização (org_id)
Para aplicativos multiusuário em que as equipes compartilham dados, adicione uma coluna org_id. Quando ambas as colunas estão presentes, as consultas têm escopo definido por ambas: WHERE owner_email = ? AND org_id = ?.
O auxiliar de esquema ownableColumns() adiciona owner_email, org_id e visibility em uma chamada, para que novas tabelas com reconhecimento de locatário obtenham o contrato de escopo completo por padrão:
import { table, text, ownableColumns } from "@agent-native/core/db/schema";
export const projects = table("projects", {
id: text("id").primaryKey(),
title: text("title").notNull(),
...ownableColumns(), // adds owner_email + org_id + visibility
});Proteções de acesso em actions
O agente bruto SQL tem o escopo definido pelas visualizações temporárias acima. O código de ação que consulta diretamente com Drizzle deve passar pelos auxiliares de acesso da estrutura para que as leituras e gravações permaneçam no escopo da identidade atual:
accessFilter— retorna o predicadoWHEREque limita uma consulta às linhas que o usuário/organização atual pode ver. Use-o em consultas de lista/leitura.resolveAccess— resolve o escopo de acesso efetivo (proprietário, organização, compartilhado) da solicitação atual.assertAccess— protege uma gravação ou leitura de registro único, lançando se a identidade atual não atuar na linha de destino.
As tabelas criadas com ownableColumns() exigem essas leituras e gravações com escopo definido; rotas Nitro personalizadas devem estabelecer o contexto da solicitação antes de consultar dados proprietários. A verificação guard-no-unscoped-queries (executada via pnpm guards) impõe isso no momento do CI. Veja a habilidade sharing para o ajudante completo API.
Validação
pnpm action db-check-scoping # Verifique se todas as tabelas possuem proprietário_email
pnpm action db-check-scoping --require-org # Também requer org_idGerenciamento de segredos
| Tipo de segredo | Onde armazenar |
|---|---|
| Chaves no nível da implantação (uma por aplicativo) | Arquivo .env (gitignored, somente no lado do servidor) |
| Chaves API por usuário/por organização | saveCredential/resolveCredential (criptografado em repouso) |
| Segredos registrados (cofre da barra lateral) | Cofre app_secrets (criptografado em repouso) |
| Tokens OAuth (Google, GitHub) | Armazenamento oauth_tokens via saveOAuthTokens() |
| Tokens de sessão | Automático (melhor autenticação lida com isso) |
As credenciais por usuário/organização e o cofre são criptografados em repouso com AES-256-GCM. O cofre compartilhado do workspace prefere WORKSPACE_SECRETS_ENCRYPTION_KEY, depois candidatos legados derivados de SECRETS_ENCRYPTION_KEY, A2A_SECRET e BETTER_AUTH_SECRET; durante uma rotação, WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS mantém as linhas antigas legíveis até a migração. Para criptografar linhas existentes em texto simples, execute pnpm action db-migrate-encrypt-credentials (idempotente, não destrutivo).
Nunca armazene segredos em settings, application_state, código-fonte ou respostas de ação. Use as credenciais/cofre APIs acima: elas lidam com criptografia e escopo por usuário.
Autenticação
A autenticação é automática. Consulte a documentação do Authentication para a configuração completa.
Pontos-chave para segurança:
- Os endpoints
defineActionsão protegidos automaticamente pela proteção de autenticação - Rotas
/api/personalizadas devem chamargetSession(event)e verificar o resultado - As operações de mudança de estado devem usar POST (o padrão para actions)
- Os cookies
SameSite=lax+httpOnlyevitam a maioria dos ataques CSRF
Verificação de identidade A2A
Quando os aplicativos ligam entre si por meio do protocolo A2A, eles verificam a identidade usando tokens JWT assinados com um segredo compartilhado:
A2A_SECRET=your-shared-secret-at-least-32-chars- O aplicativo A assina um JWT contendo
sub: "steve@example.com" - O aplicativo B verifica a assinatura JWT com o mesmo segredo
- O aplicativo B lê a declaração
subverificada no contexto da solicitação - O escopo dos dados se aplica: o aplicativo B mostra apenas os dados de Steve
Sem A2A_SECRET em produção, cada endpoint A2A e o endpoint de disparo automático /_agent-native/integrations/process-task retornam 503. Configure-o em todos os aplicativos que chamam ou recebem tráfego A2A. (Para desenvolvimento local, a estrutura ainda permite chamadas não autenticadas.)
Entrada Webhooks
Os manipuladores de webhook de entrada (Resend, SendGrid, Slack, Telegram, WhatsApp, Recall.ai, Deepgram, Zoom) recusam solicitações forjadas por padrão na produção: quando a variável de env secreta de assinatura correspondente está faltando, o manipulador retorna 401 em vez de aceitar e despachar.
Anteriormente, essa era uma postura de "avisar e aceitar" - defina o segredo que de outra forma estaria faltando ou opte novamente pelo comportamento antigo com AGENT_NATIVE_ALLOW_UNVERIFIED_WEBHOOKS=1 apenas para desenvolvedores locais. Consulte Messaging para obter as variáveis secretas de assinatura por integração.
As notificações changes.watch do Google Drive não passam por esse gateway genérico de adaptadores: são solicitações opacas sem corpo. Por isso, o framework registra um token aleatório por canal e verifica os cabeçalhos X-Goog-Channel-ID, X-Goog-Channel-Token e X-Goog-Resource-ID antes de buscar as alterações. São notificações diretas do Drive, não mensagens Pub/Sub, e não carregam tokens OIDC bearer do Pub/Sub.
Lista de verificação de produção
Autenticação e segredos
-
BETTER_AUTH_SECRETdefinido como uma string aleatória com mais de 32 caracteres (openssl rand -hex 32), a menos que esta seja uma implantação de espaço de trabalho hospedado derivada deA2A_SECRET -
OAUTH_STATE_SECRETdefinido como uma string aleatória separada com mais de 32 caracteres (não reutilizeBETTER_AUTH_SECRET) — consulte OAuth State Signing -
A2A_SECRETdefinido em cada aplicativo que chama ou recebe tráfego A2A — consulte A2A Identity Verification -
WORKSPACE_SECRETS_ENCRYPTION_KEYdefinido de forma consistente para cofres compartilhados multi-app; a criptografia local mantém os fallbacksSECRETS_ENCRYPTION_KEY/BETTER_AUTH_SECRET— consulte Secrets Management -
AUTH_SKIP_EMAIL_VERIFICATIONnão é definido em produção (ou definido apenas em implantações de visualização de controle de qualidade)
Segredos do webhook (defina aqueles para integrações que você usa)
- Conjunto de segredos de assinatura para cada integração de entrada habilitada — consulte Inbound Webhooks e Messaging para obter a lista por integração
-
AGENT_NATIVE_ALLOW_UNVERIFIED_WEBHOOKSnão está definido no produto
Esquema
- Cada tabela voltada para o usuário tem
owner_email, tabelas multiusuário tambémorg_id- consulte Data Scoping - Leituras/gravações de tabelas próprias passam pelo access guards
- Todos os actions usam
defineActioncom Zodschema:— veja Input Validation - As buscas do lado do servidor de usuários/agentes URLs passam por
ssrfSafeFetch— consulte SSRF - Nenhum
dangerouslySetInnerHTMLcom conteúdo do usuário (ou a saída é executada por meio do DOMPurify) - Nenhum SQL concatenado com string
-
pnpm guardsestá limpo (guard-no-unscoped-queries,guard-no-env-credentials,guard-no-env-mutation,guard-no-localhost-fallback,guard-no-unscoped-credentials,guard-no-drizzle-push) - Testado com duas contas de usuário para verificar o isolamento de dados
Endurecimento diverso
-
AGENT_NATIVE_DEBUG_ERRORSnão é definido em produção real (somente em visualizações de depuração) -
AGENT_NATIVE_KEYS_WORKSPACE_FALLBACKnão é definido, a menos que sua organização realmente compartilhe chaves de espaço de trabalho — consulte Cross-User Tooling Secrets - Em implantações multilocatários, os usuários trazem seu próprio
ANTHROPIC_API_KEY— a estrutura se recusa a voltar para o ambiente de nível de implantação var
Execute agent-native doctor (pnpm agent-native:doctor em um app gerado pelo scaffold) para verificar automaticamente os itens de esquema, credenciais e variáveis de ambiente desta checklist — cada descoberta indica o guard, o arquivo e a linha. Consulte a documentação do Doctor.
As seções abaixo abordam sinalizadores de ambiente de nicho que você só alcança em implantações específicas. A maioria dos aplicativos nunca toca neles.
Assinatura de estado OAuth
Fluxos OAuth (Google, Atlassian, Zoom) assinam seu envelope de estado com uma chave HMAC dedicada:
OAUTH_STATE_SECRET=$(openssl rand -hex 32)Isso costumava recorrer a GOOGLE_CLIENT_SECRET (uma credencial compartilhada com o Google) — um vazamento do segredo do Google teria permitido que invasores falsificassem envelopes de estado OAuth. A chave dedicada é independente de qualquer segredo de terceiros. Se OAUTH_STATE_SECRET não estiver definido, a estrutura retornará para BETTER_AUTH_SECRET; implantações de espaço de trabalho hospedado também podem derivar uma chave OAuth específica do A2A_SECRET já necessário. Se nenhum desses segredos do servidor estiver disponível, os fluxos OAuth falharão na produção.
Os parâmetros de consulta redirect_uri também são validados em uma lista de permissões (mesma origem + caminhos da estrutura /_agent-native/...). Os fluxos OAuth personalizados em modelos devem usar o auxiliar isAllowedOAuthRedirectUri() da estrutura antes de assinar o estado.
Segredos de ferramentas para vários usuários
Ferramentas e automações que fazem referência a ${keys.NAME} resolvem segredos por usuário por padrão. O substituto do escopo do espaço de trabalho está desativado por padrão nesta versão. Caso contrário, um membro mal-intencionado da organização poderia plantar um espaço de trabalho OPENAI_API_KEY e coletar chamadas API de outros membros.
Se sua organização realmente compartilha chaves para todo o espaço de trabalho (por exemplo, uma única chave Stripe corporativa), opte novamente pelo comportamento antigo com:
AGENT_NATIVE_KEYS_WORKSPACE_FALLBACK=1As gravações secretas no escopo do workspace ainda exigem a função de proprietário/administrador da organização, independentemente dessa sinalização.
Próximos passos
- Authentication — modos de autenticação, cookie realms e detalhes de sessão sobre os quais esta página se baseia
- Multi-Tenancy — o modelo de associação à organização por trás do escopo
org_id - Sharing — o padrão
ownableColumns()e de access-guard aplicado a recursos compartilháveis - Messaging — os segredos de assinatura de webhook por integração referenciados na lista de verificação acima
- Deployment — as variáveis de ambiente de produção que esta lista de verificação pressupõe estarem definidas
Traduzido automaticamente — Esta página foi traduzida automaticamente e pode não ser totalmente precisa. Ver o original em inglês