Checklist de auditoria interna pré-externa
Diátaxis: explanation. Audiência: auditor externo de segurança
contratado para revisar o Anotae antes da v1.0 GA.
Este documento mapeia cada uma das 43 ameaças do
THREAT-MODEL.md para a evidência concreta
(arquivo, teste, commit) que o auditor pode verificar.
Para cada item:
- ID — referência STRIDE ou LINDDUN do threat model.
- Mitigação declarada — o que dizemos que fazemos.
- Onde verificar — arquivo + linha aproximada.
- Como auditor confirma — comando ou inspeção sugerida.
- Status auditoria — preenchido pelo auditor externo
(vazio até agora — auditoria pendente, dependente de
financiamento conforme item v0.9 BACKLOG).
Este checklist é vivo. Atualize quando uma mitigação mudar de
arquivo ou ganhar novo teste.
Legenda de Status auditoria
| Símbolo |
Significado |
— |
Não auditado ainda (default) |
✅ |
Auditor verificou e mitigação está correta |
⚠ |
Auditor identificou gap — mitigação parcial ou incompleta |
❌ |
Auditor identificou falha — mitigação não funciona como declarado |
🔄 |
Auditor sugeriu refinamento — issue/PR aberto para tratar |
Quando preencher: auditor adiciona uma linha após cada tabela com
texto livre justificando ⚠/❌/🔄, com referência (data, número de
relatório de auditoria). Mudanças nesse arquivo passam por PR
normalmente — auditor envia patch.
Como o auditor preenche
-
Cloning + setup (repositório fechado nesta fase — acesso
concedido individualmente pelo mantenedor ao auditor convidado; após
v1.0 o clone funciona sem convite):
git clone https://github.com/carlosefdeavila/anotae.git
cd anotae
bash scripts/maintainer-onboard.sh # ~5 min
-
Para cada linha: rodar o "Comando auditor" da tabela e
verificar a evidência. Adicionar coluna "Status" preenchida
com símbolo apropriado.
-
Patch: PR com tabelas atualizadas + nova subseção
"Resultado da auditoria — \<DATA>" listando ⚠/❌/🔄 em
ordem de severidade.
STRIDE
S-1 — Usuário malicioso assume identidade do operador
| Item |
Detalhe |
| Mitigação |
Vault cifrado por senha-mestra; CFM 2.454/2026 obriga revisão humana |
| Onde verificar |
anotae/persistence/vault.py:open_vault linha ~110 + sei_inject_dialog.py |
| Comando auditor |
pytest tests/integration/test_vault.py -k password |
S-2 — Extensão maliciosa se passa pela Anotae no NMH
| Item |
Detalhe |
| Mitigação |
Extension ID determinístico via key MV3 + allowed_origins em manifest |
| Onde verificar |
extension/chrome/manifest.json (key fixa) + anotae/nmh/manifest.py:ALLOWED_ORIGINS_CHROME |
| Comando auditor |
python -m anotae extension-info (mostra ID esperado) + verificar que chrome://extensions mostra mesmo ID |
S-3 — App falso responde no HTTP loopback
| Item |
Detalhe |
| Mitigação |
Token efêmero; binding 127.0.0.1; rejeita request sem header |
| Onde verificar |
anotae/http_bridge/server.py |
| Comando auditor |
pytest tests/integration/test_http_bridge.py |
S-4 — Site malicioso assume identidade do e-SUS/SISREG/SEI
| Item |
Detalhe |
| Mitigação |
host_permissions whitelist em manifest.json |
| Onde verificar |
extension/chrome/manifest.json:host_permissions |
| Comando auditor |
jq '.host_permissions' extension/chrome/manifest.json |
T-1 — Adulteração do vault.db em disco
| Item |
Detalhe |
| Mitigação |
ChaCha20-Poly1305 AEAD detecta tampering; SHA-256 por encounter em hash_sha256 |
| Onde verificar |
anotae/persistence/crypto.py:encrypt_field + compute_encounter_hash |
| Comando auditor |
pytest tests/security/test_crypto_invariants.py |
T-2 — MITM em mensagens NMH
| Item |
Detalhe |
| Mitigação |
Por design — stdin/stdout do processo filho, sem rede |
| Onde verificar |
anotae/nmh/handler.py |
| Comando auditor |
grep -n "socket\|http\|net" anotae/nmh/ (esperado: zero matches) |
T-3 — Adulteração de release binário
| Item |
Detalhe |
| Mitigação |
Sigstore PEP 740 keyless via OIDC do GitHub Actions |
| Onde verificar |
.github/workflows/build-windows.yml:Generate Sigstore attestation (PR #32) |
| Comando auditor |
bash scripts/release-attestation.sh verify dist/Anotae-windows-x64.zip |
T-3b — Adulteração de release não detectada por chave comprometida
| Item |
Detalhe |
| Mitigação |
Build reproduzível via SOURCE_DATE_EPOCH permite verificar byte-a-byte |
| Onde verificar |
.github/workflows/build-windows.yml:Build desktop app (PR #53) |
| Comando auditor |
bash scripts/verify-reproducible.sh (clonar release, rodar, comparar SHA-256) |
T-4 — Adulteração de dependência via supply chain
| Item |
Detalhe |
| Mitigação |
pip-audit + Dependabot + lock file com hashes; SBOM CycloneDX |
| Onde verificar |
pyproject.toml + .github/dependabot.yml + dist/sbom.json (gerado em CI) |
| Comando auditor |
bash scripts/generate-sbom.sh && jq '.components | length' dist/sbom.json |
| Item |
Detalhe |
| Mitigação |
Append-only por contrato API; details cifrados field-level (defesa em profundidade) |
| Onde verificar |
anotae/persistence/audit_log_repository.py (sem UPDATE/DELETE expostos) |
| Comando auditor |
grep -n "UPDATE\|DELETE FROM audit_log" anotae/ (esperado: zero matches) |
| Limitação |
Atacante com acesso ao SQLite raw pode editar — limitação aceita do modelo offline |
T-6 — Hash do encounter não verificado ao ler
| Item |
Detalhe |
| Mitigação |
Verificação automática (parcial) ao carregar |
| Onde verificar |
anotae/persistence/repository.py |
| Comando auditor |
pytest tests/security/test_crypto_invariants.py -k hash |
| Status |
Cobertura parcial — gap reconhecido |
R-1 — Profissional nega ter editado encounter
| Item |
Detalhe |
| Mitigação |
Audit log no vault registra encounter.{created,updated,deleted} com timestamp + actor_cns |
| Onde verificar |
anotae/persistence/audit_log_repository.py + ui/views/audit_history_dialog.py (Ctrl+H) |
| Comando auditor |
Abrir vault, criar encounter, ir em Vault → Histórico (Ctrl+H), confirmar entrada |
R-2 — Atacante apaga audit log para esconder rastro
| Item |
Detalhe |
| Mitigação |
Limitação aceita — modelo offline sem servidor independente |
| Onde verificar |
docs/THREAT-MODEL.md seção R-2 |
| Comando auditor |
Verificar que documentação aceita explicitamente esta limitação |
R-3 — Profissional nega uso de IA na geração
| Item |
Detalhe |
| Mitigação |
Disclaimer obrigatório em sei_inject_dialog; metadata.ai_assisted |
| Onde verificar |
anotae/ui/views/sei_inject_dialog.py:DISCLAIMER + anotae/core/models.py:EncounterMetadata |
| Comando auditor |
grep -n "ai_assisted" anotae/core/models.py |
I-1 — Vault.db cai em mãos erradas
| Item |
Detalhe |
| Mitigação |
ChaCha20-Poly1305 + Argon2id (DEFAULT m=64MiB t=3 p=1) |
| Onde verificar |
anotae/persistence/crypto.py:DEFAULT_TIME_COST (=3) e DEFAULT_MEMORY_COST (=65536) |
| Comando auditor |
python -m anotae kdf benchmark --target 0.5 (verifica no hardware do auditor) |
I-2 — Logs/stack traces expõem PII
| Item |
Detalhe |
| Mitigação |
friendly_error_text() redige paths com username (PR #22) |
| Onde verificar |
anotae/core/error_messages.py:redact_home_paths |
| Comando auditor |
pytest tests/unit/test_error_messages.py -k redact |
I-3 — Backup contém PII em texto claro
| Item |
Detalhe |
| Mitigação |
Backups são cópias byte-a-byte do vault cifrado |
| Onde verificar |
anotae/persistence/backup.py:create_backup (usa shutil.copy2) |
| Comando auditor |
bash scripts/smoke-e2e.py --keep + file dist/.../backups/*.db (verificar magic bytes SQLite cifrado) |
I-4 — Telemetria local expõe PII
| Item |
Detalhe |
| Mitigação |
Whitelist de 29 eventos categóricos; valores em extra ≤64 chars + escalar; default OFF |
| Onde verificar |
anotae/core/telemetry.py:ALLOWED_EVENTS + _validate_extra |
| Comando auditor |
pytest tests/unit/test_telemetry.py -k pii |
I-5 — Memory dump expõe master key
| Item |
Detalhe |
| Mitigação |
_secure_zero_bytes zera master key após derivar (best-effort) |
| Onde verificar |
anotae/persistence/crypto.py:_zero_master |
| Comando auditor |
grep -n "_zero_master\|secure_zero" anotae/persistence/ |
| Limitação |
Python não controla GC — best-effort. Documentar para usuário não rodar em sessão suspeita |
I-6 — Side channel timing em verificação de senha
| Item |
Detalhe |
| Mitigação |
hmac.compare_digest ao verificar token; Argon2id é constant-time |
| Onde verificar |
anotae/persistence/vault.py:_verify_password |
| Comando auditor |
grep -n "compare_digest\|compare_bytes" anotae/persistence/vault.py |
I-7 — Anonimização inadequada em export
| Item |
Detalhe |
| Mitigação |
3 níveis (none/weak/strong) com salt aleatório; k-anonymity opcional (PR #51) |
| Onde verificar |
anotae/core/anonymizer.py (anonymize_encounter, apply_k_anonymity) |
| Comando auditor |
pytest tests/unit/test_anonymizer.py tests/unit/test_k_anonymity.py |
I-8 — Strings traduzíveis (.pot) expõem PII
| Item |
Detalhe |
| Mitigação |
xgettext só pega literais; runtime nunca entra |
| Onde verificar |
scripts/i18n-extract.sh + locale/anotae.pot |
| Comando auditor |
grep -i "cns\|cpf\|paciente" locale/anotae.pot (esperado: zero ou só chaves de UI) |
D-1 — Argon2id trava hardware modesto
| Item |
Detalhe |
| Mitigação |
Detecção automática de RAM em detect_kdf_params() cai para FALLBACK em <4GB |
| Onde verificar |
anotae/persistence/crypto.py:detect_kdf_params |
| Comando auditor |
pytest tests/unit/test_crypto.py -k kdf_params |
D-3 — OOM via payload absurdo
| Item |
Detalhe |
| Mitigação |
Validação de tamanho em nmh/protocol.py; HTTP loopback rejeita >1MB |
| Onde verificar |
anotae/nmh/protocol.py:MAX_MESSAGE_SIZE + anotae/http_bridge/server.py |
| Comando auditor |
grep -n "MAX_MESSAGE_SIZE\|max_size" anotae/nmh/ anotae/http_bridge/ |
D-5 — Atacante força fallback Argon2id
| Item |
Detalhe |
| Mitigação |
Parâmetros KDF gravados em vault_meta no momento da criação — não negociáveis em open |
| Onde verificar |
anotae/persistence/vault.py:_load_vault_meta |
| Comando auditor |
pytest tests/integration/test_vault.py -k kdf_params |
E-1 — Plugin de mapping malicioso
| Item |
Detalhe |
| Mitigação |
Plugins são apenas dados JSON; sem eval (PR #35) |
| Onde verificar |
anotae/core/mapping_plugin.py + anotae/core/mapping_engine.py |
| Comando auditor |
grep -n "eval\|exec\|subprocess" anotae/core/mapping_*.py (esperado: zero matches) |
| Item |
Detalhe |
| Mitigação |
Whitelist de variáveis; string.Template ou regex próprio (não Jinja2) |
| Onde verificar |
anotae/core/template_renderer.py:ALLOWED_VARS |
| Comando auditor |
grep -n "Jinja\|eval" anotae/core/template_renderer.py (esperado: zero) |
E-3 — Extensão escala privilégios ao app
| Item |
Detalhe |
| Mitigação |
NMH whitelist de mensagens em protocol.py |
| Onde verificar |
anotae/nmh/protocol.py:MessageType enum |
| Comando auditor |
grep -n "MessageType" anotae/nmh/protocol.py |
E-4 — Subprocess vaza arg injection
| Item |
Detalhe |
| Mitigação |
Argumentos hardcoded com subprocess.run([list, ...]); sem shell=True |
| Onde verificar |
Toda chamada a subprocess no codebase |
| Comando auditor |
grep -rn "shell=True" anotae/ scripts/ (esperado: zero) |
LINDDUN
L-1 — Encounter linkado a paciente em export anonimizado
| Item |
Detalhe |
| Mitigação |
Hash com salt aleatório por export; IDs ANON-XXXXXXXX não reutilizáveis cross-export. Suite anti-rainbow (PR #66) garante 10 propriedades. |
| Onde verificar |
anotae/core/anonymizer.py:_hash_truncate + tests/security/test_anonymizer_anti_rainbow.py |
| Comando auditor |
pytest tests/security/test_anonymizer_anti_rainbow.py -v |
Id-1 — Vault descriptografado revela identidade real
| Item |
Detalhe |
| Mitigação |
Senha-mestra obrigatória |
| Onde verificar |
anotae/persistence/vault.py:open_vault |
| Comando auditor |
Tentar abrir vault sem senha — esperar SecurityError |
Id-3 — Texto SOAP em audit details revela identidade
| Item |
Detalhe |
| Mitigação |
Audit details são metadados estruturados, não texto livre |
| Onde verificar |
anotae/persistence/audit_log_repository.py:_validate_extra |
| Comando auditor |
pytest tests/integration/test_audit_log_repository.py -k pii |
N-1, N-2 — Non-repudiation aceita
Aceitas por design (CFM exige rastreabilidade; Sigstore Rekor é log público).
D-2 — Tráfego de rede revela uso
| Item |
Detalhe |
| Mitigação |
Sem rede — NMH stdin/stdout; HTTP loopback nunca sai de 127.0.0.1 |
| Onde verificar |
anotae/http_bridge/server.py (binding) |
| Comando auditor |
grep -n "bind\|0\\.0\\.0\\.0\|address" anotae/http_bridge/server.py |
U-1 — Usuário não sabe que telemetria local existe
| Item |
Detalhe |
| Mitigação |
Default OFF; aba 'Privacidade' no AboutDialog informa explicitamente |
| Onde verificar |
anotae/ui/views/about_dialog.py:_tab_privacy |
| Comando auditor |
Abrir Anotae, ir em Ajuda → Sobre → aba Privacidade |
U-2 — Usuário não sabe que audit log existe
| Item |
Detalhe |
| Mitigação |
Vault → Histórico (Ctrl+H) expõe interface; schema docs em docs/reference/schema.md |
| Onde verificar |
anotae/ui/views/audit_history_dialog.py |
| Comando auditor |
Abrir Anotae, Ctrl+H |
U-3 — Usuário não sabe quais dados saem ao exportar
| Item |
Detalhe |
| Mitigação |
Aviso amarelo no ExportEncounterDialog + 3 níveis explícitos |
| Onde verificar |
anotae/ui/views/export_encounter_dialog.py (warning label) |
| Comando auditor |
Abrir Anotae, exportar atendimento atual, observar UI |
Nc-1 — LGPD Art. 12 anonimização inadequada
| Item |
Detalhe |
| Mitigação |
3 níveis explícitos + k-anonymity; testes invariantes |
| Onde verificar |
anotae/core/anonymizer.py + tests/unit/test_anonymizer.py + tests/unit/test_k_anonymity.py |
| Comando auditor |
pytest tests/unit/test_anonymizer.py tests/unit/test_k_anonymity.py -v |
Nc-3 — CFM 2.454/2026 — uso de IA exige registro
| Item |
Detalhe |
| Mitigação |
Disclaimer fixo no editor; metadata.ai_assisted; sei_inject_dialog reforça humano-no-loop |
| Onde verificar |
anotae/core/models.py:EncounterMetadata.ai_assisted |
| Comando auditor |
grep -n "ai_assisted" anotae/core/models.py anotae/ui/views/ |
Nc-5 — AGPL §6 acesso ao código-fonte
| Item |
Detalhe |
| Mitigação |
AboutDialog → Licença AGPL-3.0 com texto integral + link GitHub. Sigstore prova proveniência |
| Onde verificar |
anotae/ui/views/about_dialog.py:_tab_license |
| Comando auditor |
Abrir Anotae, Ajuda → Sobre → aba Licença AGPL-3.0 |
Resumo de cobertura
| Categoria |
Total ameaças |
Com evidência neste checklist |
% |
| STRIDE |
30 |
28 |
93% |
| LINDDUN |
13 |
13 |
100% |
| Total |
43 |
41 |
95% |
Ameaças com evidência indireta (não listadas detalhadamente aqui mas
cobertas pelo THREAT-MODEL):
- T-2 (MITM NMH) — coberto por design
- D-2 (vault grande) — limitação aceita pelo usuário
Como rodar a auditoria local
Auditor pode reproduzir esta tabela executando os comandos da
coluna "Comando auditor". Sugestão de fluxo:
# 1. Setup (repositório fechado nesta fase — acesso concedido
# individualmente pelo mantenedor ao auditor convidado)
git clone https://github.com/carlosefdeavila/anotae.git
cd anotae
bash scripts/maintainer-onboard.sh # ~5 min, cria .venv-maint/
# 2. Testes específicos por categoria
source .venv-maint/bin/activate
pytest tests/security/ # invariantes de cripto
pytest tests/unit/test_anonymizer.py # I-7, Nc-1
pytest tests/unit/test_k_anonymity.py # I-7
pytest tests/unit/test_telemetry.py # I-4
pytest tests/integration/test_audit_log_repository.py # R-1, T-5
# 3. Smoke E2E completo
python scripts/smoke-e2e.py
# 4. Verificar reprodutibilidade do build
bash scripts/verify-reproducible.sh # ~3 min em laptop moderno
# 5. SBOM
bash scripts/generate-sbom.sh
jq '.components | length' dist/sbom.json # ~96 componentes
# 6. Verificar Sigstore de release oficial
sigstore verify github \
--repository carlosefdeavila/anotae \
--bundle <bundle.sigstore.json> \
<Anotae-windows-x64.zip>
Quando atualizar este checklist
- Sempre que
THREAT-MODEL.md ganhar novo item.
- Sempre que mitigação migrar de arquivo (refactoring).
- Após auditoria externa, atualizar tabela "Status agregado da
auditoria" abaixo + adicionar subseção "Resultado da auditoria
— \<DATA>" com narrativa.
Status agregado da auditoria
Esta tabela é preenchida pelo auditor externo após verificação
ponto-a-ponto das mitigações. Default — (não auditado).
Símbolos: ✅ verificado · ⚠ gap parcial · ❌ falha · 🔄 refinamento sugerido
STRIDE
| ID |
Categoria |
Ameaça |
Status |
| S-1 |
Spoofing |
Usuário malicioso assume identidade do operador |
— |
| S-2 |
Spoofing |
Extensão maliciosa se passa pela Anotae no NMH |
— |
| S-3 |
Spoofing |
App falso responde no HTTP loopback |
— |
| S-4 |
Spoofing |
Site malicioso assume identidade do e-SUS/SISREG/SEI |
— |
| T-1 |
Tampering |
Adulteração do vault.db em disco |
— |
| T-2 |
Tampering |
MITM em mensagens NMH |
— |
| T-3 |
Tampering |
Adulteração de release binário |
— |
| T-3b |
Tampering |
Adulteração não detectada por chave comprometida |
— |
| T-4 |
Tampering |
Adulteração de dependência via supply chain |
— |
| T-5 |
Tampering |
Audit log adulterado por atacante com SQLite raw |
— |
| T-6 |
Tampering |
Hash do encounter não verificado ao ler |
— |
| R-1 |
Repudiation |
Profissional nega ter editado encounter |
— |
| R-2 |
Repudiation |
Atacante apaga audit log para esconder rastro |
— |
| R-3 |
Repudiation |
Profissional nega uso de IA na geração |
— |
| I-1 |
Disclosure |
Vault.db cai em mãos erradas |
— |
| I-2 |
Disclosure |
Logs/stack traces expõem PII |
— |
| I-3 |
Disclosure |
Backup contém PII em texto claro |
— |
| I-4 |
Disclosure |
Telemetria local expõe PII |
— |
| I-5 |
Disclosure |
Memory dump expõe master key |
— |
| I-6 |
Disclosure |
Side channel timing em verificação de senha |
— |
| I-7 |
Disclosure |
Anonimização inadequada em export |
— |
| I-8 |
Disclosure |
Strings traduzíveis (.pot) expõem PII |
— |
| D-1 |
DoS |
Argon2id trava hardware modesto |
— |
| D-3 |
DoS |
OOM via payload absurdo |
— |
| D-5 |
DoS |
Atacante força fallback Argon2id |
— |
| E-1 |
Elevation |
Plugin de mapping malicioso |
— |
| E-2 |
Elevation |
Template renderer com expressão maliciosa |
— |
| E-3 |
Elevation |
Extensão escala privilégios ao app |
— |
| E-4 |
Elevation |
Subprocess vaza arg injection |
— |
LINDDUN
| ID |
Categoria |
Ameaça |
Status |
| L-1 |
Linkability |
Encounter linkado a paciente em export anonimizado |
— |
| Id-1 |
Identifiability |
Vault descriptografado revela identidade real |
— |
| Id-3 |
Identifiability |
Texto SOAP em audit details revela identidade |
— |
| N-1, N-2 |
Non-repudiation |
Aceitas por design |
— |
| D-2 |
Detectability |
Tráfego de rede revela uso |
— |
| U-1 |
Unawareness |
Usuário não sabe que telemetria local existe |
— |
| U-2 |
Unawareness |
Usuário não sabe que audit log existe |
— |
| U-3 |
Unawareness |
Usuário não sabe quais dados saem ao exportar |
— |
| Nc-1 |
Non-compliance |
LGPD Art. 12 anonimização inadequada |
— |
| Nc-3 |
Non-compliance |
CFM 2.454/2026 — uso de IA exige registro |
— |
| Nc-5 |
Non-compliance |
AGPL §6 acesso ao código-fonte |
— |
Resultado da auditoria
Auditoria —
Aguardando auditoria. Esta seção é preenchida pelo auditor com
narrativa por categoria de status.
Itens ✅ (verificados): N de M ameaças
Itens ⚠ (gaps parciais): lista
Itens ❌ (falhas): lista — prioridade alta para fix
Itens 🔄 (refinamentos sugeridos): lista — issues abertos
Recomendações gerais: texto livre do auditor