Ir para o conteúdo

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

  1. TB-1: humano ↔ app desktop (senha-mestra digitada).
  2. TB-2: app desktop ↔ extensão (NMH process boundary).
  3. TB-3: extensão isolated world ↔ página do SUS (page world).
  4. 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

I — Information Disclosure

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

D — Disclosure of Information

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)

  1. 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.
  2. 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).
  3. T-5 (audit log adulterado) — mesma raiz que R-2. Defesa em profundidade com cifragem de detalhes.
  4. T-4 (supply chain) — pip-audit + Dependabot, mas Renovate em paralelo ainda pendente.
  5. D-1 (detecção de uso por path padrão) — usuário pode mudar.

Próximos passos para reduzir risco

  • Renovate em paralelo a Dependabot (item v0.8 BACKLOG aberto)
  • Cobertura de testes de regressão de segurança
  • Auditoria externa (item v0.9 dependente de financiamento)

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.