CodeGym /Cursos /ChatGPT Apps /Auditoria e ciclo de vida: logs de auditoria, retenção de...

Auditoria e ciclo de vida: logs de auditoria, retenção de dados, exclusão sob solicitação, continuidade/backups

ChatGPT Apps
Nível 15 , Lição 4
Disponível

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:

  1. Logs de auditoria — uma camada separada de logging para segurança e auditoria.
  2. Data retention — prazos de vida para diferentes tipos de dados e como implementá-los.
  3. Exclusão sob solicitação do usuário — o “direito ao esquecimento” na prática.
  4. 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:

  1. O usuário (autenticado) clica em “Excluir minha conta”.
  2. Uma solicitação chega ao servidor com o seu userId.
  3. 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:

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

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

1
Pesquisa/teste
Segurança, nível 15, lição 4
Indisponível
Segurança
Segurança e conformidade
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION