1. Por que você precisa de auditoria e ciclo de vida (audit & lifecycle) no ChatGPT App
Enquanto você escreve um protótipo, o usuário é você mesmo, o BD é um SQLite local e “incidentes” são curados com git reset --hard, tudo parece simpático e caseiro.
Mas assim que seu GiftGenius (ou outro aplicativo ChatGPT (App)) recebe usuários de verdade, especialmente com pagamentos e PII, surgem de repente:
- a equipe de segurança do cliente perguntando “quem pode ver nossos pedidos e quem os alterou?”;
- advogados perguntando “por quanto tempo vocês armazenam dados e como cumprem a solicitação ‘exclua meus dados’?”;
- a realidade de produção perguntando “o que acontece se um desenvolvedor der DROP em uma tabela em produção?”
Nesta aula, veremos quatro blocos fundamentais:
- Logs de auditoria — uma camada separada de logging para segurança e auditoria.
- Data retention — prazos de vida para diferentes tipos de dados e como implementá-los.
- Exclusão sob solicitação do usuário — o “direito ao esquecimento” na prática.
- Business continuity & backups — como sobreviver a falhas sem perder a face e os dados.
Sempre que possível, vamos relacionar os exemplos ao nosso App didático (um GiftGenius hipotético em Next.js + Apps SDK + MCP).
2. Logs de auditoria: quem, o quê, quando e como terminou
Como os logs de auditoria diferem dos logs normais
Logs de aplicação comuns são mensagens amigáveis para o desenvolvedor. Neles vivem stack traces, informações de debug, valores estranhos de variáveis, e depurações como console.log("aqui com certeza não deve ser null"). Eles vivem pouco e são lidos por engenheiros.
Logs de auditoria — outro universo. O público principal são equipes de segurança, auditores e às vezes advogados. Eles não precisam da linha “NullPointer na linha 55”, eles precisam de um registro do tipo “o usuário X alterou as configurações de pagamento da organização Y em tal horário e o resultado foi sucesso”. Registros de auditoria geralmente vivem muito mais (anos) e são considerados evidência em caso de investigações.
Principais diferenças:
| Característica | Application Logs | Audit Logs |
|---|---|---|
| Objetivo | Depuração, diagnóstico | Segurança, compliance, investigações |
| Público | Desenvolvedores, SRE | Segurança, jurídicos, às vezes reguladores |
| Composição dos dados | Detalhes técnicos, stack trace | Quem/o quê/quando/sobre qual recurso/com qual resultado |
| Prazo de armazenamento | Semanas–1 mês | Anos (muitas vezes ≥ 1 ano) |
| Operações sobre logs | Podem ser apagados/regravados | Preferencialmente apenas append, sem UPDATE/DELETE |
OWASP e guias semelhantes destacam: é melhor manter os logs de auditoria em um armazenamento ou tabela separados, e não misturá-los com os logs normais da aplicação.
O que registrar no contexto de um aplicativo ChatGPT
Para um aplicativo ChatGPT, especialmente com comércio, o mínimo razoável de auditoria é:
- eventos de autenticação: login, logout, tentativas de acesso;
- operações com dados críticos: criação/atualização/remoção de perfis, pedidos, configurações de pagamento;
- ações administrativas: mudança de papéis, alteração de configurações do tenant;
- chamadas de ferramentas sensíveis do MCP/Agents: create_order, charge_customer, cancel_subscription etc.
Uma boa intuição: tudo o que, em caso de incidente, você vai querer perguntar “quem fez isso e por meio de quê?” deve ir para a auditoria.
Estrutura de um evento de auditoria
Um modelo mental útil: cada registro é “quem / qual ação / sobre o quê / em qual contexto / com qual resultado”. Frequentemente isso se formula com a estrutura who, action, resource, context, outcome.
Para o nosso GiftGenius, vamos descrever a interface em TypeScript:
// lib/audit.ts
export type AuditAction =
| "auth.login"
| "auth.logout"
| "order.create"
| "order.cancel"
| "account.delete"
| "giftidea.generate";
export interface AuditEvent {
eventId: string; // uuid
timestamp: string; // ISO
actor: {
userId: string | null; // pode ser null antes do login
tenantId?: string | null;
ip?: string | null;
client: "chatgpt-app" | "admin-panel" | string;
};
action: AuditAction;
resource?: {
type: string; // "order", "user", ...
id?: string;
};
context?: {
mcpTool?: string;
requestId?: string;
};
outcome: {
status: "success" | "failure";
reason?: string | null;
};
}
Observe que o evento não inclui e‑mails completos, números de cartão e outras PII, que aprendemos em aulas anteriores a mascarar e a não registrar sem necessidade.
Onde e como armazenar logs de auditoria
Requisitos mínimos para o armazenamento:
- tabela separada ou até mesmo um BD separado dos logs comuns, para ser mais difícil apagar algo por engano;
- se possível, modo append‑only: tecnicamente pode ser apenas a política “nunca fazemos UPDATE/DELETE nessa tabela”, além de um papel do BD que tenha apenas permissões de INSERT e SELECT;
- acesso restrito: nem toda a equipe de engenharia precisa ter direito de ler o audit completo.
Se você usa PostgreSQL via Prisma/Drizzle, o modelo pode ser assim (exemplo simplificado):
CREATE TABLE audit_events (
event_id uuid PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT now(),
actor_user_id text,
actor_tenant_id text,
actor_ip inet,
action text NOT NULL,
resource_type text,
resource_id text,
context_mcp_tool text,
context_request_id text,
outcome_status text NOT NULL,
outcome_reason text
);
O esquema se adapta às suas necessidades, mas o principal é a estruturação. Você vai amaldiçoar depois um lixo JSON em única linha.
Implementando a auditoria no nosso App
Vamos criar um pequeno helper no aplicativo Next.js (ambiente Node, por exemplo no servidor MCP ou em um endpoint de API):
// lib/audit.ts
import { randomUUID } from "crypto";
import { db } from "./db"; // seu cliente para o banco de dados
export async function logAudit(event: Omit<AuditEvent, "eventId" | "timestamp">) {
const full: AuditEvent = {
...event,
eventId: randomUUID(),
timestamp: new Date().toISOString(),
};
// Na vida real — via fila/background; aqui apenas inserção direta
await db.insertInto("audit_events").values({
event_id: full.eventId,
created_at: full.timestamp,
actor_user_id: full.actor.userId,
action: full.action,
outcome_status: full.outcome.status,
outcome_reason: full.outcome.reason ?? null,
// ...demais campos
});
}
Agora adicionamos a chamada ao handler que cria um pedido (imagine que seja uma ferramenta MCP ou um endpoint de servidor):
// app/api/orders/route.ts
export async function POST(req: Request) {
const user = await requireUser(req); // do módulo de autenticação
const body = await req.json();
const order = await createOrderInDb(user, body);
await logAudit({
actor: { userId: user.id, client: "chatgpt-app" },
action: "order.create",
resource: { type: "order", id: order.id },
context: { mcpTool: "create_order_tool" },
outcome: { status: "success" },
});
return Response.json(order);
}
O mesmo pode ser feito ao redor de operações perigosas — cancelamento de pedido, alteração de dados de pagamento, exclusão de conta.
Ganhamos uma camada separada e estruturada de auditoria — ótimo. A próxima pergunta natural é: por quanto tempo todos esses eventos (e os demais dados do usuário) devem viver e o que fazer com eles ao fim do prazo?
3. Data retention: por quanto tempo seus dados vivem
Por que não dá para guardar tudo para sempre
O instinto engenheiril de “vai que um dia serve” no contexto de dados de usuários é muito perigoso.
Primeiro, quanto mais e por mais tempo você acumula dados, mais graves são as consequências em caso de vazamento: quanto maior o barril de gasolina, pior o incêndio. Muitos guias de proteção de dados chamam dados de “ativo tóxico”: é preciso armazená-los com propósito, mas minimizando volume e prazo.
Segundo, a legislação como GDPR/CCPA traz o princípio “não mais do que o necessário para a finalidade do tratamento”. Ou seja, dados pessoais não podem ser mantidos indefinidamente “por via das dúvidas”. Para cada tipo de dado devem existir prazos claros de retenção e procedimentos de exclusão ou anonimização.
Terceiro, armazenamento em nuvem custa dinheiro. Tabelas grandes de logs e histórico de chats crescem rápido e, em um ano, de repente metade da fatura do provedor é “lixo de ontem”.
Dados diferentes — prazos diferentes
A experiência das empresas e guias públicos dão aproximadamente este quadro:
| Tipo de dado | Prazos típicos de retenção |
|---|---|
| Logs de debug, métricas técnicas | de 1 a 12 meses |
| Logs de auditoria | ≥ 12 meses, às vezes 2–5 anos |
| Pedidos, pagamentos, faturas | 3–7 anos (por exigências contábeis/tributárias) |
| Sessões, tokens temporários | horas–dias |
| Chats brutos / solicitações | de algumas semanas a alguns meses, ou nem armazenar |
| Agregados anônimos (análises) | por mais tempo, pois já não há PII |
Importante: isto não é consultoria jurídica, e sim referências de engenharia. Para um produto real, você vai alinhar prazos com o jurídico, mas tecnicamente já deve estar pronto para implementar diferentes TTLs.
Como implementar retention no código
O padrão mais comum: a tabela tem created_at ou expires_at, e você tem um processo periódico que exclui ou anonimiza registros antigos.
Exemplo: limpeza de logs comuns com mais de 90 dias.
// scripts/cleanup-logs.ts
import { db } from "../lib/db";
async function cleanup() {
await db
.deleteFrom("app_logs")
.where("created_at", "<", new Date(Date.now() - 90 * 24 * 60 * 60 * 1000));
console.log("Old logs removed");
}
cleanup().catch(console.error);
Esse script pode ser executado por cron, via GitHub Actions por agendamento ou com um scheduler da nuvem.
Para PII, em vez de exclusão, muitas vezes se faz anonimização. Por exemplo, pedidos mais antigos que N anos perdem o vínculo com o usuário específico:
UPDATE orders
SET user_id = NULL
WHERE created_at < now() - interval '3 years';
Assim, permanecem valores, itens e toda a “contabilidade”, mas desaparece o vínculo com a pessoa.
Não se esqueça de que backups também precisam ter prazos de vida. A periodicidade e a duração do armazenamento dos backups discutiremos à parte no bloco sobre backups, mas a ideia é a mesma: nem arquivos de arquivo devem ser mantidos para sempre, ou o “direito ao esquecimento” vira ficção.
4. Exclusão sob solicitação do usuário: “direito ao esquecimento” no código
De onde vem o requisito
O GDPR europeu (e leis semelhantes) estabelece o chamado “direito ao esquecimento”: o usuário pode exigir a exclusão de seus dados pessoais, e a empresa deve fazer isso sem demora injustificada.
Do ponto de vista do desenvolvedor, isso significa: cedo ou tarde chegará a solicitação “excluam todos os dados sobre mim” (ou você mesmo fará um botão “Delete my data”), e será preciso não só remover o registro na tabela users, mas também percorrer todo o rastro: pedidos, sessões, tokens, registros de ações, CRM, pagamentos etc.
Mas existem leis que exigem que você guarde determinados dados: transações financeiras, por exemplo. Então há mais complexidades jurídicas do que técnicas aqui.
O que exatamente deve ser limpo
Mínimo para o nosso GiftGenius:
- perfil do usuário (nome, e‑mail, configurações);
- sessões, refresh tokens, vínculos com provedores OAuth;
- pedidos, se não forem necessários “de forma personalizada” (ou se puderem ser anonimizados);
- logs e registros de auditoria onde exista PII dentro (por exemplo, e‑mail em forma bruta).
Permanecem dados importantes para relatórios, porém já desidentificados — valores de pedidos, quantidade de transações, agregados por país etc.
Exemplo de algoritmo de exclusão
Esquema do fluxo:
- O usuário (autenticado) clica em “Excluir minha conta”.
- Uma solicitação chega ao servidor com o seu userId.
- O servidor:
- exclui/anonimiza registros dependentes (pedidos, sessões, integrações);
- limpa PII no perfil;
- registra um evento no log de auditoria “solicitação de exclusão de dados processada”.
Para simplificar, mostraremos um mínimo em duas tabelas. Em um produto real, ao redor desse mesmo núcleo você adicionará entidades adicionais (integrações, serviços de terceiros etc.).
Código de serviço em Next.js (exemplo simplificado):
// app/api/delete-me/route.ts
import { db } from "@/lib/db";
import { logAudit } from "@/lib/audit";
export async function POST(req: Request) {
const user = await requireUser(req);
await db.transaction(async (tx) => {
await tx.deleteFrom("sessions").where("user_id", "=", user.id);
await tx.deleteFrom("orders").where("user_id", "=", user.id);
await tx.updateTable("users")
.set({
is_deleted: true,
name: null,
email: null,
})
.where("id", "=", user.id);
await logAudit({
actor: { userId: user.id, client: "chatgpt-app" },
action: "account.delete",
outcome: { status: "success" },
});
});
return new Response(null, { status: 204 });
}
No mundo real, você adicionará aqui chamadas a APIs externas (por exemplo, Stripe — para desvincular o customer), e também tornará a transação mais robusta. Mas o princípio está aí: tudo em um só lugar, com registro de auditoria.
Relação com backups
A parte complicada “e os backups?” traz muitas questões interessantes. Mesmo que você exclua o usuário do BD de produção, os dados dele podem permanecer nos snapshots noturnos. Para que isso não se torne “na prática nunca excluímos ninguém”, há duas abordagens:
- Os backups têm vida útil limitada (por exemplo, 30–90 dias) e, após esse período, desaparecem junto com os dados. Depois do fim da retenção, nem o BD principal nem os arquivos de backup contêm o usuário.
- Se você precisar subir o sistema a partir de um backup, mantenha um registro de IDs “excluídos” e, após a restauração, rode novamente os scripts de exclusão/anonimização.
Em empresas grandes, às vezes se pratica crypto‑shredding: a PII do usuário é criptografada com uma chave separada e, ao solicitar a exclusão, destrói-se a chave. Mesmo que existam cópias de dados criptografados em algum lugar (em logs, backups), sem a chave, são lixo inútil. É ótimo, mas para uma startup é um pouco “ciência de foguetes”.
Ponto importante de UX
Lembre-se de que a exclusão não é só SQL. O usuário espera:
- um meio claro de enviar a solicitação (botão, formulário, e‑mail);
- prazos razoáveis de execução (na prática, até 30 dias);
- notificação de sucesso ou recusa motivada (por exemplo, quando parte dos dados deve ser mantida por lei).
Do lado técnico, você já tem tudo: sabe fazer a limpeza, registrar a ação e não guardar o desnecessário em backups.
5. Continuidade de negócios e backup
Agora imagine que tudo acima funciona perfeitamente… até acontecer um fatídico DROP TABLE orders, uma falha na nuvem ou a queda de uma região. Precisamos de mecanismos para trazer o serviço de volta à vida em prazos razoáveis e não perder dados críticos.
RTO e RPO — duas letras que definem sua dor
Dois parâmetros básicos de Disaster Recovery:
- RTO (Recovery Time Objective) — quanto tempo você pode se dar ao luxo de ficar indisponível. Por exemplo, se RTO = 1 hora, significa que após uma falha séria você precisa levantar o sistema em no máximo uma hora.
- RPO (Recovery Point Objective) — quanto tempo de dados você aceita perder. Se RPO = 10 minutos, então, na restauração, é aceitável perder os últimos 10 minutos de histórico — mas não mais do que isso.
Quanto mais crítico o produto (banco, sistemas de negociação), mais ambos se aproximam de zero. Para o GiftGenius didático, dá para viver com RTO de algumas horas e RPO de ~ 15–60 minutos, mas ainda assim isso precisa ser implementado.
O que pode dar errado no seu stack
No contexto de um aplicativo ChatGPT em Vercel + BD em nuvem + APIs externas, a lista típica de dores é:
- OpenAI API indisponível: seu App responde com erros em tool‑calls.
- Vercel (ou outro) em falha: o widget não consegue alcançar seu backend.
- Banco de dados corrompido ou algo removido por engano (por exemplo, DROP TABLE).
- Conta comprometida ou com problemas, controlando a infraestrutura.
Para tudo isso, você responde com uma combinação de backups, replicações e comportamento razoável do aplicativo diante da falha.
Estratégias de backup
Postgres/BDs gerenciados modernos geralmente oferecem pelo menos três opções:
- Backups completos + incrementais.
Fazer snapshot completo do BD uma vez por dia e, entre eles, armazenar mudanças incrementais. A restauração é voltar a um snapshot específico e aplicar o log de alterações. - Point‑in‑Time Recovery (PITR).
O banco escreve o log de transações (WAL) e permite restaurar para um momento arbitrário (por exemplo, “estado às 14:03:00, antes de darmos DROP na tabela”). - Replicação em outra região.
Manter uma réplica passiva ou ativa em outra região/nuvem. Ao perder a região principal, é possível comutar o aplicativo para a réplica, perdendo apenas os dados que ainda não chegaram.
Para o nosso porte, geralmente basta ativar PITR no provedor do BD e fazer backups off‑site periódicos.
Exemplo simples: dump diário para BD local/dev
Mesmo que, em produção, você dependa de um BD gerenciado, para staging/dev às vezes vale ter um script simples:
# scripts/backup.sh
#!/usr/bin/env bash
set -e
DATE=$(date +%F)
pg_dump "$DATABASE_URL" > "backups/backup-$DATE.sql"
echo "Backup criado: backups/backup-$DATE.sql"
Você pode executá-lo via cron ou GitHub Actions. O principal — não esquecer que backups também precisam ser excluídos conforme o prazo de vida.
Comportamento do App quando serviços externos caem
Backups e PITR resolvem “o que fazer se tudo quebrou de vez ou os dados se corromperam”. Mas, no dia a dia, ocorrem mais falhas parciais — API externa fora do ar, rede cortada, gateway de pagamentos travado.
Quando OpenAI API ou o gateway caem, a pior estratégia é retornar 500 cru e um stack trace sem sentido na resposta. Idealmente:
- o backend retorna um erro estruturado como { error: "upstream_unavailable" };
- o widget mostra uma mensagem compreensível: “Serviço temporariamente indisponível, tente novamente mais tarde”;
- o sistema não continua bombardeando a API caída com retries infinitos (padrões como Circuit Breaker etc. veremos no módulo de resiliência).
Exemplo de handler de ferramenta MCP que trata erro externo:
// mcp/tools/createGiftIdea.ts
export async function createGiftIdea(args: Input): Promise<Output> {
try {
return await callOpenAiModel(args);
} catch (err) {
await logAudit({
actor: { userId: args.userId ?? null, client: "chatgpt-app" },
action: "giftidea.generate",
outcome: { status: "failure", reason: "openai_unavailable" },
});
throw new Error("UPSTREAM_UNAVAILABLE");
}
}
Depois, sua camada entre o MCP e o widget já sabe como exibir esse erro com cuidado no UI.
Teste de restauração: backup sem restore — é só um arquivo
Anti‑padrão clássico: o backup é feito diariamente, todos felizes… até descobrir que não é possível restaurar (o formato mudou, a chave foi perdida, faltou espaço).
Plano mínimo:
- periodicamente (por exemplo, mensalmente) levantar um ambiente de staging a partir de um backup;
- percorrer cenários básicos: login, criação de pedido, funcionamento do App;
- garantir que o tempo de restauração e a perda de dados se encaixem nos seus RTO/RPO.
Em vez de um curso inteiro de “religião DevOps”, nesta aula basta entender: processos de backup são parte da arquitetura do App, não “algo que alguém na nuvem fará”.
6. Visualização: ciclo de vida de dados e eventos
Para não ficar só nas palavras, vamos desenhar dois esquemas simples.
Ciclo de vida dos dados do usuário
flowchart TD
A["Criação de dados<br/>(registro, pedido)"] --> B["Armazenamento e uso<br/>(prod DB)"]
B --> C["Arquivo/agregação<br/>(métricas anônimas)"]
B --> D[Solicitação de exclusão]
D --> E[Exclusão/anonimização<br/>no prod DB]
E --> F["Término do prazo de vida dos backups<br/>(retention)"]
A ideia principal: o ciclo de vida dos dados não termina no BD de produção — ele continua também nos backups.
Fluxo de auditoria para uma ação perigosa
sequenceDiagram
participant User as Usuário
participant ChatGPT as ChatGPT
participant App as Seu backend/MCP
participant DB as Banco
participant Audit as Armazenamento de auditoria
User->>ChatGPT: "Cancele o pedido #123"
ChatGPT->>App: callTool cancel_order
App->>DB: UPDATE orders SET status='canceled'
App->>Audit: INSERT audit_event {actor, action, resource, outcome}
App-->>ChatGPT: Resultado da operação
ChatGPT-->>User: Mensagem com o resultado
7. Erros comuns em audit & lifecycle
Erro nº 1: Misturar logs de auditoria e logs normais.
Quando todas as mensagens entram em um único índice logs, em seis meses ninguém distingue “o usuário alterou o papel de administrador” de “temos novamente uma referência null”. Em auditoria devem existir eventos estruturados de nível de negócio (veja a seção sobre estrutura de evento de auditoria) e um armazenamento separado com acesso restrito.
Erro nº 2: Registrar PII em auditoria e logs de debug.
E‑mail completo, telefone, endereço de entrega, últimos quatro dígitos do cartão — tudo isso frequentemente cai por engano nos logs. Isso aumenta o risco de vazamento e contraria recomendações de privacidade. Em vez disso, registre identificadores e valores mascarados.
Erro nº 3: Ausência de política de retenção — “guardamos tudo sempre”.
Na fase de MVP isso parece “tudo bem”, e em um ano suas tabelas crescem a tamanhos monstruosos, e qualquer consulta de analytics vira um DDoS no próprio BD. Além disso, você viola o princípio de minimização, presente em leis modernas de dados. TTLs mínimos por tipo de dado devem ser pensados, e a limpeza — automatizada.
Erro nº 4: “Exclusão sob solicitação” == DELETE FROM users.
Se você apenas removeu a linha do usuário mas deixou sua PII em pedidos, sessões e logs, você essencialmente não excluiu ninguém. A abordagem correta é percorrer, de maneira transacional, todas as entidades relacionadas; onde não puder excluir — anonimizar. E não se esqueça de registrar o próprio fato da exclusão como evento de auditoria.
Erro nº 5: Ignorar os backups ao excluir dados.
Excluiu o usuário em produção — ótimo, mas os dados dele ainda vivem um ano em snapshots antigos. Ao restaurar deles, tudo “ressuscita”, e você volta a descumprir suas promessas ao usuário e à sua Privacy Policy. É preciso ou limitar o prazo de vida dos backups, ou ter um procedimento de reaplicação das exclusões após a restauração.
Erro nº 6: “Temos backups ativados, então está tudo bem”, mas ninguém tentou restaurar.
Backup que nunca foi testado em restore — é apenas um arquivo caro. Sem verificação periódica de restauração você não conhece seu RTO/RPO de fato, nem se seu plano de DR funciona. No mínimo — levantar regularmente um staging a partir de backup, seguindo um checklist.
Erro nº 7: Divergência entre documentação e realidade.
Na Privacy Policy você escreve que guarda logs por 30 dias e exclui dados sob solicitação, mas no código tudo fica para sempre. A loja do ChatGPT, clientes enterprise e auditores descobrirão isso facilmente com perguntas como “mostre a tabela de retenção” e “demonstre a exclusão de um usuário específico”. Melhor fazer primeiro, depois escrever.
GO TO FULL VERSION