Hoje acrescentei uma prática ao nosso manifesto, na wiki da Codeminer42: todo projeto precisa manter um threat model no repositório. Nessa conversa, descobri que muita gente não conhecia o conceito.

Pedir o documento não basta: falta explicar o que ele registra e como começar. Os exemplos públicos e a skill que disponibilizamos ajudam nisso.

1. Seguro contra o quê?

Se você me diz que um sistema é seguro, ainda falta informação. Seguro contra alguém na internet? Contra outro cliente autenticado? Contra uma conta de suporte comprometida? O que essa pessoa conseguiria ler, alterar ou interromper?

Um threat model, ou modelo de ameaças, descreve o sistema do ponto de vista da segurança. Nele, a gente registra o que quer proteger, quem pode causar dano, quais premissas assume e como lida com as ameaças. Também escreve contra o que o sistema não oferece proteção.

Pensa na portaria de um prédio. A pessoa passou pelo porteiro e entrou. Isso dá a ela direito de entrar em todos os apartamentos? O entregador, o morador e quem tem uma cópia roubada da chave chegam a lugares diferentes. Dizer que “o prédio tem portaria” não responde quais portas cada um consegue abrir.

No software, “tem login” deixa a mesma pergunta em aberto.

Adam Shostack organiza o processo em quatro perguntas, também adotadas pelo Threat Modeling Manifesto. Em tradução livre:

  1. Em que estamos trabalhando?
  2. O que pode dar errado?
  3. O que vamos fazer a respeito?
  4. Fizemos um trabalho bom o suficiente?

A última pergunta exige voltar às outras. Você mudou uma integração, passou a guardar dados pessoais ou abriu uma API pra outro público? Precisa conferir se o modelo ainda descreve o sistema.

2. O que entra nesse documento

Imagine uma aplicação em que várias empresas guardam documentos, cada uma com seus usuários. É um exemplo fictício.

Os documentos são ativos, coisas que queremos proteger. As sessões dos usuários e a disponibilidade da aplicação também entram nessa lista. Pra cada ativo, escreva o que acontece se a proteção falhar: um documento vaza, alguém usa a sessão de outra pessoa ou todo mundo fica sem conseguir trabalhar.

Os atores incluem visitantes anônimos, usuários autenticados, administradores e serviços externos. Uma conta legítima pode ser usada pra causar dano. Não dá pra resumir os atores a “usuário” e “hacker”.

As fronteiras de confiança, ou trust boundaries, são os pontos em que mudam as permissões ou as suposições sobre os dados. O navegador mandar um ID não autoriza o servidor a devolver aquele documento. O backend precisa verificar se o usuário pode acessá-lo.

Navegador -> API -> Banco de dados
             |
             +-> Serviço externo de processamento

Esse desenho ajuda a começar, mas as caixas não bastam. Quem escreve no banco? O que vai pro serviço externo? A resposta dele pode determinar quem recebe acesso? Dois processos na mesma rede podem estar em lados diferentes de uma fronteira de confiança.

Nesse exemplo, a ameaça é um usuário de uma empresa conseguir ler documentos de outra. A falta de uma verificação de pertencimento seria uma vulnerabilidade: a fraqueza que permite esse acesso. O risco combina a probabilidade de isso acontecer com o impacto que causaria, considerando os controles existentes.

Por isso o documento precisa ir além da lista de bugs. Corrigir a consulta fecha aquela vulnerabilidade, mas a ameaça continua relevante. Uma rota nova pode permitir o mesmo acesso indevido.

O modelo também registra os controles que já funcionam. No nosso exemplo, a API verifica o vínculo do usuário com a empresa antes de buscar o documento. Escreva onde essa verificação acontece e onde ela não acontece. Proteger a rota HTTP não garante que um worker ou um canal de WebSocket aplique a mesma regra.

Pra procurar ameaças de forma sistemática, a OWASP apresenta o STRIDE: falsificação de identidade, adulteração, repúdio de ações, divulgação de informação, negação de serviço e elevação de privilégio. São categorias pra orientar perguntas sobre cada fronteira, não seis campos pra preencher com qualquer coisa.

NOTA: o threat model pode ser feito durante o desenho, antes de existir código. Num sistema que já está rodando, ler a implementação ajuda a confrontar as premissas com o que foi construído. Não substitui testes, auditorias ou um pentest.

3. Dois exemplos públicos

ai-memory: guardar texto não o torna confiável

O ai-memory, do Fabio Akita, guarda memória de longo prazo pra agentes de programação. Seu SECURITY.md tem um threat model com a premissa, o que está no escopo e o que fica fora.

Uma das ameaças descritas é a prompt injection em conteúdo armazenado. Um texto malicioso entra na memória e depois volta numa busca ou numa passagem de contexto entre agentes. Se o agente obedecer àquele texto, o ataque chegou até ele pela memória.

O documento explica que o sistema marca o conteúdo recuperado como histórico não confiável. Sanitização e formatos estruturados ajudam, mas não provam que o modelo vai ignorar qualquer tentativa de manipulação. A descrição da defesa já diz até onde dá pra contar com ela.

O diretório de dados depende das permissões do sistema operacional; a aplicação não oferece criptografia em repouso própria. O serviço também não foi desenhado pra ficar exposto diretamente à internet hostil.

Na versão consultada, os projetos são abertos por padrão, e o operador pode restringir por projeto o acesso dos usuários cadastrados no banco. Dentro de um projeto, porém, não há permissões por página. Identidades fornecidas por um proxy confiável seguem outra fronteira de autorização, descrita no documento.

Se você precisa que duas pessoas só possam ler determinadas páginas dentro do mesmo projeto, esse controle não atende. Ter múltiplos usuários não basta pra concluir que há esse isolamento.

Syncthing: conteúdo e metadados são coisas diferentes

O Syncthing sincroniza arquivos entre dispositivos. Sua página Security Principles não usa o título Threat Model, mas registra ameaças, controles e premissas que cumprem esse papel.

O tráfego entre dispositivos usa TLS, e a conexão verifica a identidade do dispositivo contra uma lista de dispositivos aceitos. Um servidor de retransmissão, o relay, não consegue inspecionar o conteúdo transmitido. Mas conhece os identificadores dos dispositivos que conecta. Com descoberta global habilitada, o operador do servidor de descoberta consegue relacionar identificadores a endereços IP e deduzir conexões.

Proteger o conteúdo não significa esconder que você usa o sistema ou com quem seus dispositivos se conectam.

A documentação também explica que quem obtém as chaves TLS e a configuração de um dispositivo pode se passar por ele e acessar arquivos sincronizados nos pares. Por isso, recomenda revogar dispositivos perdidos e proteger a pasta de configuração, não apenas os arquivos sincronizados.

Perguntar se o operador do relay pode ler seus arquivos é diferente de perguntar o que ele sabe sobre a conexão ou o que acontece se alguém roubar as chaves do seu notebook. O produto é o mesmo, mas as respostas e os controles mudam.

4. Uma skill pra escrever a primeira versão

Disponibilizamos a threat-model no repositório público de skills da Codeminer42. Você pode instalá-la com:

npx skills add Codeminer42/skills --skill threat-model

Depois, no repositório que você tem autorização pra analisar, peça ao agente:

Use a skill threat-model pra criar o modelo de ameaças deste projeto.
Confirme comigo a premissa e o escopo antes de escrever.

A skill começa procurando um modelo existente. Se o projeto ou o cliente já mantém um, ela respeita o formato e só o atualiza quando isso foi pedido. Também verifica a visibilidade do repositório antes de escrever ameaças abertas.

Depois, combina a premissa com você: quais aplicações entram, quem pode alcançá-las, quais sistemas externos escrevem nos dados e o que seria mais grave perder. Quem conhece o negócio precisa responder. O código não decide qual risco o time aceita.

Daí o agente lê as rotas e as verificações de autenticação e autorização. Examina bancos, integrações, arquivos, configuração e logs, além do caminho de build e deploy. Quando há suporte a subagentes, divide a leitura por área. Depois confere as evidências e reúne os achados, juntando os que pertencem à mesma ameaça.

O resultado é um rascunho em docs/threat-model.md, com ativos, atores, fronteiras, ameaças e controles. Cada ameaça precisa apontar os arquivos e funções que sustentam a análise. A skill também adiciona uma referência nas instruções do agente, como AGENTS.md, pra que ele consulte o documento nas próximas mudanças.

Nas execuções da skill em projetos reais, os documentos separaram o que o código mostrava das perguntas ainda abertas sobre o ambiente. Também registraram até onde os controles existentes protegiam o sistema. O time recebe uma lista do que conferir, em vez de um parecer genérico sobre segurança.

Por exemplo, numa situação hipotética, o agente lê uma rota sem autenticação, mas não consegue ver as regras de acesso da rede. O documento precisa separar o que está demonstrado no código do que depende da configuração. “Não encontrei essa regra no repositório” não significa “confirmei que ela não existe em produção”.

A skill distingue esses casos. Se o código já demonstra o problema, a ameaça pode ficar como Open, com uma linha Verify pro que falta conferir. Se o dano depende de uma configuração ou de um comportamento que a leitura não mostra, fica como To verify.

A skill não corrige o código, não testa ambientes implantados, não aceita riscos e não faz commit. Você recebe um rascunho pra revisar, não uma auditoria concluída. Alguém precisa abrir as evidências de cada ameaça, conferir as premissas e discutir a severidade com o time.

5. O arquivo precisa participar do trabalho

No formato que adotamos, ativos recebem IDs como A1, atores X1, fronteiras B1 e ameaças T1. Esses IDs não são renumerados nem reaproveitados.

Isso permite escrever numa descrição de PR:

Esta mudança toca B1 e mitiga T1.
A consulta agora verifica o vínculo com a empresa antes de devolver o documento.
O teste cobre a tentativa de acesso de um usuário de outra empresa.

O PR do exemplo também é fictício. Com esses IDs, quem revisa abre T1, entende a ameaça e confere se a mudança resolve o que promete.

A ameaça corrigida continua no modelo com status Mitigated e a referência da correção. Se o time decidir conviver com o risco, o status vira Accepted, com um ADR, um registro de decisão arquitetural, explicando por quê. O agente pode sugerir uma mitigação, mas quem responde pelo sistema é que pode aceitar o risco.

Também registramos onde a correção precisa acontecer. Às vezes dá pra resolver tudo neste repositório. Em outras, parte da mudança depende de uma integração, da configuração de acesso ou de outro time. O modelo precisa dizer o que depende dessa correção externa e o que a aplicação consegue fazer pra limitar o dano enquanto isso.

Quem adicionar uma integração, rota ou tabela compartilhada atualiza as fronteiras no mesmo PR. Durante o code review, a gente consulta os controles existentes: uma simplificação aparentemente inocente pode remover a verificação que impede uma ameaça já conhecida.

NOTA: manter o modelo perto do código não significa publicar vulnerabilidades abertas. Em repositórios públicos, premissas, controles e limites podem ser públicos, enquanto achados sensíveis seguem o processo privado de segurança. A skill pergunta antes de escrever essas ameaças. Não coloque credenciais, dados pessoais ou detalhes de clientes no documento.

Comece com o que você sabe

Num projeto novo, comece com a premissa, um desenho pequeno e os riscos dos primeiros fluxos. Se o sistema já existe, descreva como ele funciona hoje e anote as dúvidas. Se o cliente já mantém um modelo, use-o em vez de criar um concorrente.

Mesmo incompleto, o documento já ajuda. Você registra quem escreve nos dados, quem pode lê-los e quais verificações controlam esse acesso. Daí consegue discutir uma mudança com mais precisão do que “acho que está seguro”.

Ainda pode haver ameaças que ninguém considerou. Mas o time consegue ler o que foi pensado e verificado, e o que falta resolver.

Por hoje é só.