# Auditoria de Segurança — OWASP Top 10

Documento de referência com os achados de segurança da aplicação `gertel-talentos-backend`, classificados por severidade e mapeados para as categorias do OWASP Top 10 (2021).

> **Status:** auditoria inicial. Os itens marcados como `[ ]` ainda não foram corrigidos.

---

## 🔴 Críticos

### 1. A02 / A07 — Hash de senha SHA-256 sem salt

**Status:** `[ ] Pendente`

**Localização:**
- `src/use-cases/candidate-control-access.ts:40` — autenticação de candidatos.
- `src/use-cases/control-candidate.ts:29,35,77` — criação/atualização de senha de candidato.
- `src/use-cases/control-user.ts:30,36,69` — criação/atualização de senha de usuário (admin).

**Descrição:**
Senhas armazenadas em SHA-256 sem salt. SHA-256 é uma função de hash de propósito geral (rápida) e sem salt; em caso de vazamento do banco, senhas fracas/médias caem em segundos via rainbow tables ou GPU bruteforce.

**Inconsistência adicional:** `control-access.ts:40` (auth de admin) usa `bcryptjs.compare`, enquanto a criação/troca de senha grava SHA-256. Resultado: usuários cadastrados via fluxo atual **não conseguem logar** porque o hash gravado nunca bate com o `compare` do bcrypt.

**Correção recomendada:**
- Migrar todos os fluxos para `bcryptjs` (custo ≥ 10).
- Estratégia de migração transparente: ao logar, se o hash atual estiver em SHA-256 e a senha bater, re-hashear em bcrypt e atualizar o registro.

---

### 2. A01 — Broken Access Control (autorização ausente)

**Status:** `[ ] Pendente`

**Localização:**
- `src/http/controllers/users/routes.ts:9-12` — `POST /users`, `GET /users`, `GET /user/:id`, `POST /user/change-password` só checam JWT.
- `src/http/controllers/candidatures/routes.ts` — `approve`, `reprove`, `next-step` só checam JWT.
- `src/http/controllers/candidates/curriculum/routes.ts:42-71` — qualquer JWT válido pode editar o currículo de qualquer `id_candidate`.
- `src/http/middlewares/verifyRole.ts` — middleware de role existe mas **não é usado em nenhuma rota**.

**Descrição:**
Toda rota administrativa só verifica `verifyJWT`. Um candidato logado consegue:
- Criar usuários administradores.
- Listar e ler dados de admins.
- Aprovar/reprovar candidaturas (inclusive a própria).
- Editar o currículo de outros candidatos passando outro `id_candidate` no body.

**Correção recomendada:**
- Aplicar `verifyRole('ADMINISTRATOR')` nas rotas de gestão (`/users`, `/candidatures/:id/approve`, `/candidatures/:id/reprove`, `/candidatures/:id/next-step`, etc.).
- Nas rotas de currículo, validar que `id_candidate` no body é igual a `request.user.sub` quando `role === 'CANDIDATE'`.

---

### 3. A07 — `GET /candidate/cpf/:cpf` sem autenticação

**Status:** `[ ] Pendente`

**Localização:** `src/http/controllers/candidates/routes.ts:19`

**Descrição:**
Endpoint público que retorna dados do candidato (PII) a partir do CPF. Combinado com a falta de rate limiting, permite enumeração de CPFs e coleta massiva de dados pessoais.

**Correção recomendada:**
- Adicionar `{ onRequest: [verifyJWT] }` à rota.
- Considerar restringir a admins.

---

### 4. A07 — Cookie de refresh inseguro

**Status:** `[ ] Pendente`

**Localização:**
- `src/http/controllers/auth/user.ts:78-82`
- `src/http/controllers/auth/candidate.ts:76-82`
- `src/http/controllers/auth/user-ati.ts:87-93`
- `src/http/controllers/auth/refresh.ts:75-81`

**Descrição:**
Configuração atual:
```ts
{ secure: true, sameSite: false, httpOnly: false, maxAge: 60 * 60 * 24 * 30 }
```
- `httpOnly: false` → JavaScript do navegador pode ler o token (XSS rouba o refresh).
- `sameSite: false` → CSRF: outras origens podem disparar requests autenticados.

**Correção recomendada:**
```ts
{ secure: true, sameSite: 'lax', httpOnly: true, maxAge: 60 * 60 * 24 * 30 }
```

---

### 5. A02 — Refresh token sem rotação/invalidação

**Status:** `[ ] Pendente`

**Localização:** `src/http/controllers/auth/refresh.ts`

**Descrição:**
A rota `/refresh` emite novo refresh token mas não invalida o anterior em `TokenSchema`. O token antigo continua válido até expirar (30 dias). Mesmo problema acontece em re-login.

**Correção recomendada:**
- Marcar o refresh token consumido como inválido (delete ou flag `revoked: true`).
- Detectar reuso de refresh: se um token já consumido for reapresentado, revogar toda a sessão (sinal de roubo).

---

## 🟠 Altos

### 6. A05 — CORS `origin: true` com `credentials: true`

**Status:** `[ ] Pendente`

**Localização:** `src/app.ts:27-38`

**Descrição:**
`origin: true` reflete qualquer Origin no header `Access-Control-Allow-Origin`. Combinado com `credentials: true`, permite que qualquer site faça requests autenticados em nome do usuário logado.

**Correção recomendada:**
- Manter uma allowlist via env (`CORS_ORIGINS=https://app.gertel.com.br,https://...`).
- Validar a origin recebida contra essa lista.

---

### 7. A09 — Sniffer registra senhas e tokens em texto puro

**Status:** `[ ] Pendente`

**Localização:** `src/http/middlewares/sniffer.ts:55-69`

**Descrição:**
O middleware salva o body e os headers brutos (incluindo `Authorization: Bearer ...`) no MongoDB. Significa que:
- Senhas enviadas em `POST /auth/user`, `/auth/candidate`, `POST /users`, `/candidate/change-password` ficam armazenadas em claro.
- JWTs ficam armazenados em todos os logs.

**Correção recomendada:**
- Mascarar campos sensíveis antes de salvar (`password`, `newPassword`, `id_ati`).
- Remover/mascarar o header `Authorization` e o cookie `refreshToken`.

---

### 8. A03 — Upload sem validação de tipo

**Status:** `[ ] Pendente`

**Localização:** `src/http/controllers/candidates/curriculum/create-curriculum.ts:239-242`

**Descrição:**
- `extname(fileEntry.filename)` usa a extensão vinda do cliente sem validação.
- Não há verificação de `mimetype`.
- Os arquivos ficam servidos em `/uploads` (`src/app.ts:69-72`) sem controle de acesso (qualquer um com a URL acessa).
- Mitigação parcial: o nome do arquivo é regenerado com `randomUUID()`, evitando path traversal.

**Correção recomendada:**
- Whitelist de mimetypes (`application/pdf`, `image/png`, `image/jpeg`).
- Whitelist de extensões.
- Considerar mover `/uploads` para trás do `verifyJWT` ou usar URLs assinadas/temporárias.

---

### 9. A01 — `change-password` aceita `login` no body

**Status:** `[ ] Pendente`

**Localização:**
- `src/http/controllers/users/change-password.ts:14-19`
- `src/http/controllers/candidates/change-password.ts:14-16`

**Descrição:**
A rota está atrás de `verifyJWT` mas recebe `login` no body em vez de usar `request.user.sub`. Um JWT válido pode trocar a senha de outro usuário (precisaria saber a senha atual, mas amplia o vetor de ataque desnecessariamente).

**Correção recomendada:**
- Identificar o usuário pelo `request.user.sub` (token).
- Body deve conter apenas `password` (atual) e `newPassword`.

---

## 🟡 Médios

### 10. A09 — Stack trace em logs fora de produção

**Status:** `[ ] Pendente`

**Localização:** `src/app.ts:101-103`

**Descrição:**
`console.log(error)` exposto em `dev` e `homologation`. Homologation frequentemente está em rede acessível e pode vazar detalhes internos via logs.

**Correção recomendada:**
- Logar somente em `dev`, ou usar logger estruturado com nível controlável.

---

### 11. A04 — Sem rate limiting em `/auth/*`

**Status:** `[ ] Pendente`

**Localização:** `src/http/controllers/auth/routes.ts`

**Descrição:**
Não há proteção contra brute force / credential stuffing. Adicionalmente, as respostas de `/auth/user` e `/auth/candidate` distinguem "Login não encontrado" (404) de "Não autorizado" (401), permitindo enumeração de logins (`src/use-cases/control-access.ts:37,45` e `candidate-control-access.ts:37,45`).

**Correção recomendada:**
- Instalar `@fastify/rate-limit` e aplicar limite agressivo nas rotas `/auth/*` (ex: 5 tentativas / 15 min por IP).
- OU configurar rate limit no nível de infra (nginx/Cloudflare).
- Padronizar a resposta de falha em "Credenciais inválidas" sem distinguir o motivo.

---

### 12. A02 — `JWT_SECRET` sem comprimento mínimo no schema

**Status:** `[ ] Pendente`

**Localização:** `src/env/index.ts:14`

**Descrição:**
`JWT_SECRET: z.string()` aceita qualquer string, inclusive de 1 caractere. Em deploy é fácil acabar com um segredo fraco.

**Correção recomendada:**
- `JWT_SECRET: z.string().min(32)`.

---

### 13. A05 — `decryptIdAti` usa AES-CBC sem MAC

**Status:** `[ ] Pendente`

**Localização:** `src/lib/ati-crypto.ts:13`

**Descrição:**
AES-CBC sem HMAC permite ataques de padding oracle quando o ciphertext é aceito de fontes não confiáveis. O endpoint `/auth/user/ati` aceita o ciphertext direto do cliente.

**Correção recomendada:**
- Migrar para AES-GCM (`aes-256-gcm`), que provê autenticação integrada.
- Requer alteração coordenada com o sistema externo que gera o `id_ati`.

---

## Plano de aplicação

### Sem impacto em clientes (aplicação direta)
- [ ] Adicionar `verifyJWT` em `GET /candidate/cpf/:cpf`.
- [ ] Cookie de refresh: `httpOnly: true`, `sameSite: 'lax'`.
- [ ] Sniffer: redactar `password`, `newPassword`, `id_ati`, header `Authorization` e cookie `refreshToken`.
- [ ] CORS com allowlist via env.
- [ ] Validar mimetype/extensão no upload.
- [ ] `JWT_SECRET` exigir `min(32)`.
- [ ] `change-password` usar `request.user.sub` em vez de `login` do body.
- [ ] Aplicar `verifyRole('ADMINISTRATOR')` em rotas admin (criação de usuário, listagens, approve/reprove de candidatura).
- [ ] Validar ownership em rotas de currículo.
- [ ] Mensagem genérica "Credenciais inválidas" no login.
- [ ] Invalidar refresh token antigo ao gerar novo.

### Decisões pendentes (impactam clientes ou exigem migração)
- [ ] **Bcrypt em candidatos e users.** Estratégia recomendada: migração transparente — detectar hash SHA-256 no login, validar e re-hashear em bcrypt na próxima autenticação bem-sucedida.
- [ ] **Rate limit em auth.** Decidir se implementação fica no app (`@fastify/rate-limit`) ou na infra (nginx / Cloudflare).
- [ ] **AES-GCM em `ati-crypto`.** Requer coordenar deploy com o sistema externo que produz o ciphertext.

---

## Como atualizar este documento

Ao corrigir um item, atualizar o `Status` para `[x] Corrigido em <data> — <commit/PR>` e mover a entrada para a seção `Histórico` ao final do arquivo (a ser criada na primeira correção).
