Voltar para os textos

Mascare antes de chamar o LLM

Trocar CPF por marcador é o começo. O difícil é o mapa de volta, o detector, os quasi-identificadores e a resposta que volta com o marcador errado.

Quando um sistema de saúde ou financeiro chama um modelo de linguagem externo, cada prompt pode carregar nome, CPF, diagnóstico ou valores. Se isso sai da sua infraestrutura sem tratamento, o problema deixa de ser técnico e vira risco jurídico e de confiança.

A ideia de uma camada de privacidade é simples: antes da chamada, identificar as entidades sensíveis, trocar cada uma por um marcador e guardar o mapa de volta dentro do seu perímetro. O modelo raciocina sobre os marcadores, e a resposta é reidratada só do seu lado.

entrada: Maria Souza, CPF 123.456.789-09, internada em 12/03
modelo:  [PESSOA_1], CPF [CPF_1], internada em [DATA_1]

Até aqui, todo mundo concorda. O problema é que a versão ingênua disso protege bem menos do que parece, e os buracos estão em quatro lugares.

Token estável é pseudonimização, não anonimização

A tentação é fazer o mesmo CPF virar sempre o mesmo marcador, em todas as chamadas, para o modelo “lembrar” da pessoa. Isso cria linkabilidade: quem vê as requisições consegue juntar tudo o que passou sobre o mesmo marcador e montar um perfil sem nunca ver o CPF. E o mapa de volta vira o ativo mais sensível do sistema, porque é a única coisa que separa um monte de marcadores de um monte de pacientes.

Para a LGPD, dado pseudonimizado continua sendo dado pessoal. Só deixa de ser quando a anonimização não pode ser revertida, e um mapa de volta existe justamente para reverter. Então o marcador não te tira do escopo da lei; ele só reduz o que vaza se a chamada for interceptada.

Também não adianta trocar o mapa por um hash. O CPF tem uns 10⁹ valores possíveis, e calcular o SHA-256 de todos cabe numa tarde num notebook. Hash sem chave de identificador com espaço pequeno é só um CPF escrito de outro jeito.

O mínimo que eu considero razoável:

  • Escopo por conversa ou por tenant, nunca global. Dentro de uma conversa, marcadores sequenciais (CPF_1, CPF_2) bastam.
  • Quando precisar de consistência entre chamadas, como num documento processado em pedaços, use HMAC com chave por escopo, e rotacione a chave.
  • O mapa tratado como cofre: criptografado, com acesso restrito, retenção curta e trilha de acesso própria.
token = "CPF_" + base32(HMAC(chave[tenant][conversa], cpf))[:8]

Mandar PII para detectar PII

Um conselho comum é começar pelo determinístico, com regex e validadores para documentos, e deixar um LLM para os casos que a regra não alcança. O conselho está certo, com uma condição que costuma ficar de fora: esse modelo precisa rodar dentro do perímetro. Se o detector é uma API externa, você manda o texto cru para fora justamente para descobrir o que não podia mandar para fora.

Na prática, a detecção fica toda do seu lado: regex e validadores (dígito verificador de CPF, CNS, telefone, e-mail, datas), listas de entidades conhecidas, um NER como o do Presidio com modelo em português e, se precisar, um modelo pequeno rodando local. O modelo externo só vê texto que já passou por tudo isso.

texto
  -> regex + validadores (CPF, CNS, telefone, e-mail, datas)
  -> NER / modelo local, dentro do perímetro
  -> generalização de quasi-identificadores
  -> marcadores com escopo + mapa no cofre
  -> LLM externo
  -> validação dos marcadores na resposta
  -> reidratação

Em saúde, o vazamento mora nos quasi-identificadores

Nome e CPF são a parte fácil. O que reidentifica um paciente de verdade é a combinação de atributos que, sozinhos, parecem inofensivos. Latanya Sweeney mostrou, em 2000, que CEP, data de nascimento e sexo bastavam para identificar unicamente cerca de 87% da população dos Estados Unidos. Num prontuário em texto livre a situação é pior, porque esses atributos vêm misturados com o resto.

Nenhuma regex pega “paciente de 34 anos com HIV atendido em Sorocaba”. Não tem nome, não tem documento, e numa base de uma operadora regional pode ser uma pessoa só. É aqui que mora o problema de verdade.

Substituir não resolve, porque o modelo precisa do conteúdo clínico: num resumo de caso, o diagnóstico é o assunto. A ferramenta certa é generalizar, decidindo por tarefa o que o modelo realmente precisa saber:

AtributoOriginalGeneralizado
Idade34 anos30 a 39 anos
Datainternado em 12/03[DATA_1], ou “D+3” relativo à entrada
CidadeSorocabainterior de SP
DiagnósticoHIVmantido, se a tarefa precisa dele

E dá para medir. Se, depois de generalizar, só um paciente da base combina com “30 a 39 anos, interior de SP, HIV, internado em março”, a generalização não foi suficiente. É a intuição do k-anonimato aplicada ao que sobra no texto: cada combinação que chega ao modelo deveria corresponder a várias pessoas, não a uma.

A reidratação também falha

A volta é onde ninguém olha. O modelo pode mudar o formato do marcador ([CPF_1] vira CPF 1, [cpf_1] ou CPF_01), inventar um que não existe ([PESSOA_3] quando só havia duas pessoas) ou trocar os marcadores de lugar. Um find-and-replace ingênuo deixa marcador quebrado na tela ou, pior, coloca o nome do paciente errado na resposta.

  • Marcadores com formato rígido e difícil de parafrasear, e um reconhecedor que encontra as variações que o modelo costuma produzir.
  • Todo marcador da resposta precisa existir no mapa daquela conversa. Marcador desconhecido é recusa ou nova geração, nunca palpite.
  • Marcador de outra conversa ou de outro tenant nunca resolve, mesmo que exista no cofre.
  • Depois de reidratar, nenhum marcador pode sobrar no texto.
for token in find_tokens(resposta):
    if token not in mapa_da_conversa:
        raise TokenDesconhecido(token)

Meça o que escapa

Mascaramento que ninguém testa é só uma sensação de segurança. Mantenha um conjunto de casos sintéticos que se parecem com os reais, incluindo quasi-identificadores em texto livre, meça o recall do detector por tipo de entidade e conte o que chega ao modelo externo. A métrica que importa é o que vazou, não o que foi pego.

Isso conversa com O prompt não é fronteira de segurança: uma camada cuida do que o modelo vê, a outra cuida do que ele consegue fazer.