Modelo de ameaças do Anotae
Versão: 1.0 — 2026-05-08
Audiência: mantenedores, auditores externos, profissionais de
saúde com interesse técnico, ANPD ao avaliar conformidade LGPD.
Este modelo combina STRIDE (security threats) com LINDDUN
(privacy threats). Acompanha
docs/CRYPTO-DESIGN.md e
docs/PRIVACY-IMPACT-ASSESSMENT.md .
1. Sistema sob análise
Escopo
Anotae v0.9.0-beta4 — aplicação desktop Python/PySide6 + extensão
de navegador MV3 que assiste profissionais SUS na digitação de
atendimentos. 100% local — sem servidor central, sem nuvem.
Componentes
┌─────────────────────────────────┐
│ Profissional SUS (operador) │ trust boundary
└────────────┬────────────────────┘
│ senha-mestra (digitada)
▼
┌─────────────────────────────────┐
│ App desktop Anotae (Python) │
│ ┌────────────────────────────┐ │
│ │ UI PySide6 │ │
│ ├────────────────────────────┤ │
│ │ Vault SQLite cifrado │ │ ← dados em repouso
│ │ (sqlite3mc + ChaCha20-Poly1305) │
│ ├────────────────────────────┤ │
│ │ NMH stdin/stdout │ │
│ └─────────┬──────────────────┘ │
└────────────┼────────────────────┘
│ JSON via stdin/stdout
│ (ou HTTP loopback 127.0.0.1)
▼
┌─────────────────────────────────┐
│ Extensão de navegador (TS MV3) │
│ ┌────────────────────────────┐ │
│ │ Service worker │ │
│ ├────────────────────────────┤ │
│ │ Content script │ │ → injeta em
│ └────────────────────────────┘ │ formulários SUS
└─────────────────────────────────┘
│
▼ (página do SUS no DOM)
e-SUS APS PEC / SISREG / SEI
Trust boundaries
TB-1 : humano ↔ app desktop (senha-mestra digitada).
TB-2 : app desktop ↔ extensão (NMH process boundary).
TB-3 : extensão isolated world ↔ página do SUS (page world).
TB-4 : filesystem local ↔ disco/backup (cifragem em repouso).
Atores
Operador legítimo : profissional SUS com vault + senha-mestra.
Atacante local privilegiado : alguém com acesso físico ao PC.
Atacante local não-privilegiado : outro processo na mesma
máquina (malware, app comprometido, JS malicioso em outra aba).
Atacante de rede : man-in-the-middle, phishing, etc.
(relevante apenas se o usuário viola a regra "100% local"
enviando vault.db para nuvem).
Atacante de supply chain : modificação maliciosa de release
ou dependência.
Suposições (não modeladas)
O sistema operacional é confiável (kernel não comprometido).
A conta do usuário no SO não foi comprometida (sessão limpa).
O navegador está em versão suportada e atualizada.
O hardware não tem keylogger físico.
A senha-mestra tem entropia ≥ 60 bits (passphrase 4+ palavras
diceware ou ≥16 chars aleatórios).
2. STRIDE — Ameaças de segurança
S — Spoofing
ID
Ameaça
Mitigação
Status
S-1
Usuário malicioso assume identidade do operador real
Vault cifrado por senha-mestra; CFM 2.454/2026 obriga revisão humana antes de assinar
✅ Mitigada
S-2
Extensão maliciosa se passa pela Anotae no NMH
NMH allowlist ID determinístico (extension/chrome/manifest.json key MV3); origens validadas em manifest.json:allowed_origins
✅ Mitigada
S-3
App desktop falso responde no HTTP loopback fallback
Token efêmero gerado por boot; rejeita requests sem header correto; binding apenas em 127.0.0.1
✅ Mitigada
S-4
Site malicioso assume identidade do e-SUS/SISREG/SEI no host_permissions
Mappings só ativam em domínios na whitelist do manifest.json
✅ Mitigada
T — Tampering
ID
Ameaça
Mitigação
Status
T-1
Adulteração do vault.db em disco
ChaCha20-Poly1305 AEAD detecta tampering em descriptografia; SHA-256 do encounter individual em hash_sha256 (Migration 001)
✅ Mitigada
T-2
Modificação de mensagens NMH em trânsito
NMH usa stdin/stdout do processo filho — sem rede, sem MITM possível
✅ Mitigada por design
T-3
Adulteração de release binário em distribuição
Sigstore attestation (PEP 740) keyless via OIDC do GitHub (PR #32). Verificação por terceiros via Rekor
✅ Mitigada
T-4
Adulteração de dependência via supply chain
pip-audit + Dependabot + lock file com hashes (pyproject.toml)
⚠️ Parcial — Renovate em paralelo pendente
T-5
Adulteração do audit log local
Append-only por contrato (sem UPDATE/DELETE expostos pela API). Detalhes cifrados field-level. Não impede atacante com acesso ao SQLite raw — limitação aceita
⚠️ Defesa em profundidade
T-6
Encounter hash não-verificado ao ler
Rotina de verificação automática (parcial) ao carregar; testes em tests/security/test_crypto_invariants.py
⚠️ Cobertura parcial
R — Repudiation
ID
Ameaça
Mitigação
Status
R-1
Profissional nega ter editado encounter
Audit log no vault registra encounter.{created,updated,deleted} com timestamp + actor_cns (PR #41). Histórico append-only visível em Vault → Histórico (Ctrl+H)
✅ Mitigada
R-2
Atacante apaga audit log para esconder rastro
Audit log no mesmo vault que os dados — se apagado, vault inteiro fica suspeito (operador percebe). Não há servidor independente — limitação aceita do modelo offline
⚠️ Limitação aceita
R-3
Profissional nega ter usado IA na geração do texto
CLAUDE.md §2.3 obriga registro de uso de IA no SOAP; CFM 2.454/2026. Disclaimer obrigatório em sei_inject_dialog.py
✅ Mitigada
ID
Ameaça
Mitigação
Status
I-1
Vault.db cai em mãos erradas
ChaCha20-Poly1305 com chave derivada via Argon2id (m=64MiB t=3 p=1 default; fallback m=19MiB t=2 p=1) — força bruta inviável para senha com entropia adequada
✅ Mitigada
I-2
Logs/stack traces expõem PII em mensagens de erro
friendly_error_text() redige paths com username (PR #22); CLAUDE.md §2.1 proíbe logging de PII
✅ Mitigada
I-3
Backup contém PII em texto claro
Backups são cópias byte-a-byte do vault cifrado (PR #18) — mantém proteção. Hash SHA-256 adjacente para integridade
✅ Mitigada
I-4
Telemetria local expõe PII
Whitelist categórica de 29 eventos (anotae/core/telemetry.py); valores em extra limitados a string ≤64 chars + escalar; default OFF
✅ Mitigada
I-5
Memory dump expõe master key descifrada
secure_zero_bytes zera master key após derivar (best-effort em Python). Limitação reconhecida — Python não controla GC
⚠️ Best-effort
I-6
Side channel timing em verificação de senha
Comparação por hmac.compare_digest ao verificar token (PR de hardening anterior). Argon2id é constant-time
✅ Mitigada
I-7
Anonimização inadequada em export
3 níveis (none/weak/strong) com hash + salt (LGPD Art. 12); strong generaliza data e CIAP/CID. tests/unit/test_anonymizer.py cobre invariantes
✅ Mitigada
I-8
Strings traduzíveis (.pot) expõem PII
Análise estática só pega literais; runtime nunca entra no catálogo. Política em docs/explanation/i18n-roadmap.md
✅ Mitigada
D — Denial of Service
ID
Ameaça
Mitigação
Status
D-1
Argon2id parameters travam hardware modesto
Detecção automática de RAM em crypto.detect_kdf_params() cai para FALLBACK em <4 GB; benchmark CLI anotae kdf benchmark ajuda diagnóstico
✅ Mitigada
D-2
Vault.db gigante torna abertura inviável
Vault tipicamente <100MB para 5 anos de produção. Sem limite hardcoded — operador é responsável
⚠️ Aceita
D-3
OOM via payload absurdo na NMH/HTTP loopback
Validação de tamanho em nmh/protocol.py (limite por mensagem); HTTP loopback rejeita >1MB
✅ Mitigada
D-4
Lock SQLite congela por write longo
journal_mode WAL + retry implícito do sqlite3 stdlib
✅ Mitigada
D-5
Atacante força fallback Argon2id mais fraco
Parâmetros KDF gravados em vault_meta no momento da criação — não negociáveis em open
✅ Mitigada
E — Elevation of Privilege
ID
Ameaça
Mitigação
Status
E-1
Plugin de mapping malicioso executa código arbitrário
Plugins são apenas dados JSON (PR #35). Sem eval, sem expressão arbitrária. Engine de injeção interpreta selectors fixos
✅ Mitigada por design
E-2
Template renderer com expressão maliciosa
template_renderer.py usa whitelist de variáveis permitidas — sem code execution; usa string.Template ou regex próprio (não Jinja2)
✅ Mitigada
E-3
Extensão escala privilégios ao app desktop
NMH limita ações a uma whitelist de mensagens em nmh/protocol.py; sem comando shell arbitrário
✅ Mitigada
E-4
Subprocess no smoke E2E vaza arg injection
Argumentos hardcoded com subprocess.run([list, ...]) — sem shell=True
✅ Mitigada
3. LINDDUN — Ameaças de privacidade
LINDDUN cobre privacy threats complementares a STRIDE.
L — Linkability
ID
Ameaça
Mitigação
Status
L-1
Encounter linkado a paciente específico em export anonimizado
Hash com salt aleatório por export (anonymizer._make_handle_hash) — IDs ANON-XXXXXXXX não são reutilizáveis entre exports
✅ Mitigada
L-2
display_handle do paciente vaza identidade em vault.db
Documentado em RFC-003 — handle é controlado pelo profissional (ex.: PAC-001); recomendação não usar nome real
⚠️ Contrato com usuário
L-3
Audit log linka ações ao operador via actor_cns
Por design — auditoria interna requer identificação. CNS do operador (não do paciente) é PII fraca
✅ Aceita
I — Identifiability
ID
Ameaça
Mitigação
Status
Id-1
Vault descriptografado revela identidade real do paciente
Exigir senha-mestra para descriptografar — controle de acesso forte
✅ Mitigada
Id-2
Backup gera nome de arquivo com data identificável
Nomes vault_<timestamp>.db — apenas data, não conteúdo
✅ Mitigada
Id-3
Texto SOAP em audit details revela identidade indiretamente
Audit details são metadados estruturados (counts, IDs) — texto livre não é gravado
✅ Mitigada por design
Id-4
Telemetria local + cross-reference identifica usuário
Telemetria é só categórica + opt-in + local — atacante já no PC do usuário sabe quem é
✅ Aceita
N — Non-repudiation
ID
Ameaça
Mitigação
Status
N-1
Audit log impede o profissional de "negar" edição que fez
Por design — em contexto clínico isso é desejável (CFM exige rastreabilidade)
✅ Aceita
N-2
Sigstore attestation impede mantenedor de "negar" release
Por design — Rekor é log público append-only de identidade do signer (workflow do GitHub)
✅ Aceita
D — Detectability
ID
Ameaça
Mitigação
Status
D-1
Atacante detecta uso do Anotae por arquivo ~/.anotae/vault.db em disco
Path padronizado intencional para suporte. Avançado: usuário pode customizar via config.toml:vault_path
⚠️ Aceita
D-2
Tráfego de rede revela uso (extensão fala com app local)
Sem rede — NMH é stdin/stdout; HTTP loopback nunca sai de 127.0.0.1
✅ Mitigada por design
D-3
Atacante observa horários de uso via timestamps de modificação do vault
Recomendação no RIPD: criptografar disco inteiro (FileVault/BitLocker/LUKS) para esconder padrão
⚠️ Defesa em profundidade
Já coberto em STRIDE/I (Information Disclosure). Sem novas ameaças
únicas a LINDDUN.
U — Unawareness
ID
Ameaça
Mitigação
Status
U-1
Usuário não sabe que telemetria local existe
Default OFF; aba "Privacidade" no AboutDialog informa explicitamente; anotae telemetry status na CLI
✅ Mitigada
U-2
Usuário não sabe que audit log existe dentro do vault
Vault → Histórico (Ctrl+H, PR #37) expõe interface; docs/reference/schema.md documenta a tabela
✅ Mitigada
U-3
Usuário não sabe quais dados saem ao gerar export
Aviso amarelo no ExportEncounterDialog: "Você está exportando este atendimento para um arquivo... O Anotae nunca envia para servidor — você escolhe onde salvar"
✅ Mitigada
U-4
Usuário não sabe das implicações de sincronizar vault para nuvem
RIPD seção "Riscos" + aviso no BackupsDialog: "Não use serviços de nuvem genéricos sem revisar a política de privacidade"
✅ Mitigada
N — Non-compliance
ID
Ameaça
Mitigação
Status
Nc-1
LGPD Art. 12 (anonimização) inadequada em pesquisa
3 níveis explícitos com explicação no UI; testes invariantes em tests/unit/test_anonymizer.py
✅ Mitigada
Nc-2
LGPD Art. 7º (base legal) — uso pessoal só com consentimento
Anotae é ferramenta-do-profissional, não tem usuário paciente — base legal é "exercício regular de direitos" do profissional. Documentado no RIPD
✅ Mitigada
Nc-3
CFM 2.454/2026 — uso de IA exige registro
Disclaimer fixo no editor; campo de metadata metadata.ai_assisted: bool em encounters; sei_inject_dialog reforça humano-no-loop
✅ Mitigada
Nc-4
Anvisa SaMD classification — Anotae não é dispositivo médico
CLAUDE.md §0 + UI explicitamente marca "Anotae não é PEP, não é SaMD, não substitui julgamento clínico"
✅ Mitigada por declaração
Nc-5
AGPL §6 (acesso ao código-fonte)
AboutDialog → Licença AGPL-3.0 com texto integral + link GitHub. Sigstore prova proveniência
✅ Mitigada
4. Resumo executivo
Severidade
Ameaças mitigadas
Aceitas/Limitadas
Total
Alta
24
2
26
Média
9
4
13
Baixa
3
1
4
Total
36
7
43
Top 5 ameaças não-totalmente-mitigadas (em ordem de risco)
I-5 (memory dump da master key) — Python não controla GC,
secure_zero_bytes é best-effort. Mitigação: documentar para
usuário não rodar Anotae em sessão suspeita.
R-2 (audit log apagável por atacante com SQLite raw) —
limitação do modelo offline sem servidor independente.
Aceita; usuário percebe (vault corrompido).
T-5 (audit log adulterado) — mesma raiz que R-2. Defesa em
profundidade com cifragem de detalhes.
T-4 (supply chain) — pip-audit + Dependabot, mas Renovate em
paralelo ainda pendente.
D-1 (detecção de uso por path padrão) — usuário pode mudar.
Próximos passos para reduzir risco
5. Status de auditoria externa
A auditoria externa de segurança ainda não foi realizada
(item v0.9 do BACKLOG, dependente de financiamento).
A tabela agregada de status por ameaça vive em
docs/explanation/auditoria-checklist.md
seção "Status agregado da auditoria" — formato pronto para o
auditor preencher quando a auditoria começar:
— não auditado (default)
✅ verificado
⚠ gap parcial
❌ falha
🔄 refinamento sugerido
Após a auditoria, atualize este documento adicionando uma seção
"Histórico de auditorias" listando data, escopo, resultado.
6. Como atualizar este documento
Este threat model é vivo . Atualize quando:
Adicionar novo componente (revise trust boundaries)
Mudar protocolo NMH/HTTP loopback
Mudar parâmetros KDF
Adicionar nova fonte de plugins (mapping_plugin.py)
Auditoria externa apontar gap
Bump da versão no topo. Adicione data + assinatura GPG do mantenedor
em git tag quando promover novo modelo a release.