Right to be forgotten: implementando exclusão de dados em sistemas

1. Fundamentos Legais e Conceituais

O direito ao esquecimento, consagrado no artigo 17 do GDPR (Regulamento Geral de Proteção de Dados da União Europeia) e no artigo 18 da LGPD brasileira, garante ao titular dos dados o direito de solicitar a exclusão de suas informações pessoais dos sistemas de um controlador. Este direito não é absoluto: existem exceções como cumprimento de obrigação legal, exercício de defesa em processos judiciais e interesse público.

Para o desenvolvedor, a distinção entre três conceitos é crucial:

  • Anonimização: processo irreversível que impede a identificação do titular. Dados anonimizados não são mais considerados dados pessoais.
  • Pseudonimização: substituição de identificadores diretos por pseudônimos, mantendo a possibilidade de reidentificação controlada.
  • Exclusão física: remoção definitiva dos bits do armazenamento, sem possibilidade de recuperação.

A LGPD estabelece que o controlador (quem decide o tratamento) é o principal responsável por atender solicitações de exclusão, enquanto o operador (quem processa os dados) deve executar tecnicamente as ordens do controlador.

2. Desafios Técnicos na Exclusão de Dados

Dados em backups e logs

Backups e logs representam o maior desafio técnico. A política de retenção deve prever janelas de exclusão que respeitem o direito ao esquecimento sem comprometer a integridade do sistema. A abordagem recomendada é:

  1. Manter backups com ciclo de vida máximo definido (ex: 90 dias)
  2. Implementar exclusão diferida: dados marcados para exclusão em backups são removidos na próxima restauração ou rotação
  3. Para logs, utilizar anonimização automática após período de retenção legal

Referências cruzadas e chaves estrangeiras

A exclusão de um registro pode quebrar a integridade referencial do banco de dados. Estratégias incluem:

  • Exclusão em cascata (CASCADE DELETE) para dependências diretas
  • Atualização de chaves estrangeiras para NULL quando possível
  • Criação de tabelas de histórico com referência ao ID do titular, que são limpas no mesmo processo

Sistemas legados e dados não estruturados

E-mails, documentos em servidores de arquivos e mensagens em sistemas legados frequentemente não possuem metadados de titularidade. A solução passa por:

  • Implementar crawlers que varrem repositórios em busca de padrões de dados pessoais
  • Criar APIs de integração para sistemas legados aceitarem comandos de exclusão
  • Utilizar ferramentas de DLP (Data Loss Prevention) para mapear dados não estruturados

3. Estratégias de Implementação de Exclusão Lógica

Soft delete com timestamp de exclusão

A exclusão lógica é a abordagem mais segura para sistemas em produção. Consiste em marcar o registro como excluído sem removê-lo fisicamente do banco de dados principal.

-- Exemplo de tabela com soft delete
CREATE TABLE usuarios (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(100),
    email VARCHAR(100) UNIQUE,
    excluido_em TIMESTAMP NULL,
    excluido_por VARCHAR(100),
    motivo_exclusao TEXT,
    data_expiracao_exclusao TIMESTAMP
);

-- Query para filtrar registros ativos
SELECT * FROM usuarios 
WHERE excluido_em IS NULL;

Shadow tables para auditoria

Tabelas separadas mantêm um registro imutável das exclusões realizadas, servindo tanto para auditoria quanto para possível reversão controlada.

-- Tabela de auditoria de exclusão
CREATE TABLE auditoria_exclusoes (
    id SERIAL PRIMARY KEY,
    id_titular INTEGER NOT NULL,
    dados_excluidos JSONB,
    solicitado_em TIMESTAMP DEFAULT NOW(),
    concluido_em TIMESTAMP,
    hash_verificacao VARCHAR(64),
    status VARCHAR(20) DEFAULT 'pendente'
);

Mecanismos de expurgo agendado

Dados marcados para exclusão com soft delete devem ser fisicamente removidos após um período de carência (ex: 30 dias), permitindo recuperação em caso de solicitação equivocada.

-- Cron job de expurgo (execução diária)
DELETE FROM usuarios 
WHERE excluido_em IS NOT NULL 
  AND data_expiracao_exclusao < NOW();

4. Exclusão Física e Segurança da Informação

Sobrescrita segura

Excluir um arquivo apenas remove o ponteiro do sistema de arquivos, deixando os dados recuperáveis. Para dados sensíveis, utilize sobrescrita segura (shredding) que escreve padrões aleatórios sobre os setores do disco.

# Exemplo com ferramenta shred no Linux
shred -vfz -n 3 /caminho/para/arquivo_sensivel.csv

# -v: verbose (mostra progresso)
# -f: força permissões de escrita
# -z: adiciona uma passada com zeros ao final
# -n 3: número de passadas de sobrescrita

Gerenciamento de chaves criptográficas

Quando dados estão criptografados, a exclusão da chave de descriptografia é equivalente à exclusão dos dados, desde que a chave seja destruída de forma segura e irreversível.

-- Exemplo: destruição de chave em HSM (Hardware Security Module)
-- Comando conceitual para AWS KMS
aws kms schedule-key-deletion \
    --key-id alias/minha-chave-usuarios \
    --pending-window-in-days 7

Exclusão em ambientes cloud

Snapshots, réplicas e armazenamento redundante podem conter cópias dos dados mesmo após a exclusão do volume principal. É essencial:

  • Identificar todos os recursos que armazenam dados do titular
  • Utilizar políticas de ciclo de vida que expirem snapshots automaticamente
  • Verificar se provedores de cloud oferecem garantias contratuais de exclusão (ex: AWS Glacier, Azure Blob Storage)

5. Implementação Prática com Exemplos de Código

Exemplo 1: Fluxo de exclusão com soft delete e auditoria

-- Função para exclusão com registro de auditoria
CREATE OR REPLACE FUNCTION solicitar_exclusao_usuario(
    p_id_usuario INTEGER,
    p_solicitante VARCHAR(100),
    p_motivo TEXT
) RETURNS BOOLEAN AS $$
DECLARE
    v_dados JSONB;
BEGIN
    -- Captura dados atuais para auditoria
    SELECT row_to_json(usuarios.*)::jsonb INTO v_dados
    FROM usuarios WHERE id = p_id_usuario;

    -- Insere registro na tabela de auditoria
    INSERT INTO auditoria_exclusoes (
        id_titular, dados_excluidos, solicitado_em, status
    ) VALUES (
        p_id_usuario, v_dados, NOW(), 'pendente'
    );

    -- Marca como excluído logicamente
    UPDATE usuarios SET 
        excluido_em = NOW(),
        excluido_por = p_solicitante,
        motivo_exclusao = p_motivo,
        data_expiracao_exclusao = NOW() + INTERVAL '30 days'
    WHERE id = p_id_usuario;

    RETURN TRUE;
END;
$$ LANGUAGE plpgsql;

Exemplo 2: Exclusão em cascata com verificação de dependências

-- Função que verifica dependências antes de excluir
CREATE OR REPLACE FUNCTION excluir_usuario_completo(
    p_id_usuario INTEGER
) RETURNS TABLE(tabela TEXT, registros_afetados INTEGER) AS $$
DECLARE
    v_count INTEGER;
BEGIN
    -- Verifica pedidos pendentes do usuário
    SELECT COUNT(*) INTO v_count FROM pedidos 
    WHERE id_usuario = p_id_usuario AND status = 'pendente';

    IF v_count > 0 THEN
        RAISE EXCEPTION 'Usuário possui % pedidos pendentes', v_count;
    END IF;

    -- Exclui dados de endereços
    DELETE FROM enderecos WHERE id_usuario = p_id_usuario;
    GET DIAGNOSTICS v_count = ROW_COUNT;
    RETURN QUERY SELECT 'enderecos'::TEXT, v_count;

    -- Exclui dados de telefones
    DELETE FROM telefones WHERE id_usuario = p_id_usuario;
    GET DIAGNOSTICS v_count = ROW_COUNT;
    RETURN QUERY SELECT 'telefones'::TEXT, v_count;

    -- Anonimiza dados do usuário principal
    UPDATE usuarios SET 
        nome = '[EXCLUIDO]',
        email = CONCAT('excluido_', id, '@dominio.invalido'),
        cpf = '[EXCLUIDO]'
    WHERE id = p_id_usuario;

    RETURN QUERY SELECT 'usuarios'::TEXT, 1;
END;
$$ LANGUAGE plpgsql;

Exemplo 3: Validação de identidade antes da exclusão

-- API de exclusão com autenticação forte
-- Pseudocódigo para validação em duas etapas

POST /api/v1/direito-esquecimento/solicitar
Headers:
  Authorization: Bearer <token_acesso>
  X-Idempotency-Key: <uuid_unico>

Body:
{
  "motivo": "Revogação de consentimento",
  "metodo_verificacao": "email_token",
  "token_verificacao": "8f3a1b2c..."
}

-- Fluxo de validação:
-- 1. Verificar token de acesso (JWT com escopo de exclusão)
-- 2. Validar token de verificação enviado por e-mail/SMS
-- 3. Registrar solicitação com chave de idempotência
-- 4. Iniciar processo assíncrono de exclusão
-- 5. Retornar ID da solicitação para acompanhamento

6. Logs e Audit Trails Imutáveis para Exclusão

Registro de solicitações

Cada solicitação de exclusão deve gerar um registro imutável contendo:

-- Estrutura de log de solicitação de exclusão
CREATE TABLE logs_exclusao (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    id_titular_hash VARCHAR(64) NOT NULL,  -- hash do ID do titular
    data_solicitacao TIMESTAMP NOT NULL DEFAULT NOW(),
    canal_solicitacao VARCHAR(50),  -- web, email, suporte
    status_processo VARCHAR(20),    -- recebida, processando, concluida, falha
    hash_anterior VARCHAR(64),      -- hash do registro anterior (encadeamento)
    hash_atual VARCHAR(64)          -- SHA256 dos dados deste registro
);

Uso de hashes encadeados para integridade

-- Função que insere log com hash encadeado
CREATE OR REPLACE FUNCTION inserir_log_exclusao(
    p_id_titular_hash VARCHAR(64),
    p_canal VARCHAR(50),
    p_status VARCHAR(20)
) RETURNS UUID AS $$
DECLARE
    v_id UUID;
    v_hash_anterior VARCHAR(64);
    v_dados_para_hash TEXT;
BEGIN
    -- Obtém o hash do último registro
    SELECT hash_atual INTO v_hash_anterior 
    FROM logs_exclusao 
    ORDER BY data_solicitacao DESC 
    LIMIT 1;

    -- Gera novo ID
    v_id := gen_random_uuid();

    -- Concatena dados para hash
    v_dados_para_hash := CONCAT(
        v_hash_anterior, 
        p_id_titular_hash, 
        p_canal, 
        p_status
    );

    -- Insere registro com hash encadeado
    INSERT INTO logs_exclusao (
        id, id_titular_hash, canal_solicitacao, 
        status_processo, hash_anterior, hash_atual
    ) VALUES (
        v_id, p_id_titular_hash, p_canal, 
        p_status, v_hash_anterior, 
        encode(sha256(v_dados_para_hash::bytea), 'hex')
    );

    RETURN v_id;
END;
$$ LANGUAGE plpgsql;

7. Testes e Validação do Processo de Exclusão

Testes unitários para remoção em todas as tabelas

-- Teste: verificar se exclusão remove dados de todas as tabelas
BEGIN;
    -- Executa exclusão
    PERFORM excluir_usuario_completo(123);

    -- Verifica tabelas de dados pessoais
    ASSERT (SELECT COUNT(*) FROM enderecos WHERE id_usuario = 123) = 0,
        'Endereços não foram excluídos';

    ASSERT (SELECT COUNT(*) FROM telefones WHERE id_usuario = 123) = 0,
        'Telefones não foram excluídos';

    -- Verifica anonimização do registro principal
    ASSERT (SELECT nome FROM usuarios WHERE id = 123) = '[EXCLUIDO]',
        'Nome do usuário não foi anonimizado';

    -- Verifica registro de auditoria
    ASSERT (SELECT COUNT(*) FROM auditoria_exclusoes 
            WHERE id_titular = 123 AND status = 'concluido') > 0,
        'Auditoria não registrou a exclusão';
ROLLBACK;

Testes de integração com sistemas de backup

-- Teste: verificar exclusão em ambiente com replicação
-- 1. Inserir dados de teste no banco primário
-- 2. Aguardar replicação para o secundário
-- 3. Executar exclusão no primário
-- 4. Verificar se dados também foram excluídos no secundário
-- 5. Restaurar snapshot de backup e verificar exclusão

8. Monitoramento Contínuo e Compliance

Métricas de SLA para exclusão

A LGPD não define prazo específico, mas o GDPR estabelece resposta em até 30 dias (prorrogável por mais 60). Estabeleça métricas internas mais rigorosas:

-- Métricas de SLA
-- Tempo médio para conclusão: < 24 horas
-- Tempo máximo para conclusão: < 72 horas
-- Percentual de solicitações dentro do SLA: > 99.5%
-- Taxa de falhas no processo de exclusão: < 0.1%

Alertas para falhas no processo

-- Consulta para detectar falhas no expurgo
SELECT COUNT(*) AS solicitacoes_pendentes,
       MIN(data_solicitacao) AS mais_antiga
FROM auditoria_exclusoes
WHERE status = 'pendente'
  AND data_solicitacao < NOW() - INTERVAL '48 hours';

Relatórios periódicos para auditoria

-- Relatório mensal de exclusões
SELECT 
    DATE_TRUNC('month', data_solicitacao) AS mes,
    COUNT(*) AS total_solicitacoes,
    COUNT(*) FILTER (WHERE status = 'concluido') AS concluidas,
    COUNT(*) FILTER (WHERE status = 'falha') AS falhas,
    ROUND(AVG(EXTRACT(EPOCH FROM (concluido_em - data_solicitacao)) / 3600), 2) 
        AS tempo_medio_horas
FROM auditoria_exclusoes
GROUP BY mes
ORDER BY mes DESC;

Implementar o direito ao esquecimento exige mais do que simples comandos DELETE. Requer um ecossistema que equilibre requisitos legais, integridade dos dados, segurança da informação e performance operacional. A combinação de exclusão lógica com expurgo físico agendado, logs imutáveis e testes rigorosos forma a base de um sistema compliant e resiliente.

Referências

ão segura


Sobre o autor:
Este artigo foi elaborado para a categoria Segurança para Devs, abordando aspectos práticos e legais da implementação do direito ao esquecimento em sistemas de software. A implementação correta desse direito não apenas evita sanções regulatórias, mas também fortalece a confiança dos usuários na plataforma.