# 00 — Threat Model e fluxo de dados (módulo Fichas)

Baseado em `docs/PLANO.md` (aprovado, revisto 22/09/2026). Âmbito: só a Fase 0 até à Fase 5 do plano (núcleo); a Fase 6 (complementos) não altera o modelo de ameaça do núcleo, só o expande — referida no fim.

## 1. Ativos

| Ativo | Onde vive | Sensibilidade |
|---|---|---|
| Documentos digitalizados do processo (CC, RC, cartões PSP, comprovativos) | NAS `//10.0.30.79/Processos` (origem); nunca persistidos fora dela | Dados pessoais e de saúde/criminal (RC) — alta |
| Texto/JSON extraído dos documentos (`texto_vision`, `json_qwen`) | BD do CT assistente, `fichas_documentos` | Alta, mas com purga configurável (proposta 30 dias) |
| Manifesto (dados finais da ficha) | BD do CT assistente (`fichas_aprovacoes.manifesto_json`), ficheiro temporário no CT 107 durante o apply | Alta |
| Password da BD de prod do Sabichão | Só dentro do CT 107, resolvida em runtime por grep ao `addclientback.php`; nunca sai do CT 107 | Crítica |
| Chaves SSH (consulta e apply) para o CT 107 | CT assistente (privadas), CT 107 (`authorized_keys`, públicas) | Crítica — a chave de apply escreve em prod |
| Token do serviço leitor (mini) | CT assistente (cliente) e mini (`Authorization: Bearer`) | Média — só dá acesso ao OCR, não à BD |
| BD do Sabichão (prod) | CT 107 / 10.0.30.253 | Crítica — sistema já em produção, fora do âmbito de alteração direta |
| Sessão da webapp (cookie/login) | Browser dos RH, CT assistente | Média — acesso à fila e à aprovação |
| Backups do MySQL do CT assistente | NAS | Alta (contém tudo o que está em `fichas_*`) |

## 2. Atores

| Ator | Papel | Confiança |
|---|---|---|
| RH (1-2 utilizadores) | Regista casos, revê, aprova, aplica | Confiado, autenticado, ações auditadas |
| Motor (job LerCaso, Laravel no CT assistente) | Lê NAS, chama o mini, cruza dados, gera manifesto | Código controlado, sem intervenção humana direta |
| Serviço leitor (mini, FastAPI) | OCR/Vision + Qwen | Confiado mas isolado (rede fechada ao CT); nunca decide sozinho |
| Wrapper SSH no CT 107 | Só ponto de escrita em prod | Superfície mínima, comandos fixos |
| Claude (via skill `/inserir-funcionario`) | Via manual, alternativa à app | Nunca recebe documentos deste fluxo automatizado — via separada e já auditada à parte |
| Atacante externo | Sem acesso à LAN por definição do plano | Fora do modelo direto, mas ver secção 4 (fronteira NAS↔CT, CT↔mini) |
| Insider (RH ou quem tiver acesso à rede) | Pode ver documentos na app ou abusar da fila | Mitigado por auditoria, `[img]` fechado por omissão, logs sanitizados |

## 3. Fronteiras de confiança

```
[NAS //10.0.30.79/Processos]  --CIFS read-only-->  [CT "assistente"]  <--SSH restrito-->  [CT 107 / prod Sabichão]
                                                          |  ^
                                                    HTTPS+token
                                                          v  |
                                                   [mac mini 10.0.10.231 — serviço leitor]
                                                          |
                                                   [LM Studio localhost]

[Browser RH] --LAN, sem túnel--> [CT "assistente" webapp]
```

Fronteiras distintas:
1. **NAS → CT assistente**: montagem CIFS read-only por systemd automount.
2. **CT assistente → mini**: HTTPS (cert próprio) + token `Authorization: Bearer`, firewall do mini restrito ao IP do CT.
3. **mini → LM Studio**: só localhost no mini.
4. **CT assistente → CT 107**: duas ligações SSH distintas (consulta, apply), chaves diferentes, `command=` fixo, `from=<IP do CT>`.
5. **Browser RH → CT assistente**: LAN interna, sem exposição externa, sem VPN/túnel.
6. **CT assistente ↔ NAS (rota de documentos)**: leitura pontual por `documento_id` para mostrar `[img]`, nunca por caminho livre.

## 4. Fluxo de dados passo a passo

| Passo | Ligação | O que circula |
|---|---|---|
| 1 | RH → webapp (browser→CT) | MAI/NIF/zona/local/horas/alínea/modalidade (dados do caso, não documentos) |
| 2 | CT → NAS (CIFS) | Leitura dos PDFs/imagens da pasta do processo (read-only) |
| 3 | CT → mini (HTTPS) | Bytes do documento + `id` opaco (nunca caminho de ficheiro) — `/v1/ocr`, depois `/v1/analisar-documento` só nos tipos/campos da política |
| 4 | mini → LM Studio (localhost) | Bytes/imagem para o Qwen, dentro do próprio mini |
| 5 | mini → CT (resposta HTTPS) | JSON: texto/campos extraídos + confiança + posição — nunca a imagem de volta salvo o necessário ao pedido |
| 6 | CT → CT 107 (SSH consulta) | JSON de pedido (`consulta.zonas`, `consulta.reserva`, `consulta.funcionario`, `consulta.portal_lookup`) — sem dados sensíveis a mais do que o necessário à query |
| 7 | CT 107 → CT (resposta SSH) | JSON com o resultado do SELECT (zonas, reserva, dados do portal) |
| 8 | RH → webapp → CT (edição/aprovação) | Correção de campos, aprovação (hash do manifesto + hashes dos documentos) |
| 9 | CT → CT 107 (SSH apply/dry_run) | Manifesto completo (JSON) — é o momento de maior exposição de dados pessoais na rede interna |
| 10 | CT 107 → CT (resposta apply) | JSON com resultado, exit code, resumo sanitizado (sem PII em stderr) |
| 11 | CT → CT 107 (SSH ficha_pdf) | Pedido de render; resposta = bytes do PDF (servidos com `no-store`, nunca guardados) |
| 12 | CT → CT 107 (SSH email) | Pedido de envio; o email sai do CT 107 (já é o comportamento atual do `fichas_email.php`), não do CT assistente |
| 13 | Browser RH → CT (rota de documentos) | Pedido de `[img]` por `documento_id`; resposta = imagem rasterizada, `Cache-Control: private, no-store` |

**Nota crítica do plano**: o Claude nunca recebe imagens nem texto de documentos deste fluxo (princípio 1). Os passos 1-13 nunca tocam num LLM externo.

## 5. Ameaças concretas por fronteira e mitigação

| # | Fronteira | Ameaça | Mitigação prevista no plano |
|---|---|---|---|
| T1 | NAS→CT | Documento errado lido/montagem falha em silêncio | Automount systemd + verificação de saúde na Fase 1 (`ls /mnt/processos` após reinício); classificação por tipo antes de usar qualquer valor |
| T2 | NAS→CT | Path traversal ao servir `[img]` | Rota de documentos: só `documento_id`, autorização pelo caso, caminho da BD + `realpath` dentro da pasta autorizada, sem symlinks (Segurança, PLANO.md) |
| T3 | CT→mini | Interceção/alteração de dados pessoais em trânsito | HTTPS (mesmo com cert próprio) + token; fronteira 4 (Fase 6) prevê certificado de cliente — a confirmar se HTTPS+token é considerado suficiente para o MVP (A CONFIRMAR o nível de risco aceite antes de prod) |
| T4 | CT→mini | mini comprometido lê prompts/imagens residuais | Verificação prática de que LM Studio/FastAPI/crash reports/Time Machine não retêm prompts nem imagens (accao explícita do plano, arquitetura); temporários 0700 apagados em `finally` |
| T5 | CT→mini | Fila/temporários do mini acumulam PII | Sem montagem da NAS no mini; ficheiros temporários com 0700 e apagados sempre em `finally`; concorrência 1 evita corrida entre casos |
| T6 | CT↔CT 107 | Chave de apply usada fora do wrapper, ou PTY/forwarding abertos | `authorized_keys` com `from=`, `command=` fixo, sem PTY/forwarding; utilizador técnico dedicado; sudoers restrito a correr como `www-data` |
| T7 | CT↔CT 107 | Password de prod exposta em log/stdout/stderr | Resolvida sempre dentro do wrapper (grep local ao `addclientback.php`), nunca impressa — mesma regra já em vigor na skill manual |
| T8 | CT↔CT 107 | Apply repetido após timeout SSH duplica inserção | Sem retry automático; chave de idempotência por apply (`fichas_execucoes.chave_idempotencia`); reconciliação por SELECT |
| T9 | CT↔CT 107 | Dev com a chave de apply de prod | Explicitamente proibido no plano ("O CT de dev nunca tem a chave de apply de prod") |
| T10 | CT↔CT 107 | Documento muda entre leitura e aprovação, apply aplica dados desatualizados | Aprovação ligada ao hash de cada documento e ao hash do manifesto; qualquer mudança invalida aprovação e dry-run |
| T11 | Browser→CT | Sessão fraca / força bruta no login | Argon2id, rate limiting no login, sessões curtas, CSRF |
| T12 | Browser→CT | Fuga de PII em logs/exceções | Logs e exceções sanitizados (sem NIF/NISS/morada), explícito na secção Segurança do plano |
| T13 | Webapp | Exposição fora da LAN | Só LAN, sem túnel — decisão do plano, a verificar em Fase 1/3 (regra de firewall + ausência de porta exposta ao exterior) |
| T14 | BD do CT assistente | Perda/roubo de disco | Disco do CT e backups cifrados |
| T15 | BD do CT assistente | Corrupção/perda sem recuperação | Backup diário do MySQL para a NAS com restauro testado antes do go-live (gate explícito da Fase 4) |
| T16 | Ambiente dev | Cópias permanentes de dados reais em dev | Dev sem cópias permanentes de funcionários reais (dados sintéticos ou purga no fim de cada teste) |
| T17 | Motor/Qwen | Qwen "inventa" ou confunde dois documentos | Política de confiança por campo (04-POLITICA-CONFIANCA.md); revisão humana obrigatória com evidência; estatística contínua por leitor (`fichas_fiabilidade`) |
| T18 | Falha de software | Bug no Laravel dispara execução indevida em prod | Nenhuma escrita em prod acontece dentro do Laravel: sempre via wrapper restrito no CT 107, chave de apply só existe a partir da Fase 5 |
| T19 | Fila assíncrona | Job preso em `em_leitura` bloqueia o caso indefinidamente | Lease/heartbeat para recuperar jobs presos (02-MAQUINA-ESTADOS.md) |
| T20 | Retenção de dados | `texto_vision`/`json_qwen` guardados indefinidamente aumentam a superfície | Retenção configurável (proposta 30 dias), purgados após conclusão do caso, mantendo só valores finais + evidência mínima |

## 6. Verificações de segurança a executar antes de prod (checklist)

- [ ] Confirmar por teste real que `authorized_keys` do utilizador técnico no CT 107 bloqueia qualquer comando fora do wrapper (`command=` funciona, sem PTY, sem forwarding).
- [ ] Confirmar que a chave de apply não existe em lado nenhum fora do CT assistente de prod (procurar em backups, histórico de shell, dev).
- [ ] Testar dry-run + apply com timeout SSH simulado (ex. matar a ligação a meio) e confirmar que a reconciliação por SELECT deteta o estado real sem duplicar.
- [ ] Confirmar que os logs/stderr do wrapper nunca contêm a password nem PII (rever manualmente um output real).
- [ ] Confirmar CSP sem origens externas na rota de documentos e testar um pedido de `documento_id` fora da pasta autorizada (esperado: rejeitado).
- [ ] Testar path traversal e symlink na rota de documentos (`../`, symlink apontando para fora da pasta do processo).
- [ ] Confirmar MIME por conteúdo (não por extensão) na rota de documentos, com um ficheiro disfarçado.
- [ ] Verificar na prática (mini) que LM Studio, FastAPI, crash reports e Time Machine não retêm prompts nem imagens — procurar em logs, `~/Library/Logs`, snapshots do TM.
- [ ] Testar rate limiting e sessão curta no login da webapp; confirmar CSRF token em todos os formulários de escrita.
- [ ] Restaurar o backup do MySQL do CT assistente num ambiente de teste e confirmar integridade (gate explícito da Fase 4).
- [ ] Confirmar que o disco do CT assistente e os backups estão de facto cifrados (não só "previsto").
- [ ] Confirmar que a webapp não está acessível fora da LAN (nmap/curl externo deve falhar).
- [ ] Confirmar que o dev não tem cópias permanentes de NIF/documentos reais no fim de cada sessão de teste.
- [ ] Confirmar retenção/purga de `texto_vision`/`json_qwen` a correr como job agendado, não só como intenção.
- [ ] Rever um caso completo ponta-a-ponta e confirmar que nenhum payload passou pelo Claude (grep aos logs de rede do CT, se existirem).

## 7. Nota sobre a Fase 6

A Fase 6 introduz certificado de cliente entre CT e mini (reforça T3), tema escuro e página de estado (sem impacto de segurança relevante), e "Perguntar ao Qwen" (texto já extraído, nunca a imagem — mesma fronteira já coberta). Não é preciso reabrir este documento antes da Fase 6, só reavaliar T3 quando o certificado for implementado.

A CONFIRMAR: nível de retenção exato dos backups (dias/semanas) e responsável pela rotação de chaves SSH em caso de suspeita de compromisso — não definido no plano.
