<!-- BEGIN CHANGE: hardening e backlog de seguranca do Novo Portal -->

# Portal security

Auditoria 2026-07-22 (dev). Correcoes aplicadas na mesma data marcadas com **FIXED**.

## Tiers ACL (fonte da verdade)

| page_access | Rotulo | Capacidade |
|---|---|---|
| 0 | Player | Conta propria |
| 3-4 | Manager / Support | CMS, lookup, namelock |
| 5 | Subadmin | Pontos, shop CRUD, edits moderados |
| 6+ | Admin | Password, page_access, deletes, mass email, group_id |

## Corrigido nesta rodada

| Sev | Issue | Fix |
|---|---|---|
| Critical | Manager (>2) podia `set_page_access` / pontos / password | ACL por acao (5/6) em `admin-panel.php` |
| Critical | `cleanup_players` apagava **todos** invites | Wipe global so admin; senao so da conta |
| High | Conta `blocked` ainda logava / usava token | Check em `login.php` + `waRequireAuth` |
| High | Namelock com `page_access > 0` | Exige staff (`> 2`) |
| High | `cleanup_guilds` por qualquer logado | Admin only |
| High | Cleanup de guilds em GET publico | Removido de `guilds.php` |
| Medium | XSS news/shop HTML | `strip_tags` allowlist no write |

| Medium | Sem audit trail de acoes admin | **FIXED** - `z_admin_audit` + UI Audit (admin 6) |
| Medium | UI mostrava acoes acima do ACL do staff | **FIXED** - secoes e botoes por tier |
| Medium | Hash/recovery key sempre visiveis | **FIXED** - mascarados; reveal so admin 6 |
| Medium | Sem confirmacao de senha em mutacoes criticas | **FIXED** - `adminPassword` nas acoes sensiveis |

## Hardening 2026-07-30 (auth / CORS)

| Sev | Issue | Fix |
|---|---|---|
| High | CORS `Access-Control-Allow-Origin: *` | **FIXED** - allowlist em `waHeaders()` / `helpdesk-file.php` (`ot.` / `dev.` / `ot-test.` / `ot-beta.` / localhost) |
| High | Token 7d sem revoke no change-password | **FIXED** - coluna `accounts.web_token_version`; bump em change-password / admin set_password / block / lost-account; `waRequireAuth` valida versao |
| Medium | Sem rate-limit login | **FIXED** - throttle arquivo em `portal/cache/throttle/` (8 fails/15m por IP, 5/15m por conta); HTTP 429 `login_rate_limited` |

SQL: `portal/sql/accounts_web_token_version.sql` (auto ALTER no first auth).

## Backlog (nao bloqueante, priorizar)

| Sev | Issue | Sugestao |
|---|---|---|
| Medium | Token em query (helpdesk-file) | Preferir header; cookies HttpOnly longo prazo |
| Medium | SHA1 senhas | Manter paridade OTX; planejar migracao com dual-hash |
| Medium | Shop gift para qualquer nome | Confirmacao extra / recent chars |
| Low | `register` password max 40 vs validator 20 | Alinhar limites |
| Low | GET `vocation-guide` pode seedar DB | Seed so admin / migrate |

## Padroes obrigatorios

1. Acao destrutiva ou monetaria: autenticacao + ACL minima correta
2. Dinheiro/ownership: transacao + `FOR UPDATE`
3. HTML renderizado no React: sanitize no PHP no write
4. Sem side-effects em GET publico
5. Webhooks de pagamento: API portal (`mercadopago-webhook.php` / `paypal-ipn.php`);
   URLs publicas antigas via ProxyPass. Manter idempotencia `processed`.

## Test plan rapido pos-deploy

- [ ] Conta blocked nao loga
- [ ] page_access 3: CMS ok; `set_points` / `set_page_access` 403
- [ ] page_access 5: pontos ok; `set_password` 403
- [ ] page_access 6: acoes criticas ok
- [ ] Player comum: `cleanup_players` nao zera invites globais
- [ ] News nova sem `<script>` / sem `<a href=javascript:>`
- [ ] 5+ logins falhos na mesma conta -> 429; aguardar / limpar `cache/throttle`
- [ ] Trocar senha: sessao atual recebe novo token; outra aba/dispositivo antigo -> 401
- [ ] Origin CORS estranha nao recebe `Access-Control-Allow-Origin`

<!-- END CHANGE -->
