# 08 — Segurança e LGPD — commerce-vc1

Fonte de verdade de nomes, papéis, hosts e modos de faturamento: `docs/blueprint/_BRIEF.md`. Insumos: `docs/sources/PLAN-vc1-wholesale.md` §7–8; `docs/sources/ANALYSIS-vc1-wholesale-pivot.md` §4. Dívidas do legado **não** se copiam: `easy_cicerone/docs/FINDINGS.md`, `VC1-Intelbras`, `sales-mework360`.

Regra Beesy: nenhum deploy, mutação ou SSH em produção sem o operador. Segredos nunca entram no git, em URL, em log ou em resposta de API. Branch `front/*` + PR; nunca push em `main`.

---

## 1. Superfícies e modelo de ameaça (STRIDE)

| Superfície | Host (PROD) | Atores |
|------------|-------------|--------|
| Painel Filament VC1 | `admin.vc1.com.br` | `vc1_admin`, `vc1_finance`, `vc1_ops`, `vc1_support` |
| SPA revenda | `{slug}.vc1.com.br/painel` | `reseller_owner`, `reseller_admin`, `reseller_finance`, `reseller_seller` |
| SPA cliente final | `{slug}.vc1.com.br/app` | `customer_admin`, `customer_user` |
| Proposta pública | `{slug}.vc1.com.br/p/{code}` (atalho `sales.vc1.com.br`) | lead / contato da proposta |
| API tokens | `api.vc1.com.br` | tokens da revenda (`identity.api_tokens`) |
| Webhooks inbound | `api.vc1.com.br` | mePay, meSign |
| Conectores legado | adapters hexagonais | ERP `easy_cicerone`, SipPulse SOAP, MySQL `sippulse_reports` (leitura) |
| LAB no legado real | `*.lab.vc1.com.br` | staff + adapters `LabGuarded*` |

### 1.1 Filament admin (`admin.vc1.com.br`)

| STRIDE | Ameaça | Mitigação |
|--------|--------|-----------|
| S | Credencial staff vazada; sessão sequestrada | 2FA TOTP **obrigatório**; sessão curta; cookie `Secure` + `HttpOnly` + `SameSite=lax` |
| T | Alterar preço, modo de faturamento ou STFC de outra revenda | Policies por papel; `price_bases` versionadas; audit log |
| R | Staff negar impersonation ou alteração de fatura | `audit.audit_log` imutável; impersonation com banner |
| I | Listar clientes de todas as revendas sem necessidade | Isolamento por tenant mesmo no admin: queries sempre filtradas; mascaramento de CPF/CNPJ na UI de suporte |
| D | Brute-force login | Rate limit login 5/min (`PADRÃO PROPOSTO`); lockout de conta |
| E | `vc1_support` emitir NF ou pagar repasse | Matriz de papéis §3; finance separado de support |

### 1.2 SPA revenda (`{slug}.vc1.com.br/painel`)

| STRIDE | Ameaça | Mitigação |
|--------|--------|-----------|
| S | Login em slug alheio (`cliente.vc1.com.br` redireciona, mas token/cookie de outro Host) | Tenant resolvido pelo `Host`; Sanctum stateful **somente** no domínio do tenant; cookie com `Domain` do slug |
| T | Revenda A editar plano/proposta/fatura da revenda B (IDOR) | Policy `reseller_id` = tenant atual; testes Pest de cross-tenant (§3.2) |
| R | Vendedor alterar proposta já enviada e negar | Snapshot imutável após `sent`; `sales.proposal_events` |
| I | Token ERP legado no painel / docs de API | Token da revenda Sanctum scoped; **nunca** o token `TOKEN_WHITE_LABEL` |
| D | Enumeração de slugs / login | Rate limit; slugs reservados do brief; 404 genérico |
| E | `reseller_seller` ver faturas ou STFC | Policies §3 |

### 1.3 SPA cliente final (`{slug}.vc1.com.br/app`)

| STRIDE | Ameaça | Mitigação |
|--------|--------|-----------|
| S | Cliente de outra org no mesmo tenant | `customers.customers` + `identity.user_roles` no tenant **e** na org |
| T | Cliente alterar plano/preço pelo app | App só leitura comercial + self-service de crédito/DID conforme policy; totais só servidor |
| R | Negar recarga ou bloqueio | `billing.credit_ledger`, `telco.block_events` |
| I | CDR de outros clientes; senha SIP em listagem | CDR filtrado por `sip_accounts` da org; senha SIP revelada uma vez + rotated |
| D | Download em massa de extrato | Rate limit export |
| E | `customer_user` gerir usuários da org | Só `customer_admin` |

### 1.4 Proposta pública (`/p/{code}`)

Modelo herdado de `sales-mework360/backend/app/Http/Controllers/PublicProposalController.php` + `ProposalPublicAccessService.php` (PIN 6 dígitos, hash, cookie `proposal_public_access`, lockout).

| STRIDE | Ameaça | Mitigação |
|--------|--------|-----------|
| S | PIN adivinhado; cookie roubado | PIN numérico 6 dígitos; hash bcrypt; cookie cifrado; TLS |
| T | Lead alterar investimento para total menor (dívida R1 do legado) | Totais **somente** no servidor; snapshot da proposta; UI é display |
| R | Aceite sem PIN | `proposal.public` middleware; aceite exige PIN válido |
| I | Vazamento de `data_json` interno (sales já projeta conteúdo) | Resource público sem chaves internas; sem senha de trial no JSON |
| D | Brute-force PIN | Lockout 5 tentativas / 15 min **e** 10 tentativas/hora por IP+slug (`PADRÃO PROPOSTO`, iguais ao sales) |
| E | Operador autenticado bypassar PIN de outra revenda | Bypass só staff/vendedor **dono** do registro; nunca cross-tenant |

### 1.5 API tokens (`api.vc1.com.br`)

| STRIDE | Ameaça | Mitigação |
|--------|--------|-----------|
| S | Token em query string (dívida VC1-Intelbras) | `Authorization: Bearer`; token plaintext **só na criação**; hash no banco (`identity.api_tokens`) |
| T | Token com scope `*` alterar billing | Abilities mínimas (`plans:read`, `proposals:write`, `statements:read`, …) |
| R | Uso de token negado | Audit de cada chamada autenticada por token |
| I | Token no git / log / URL de webhook outbound | Prefixo `vc1_` para secret scanning; never log plaintext |
| D | Enumeração | Rate limit por token |
| E | Token de revenda A no Host de B | Token amarrado a `reseller_id`; Host deve bater |

### 1.6 Webhooks inbound (mePay / meSign)

meSign confirmado: `POST /api/v1/webhooks/paco-assign`, header `X-Paco-Signature`, HMAC SHA-256 `sha256=<hex>`, eventos `envelope.completed` e `envelope.archived` (`sales-mework360/backend/app/Http/Controllers/Webhooks/PacoAssignWebhookController.php`; brief §2). mePay: spec **A VERIFICAR** (produto em construção).

| STRIDE | Ameaça | Mitigação |
|--------|--------|-----------|
| S | POST forjado | HMAC `hash_equals`; secret vazio → 503 (não processa) |
| T | Replay do body válido | Timestamp + `event_id` em `integration.inbox_webhooks`; janela 5 min (`PADRÃO PROPOSTO`) |
| R | “Não recebi o pago” | Inbox + outbox; idempotência por `event_id` |
| I | Secret no repo | Vault; env `MESIGN_WEBHOOK_SECRET` / `MEPAY_WEBHOOK_SECRET` |
| D | Flood | Rate limit por IP + signature inválida conta como falha |
| E | Webhook dispara provisionamento em outro tenant | Envelope amarrado a `sales.signature_envelopes` / fatura do tenant |

### 1.7 Conectores ao legado

Alvos: `LegacyErpProvisioningAdapter` → `api/white_label` (`easy_cicerone`); `SipPulseSoapAdapter` → SOAP `172.16.49.222:8080` (VPN); `CdrPort` leitura `sippulse_reports.cdrs_report`; `MepayFiscalAdapter` (`FiscalPort` → mePay → emissor NFCom terceiro).

| STRIDE | Ameaça | Mitigação |
|--------|--------|-----------|
| S | Token ERP na query (`TOKEN=`) | Header ou body; TLS verify **sempre on** |
| T | Cron legado `geracao_faturas_whitelabel_tronco.php` cobra em paralelo | Flag `vc1_commerce` no projeto; skip no WP **só com PR legado + operador** |
| R | Chamada SOAP sem trilha | `integration.connector_calls` |
| I | TLS off (SEC-003 / VC1-Intelbras) | `CURLOPT_SSL_VERIFYPEER=1` e `VERIFYHOST=2`; testes que falham se verify off |
| D | SOAP lento / retry storm | Timeout, backoff, circuit breaker |
| E | Adapter criar projeto WL fora do piloto | LAB allowlist; PROD exige gate do operador |

### 1.8 LAB tocando legado real

LAB usa o legado **de verdade**, só contas demo (brief §2). Decorators `LabGuarded*`; tabela `integration.lab_allowlist`.

| STRIDE | Ameaça | Mitigação |
|--------|--------|-----------|
| S | Credencial de LAB apontando a PROD sem label | `APP_ENV=lab`; hosts `*.lab.vc1.com.br`; secrets separados |
| T | Write em `projetos_clientes` / subscriber de cliente real | Allowlist de `legacy_id`; recusa fora da lista; teste de ouro |
| R | Operador negar que o lab escreveu no ERP | `connector_calls` + allowlist hit/miss |
| I | Dump de CDR real / clientes reais no lab | Fixtures anonimizados; **proibido** copiar base de produção |
| D | Lab derruba SOAP de produção | Rate limit no decorator; horários combinados |
| E | Criar conta demo nova em produção | Aprovação explícita do operador (brief) |

---

## 2. Autenticação (authN)

| Ator | Mecanismo | 2FA | Sessão / token |
|------|-----------|-----|----------------|
| Staff VC1 (Filament) | e-mail + senha (guard `web`) | **TOTP obrigatório** (Filament 4 nativo ou plugin equivalente) antes do 1º acesso a LAB com dado real e sempre em PROD | Cookie stateful; timeout 8 h (`PADRÃO PROPOSTO`) |
| Usuário revenda (SPA) | e-mail + senha, Sanctum stateful no Host do slug | **Opcional** (`PADRÃO PROPOSTO`); `reseller_owner` é encorajado | Cookie por `{slug}.vc1.com.br`; `SANCTUM_STATEFUL_DOMAINS` inclui `*.vc1.com.br` e `*.lab.vc1.com.br` |
| Usuário cliente (SPA) | e-mail + senha no Host da revenda | Opcional (`PADRÃO PROPOSTO`) | Isolado da sessão da revenda (path `/app` vs `/painel` ou cookie name distinto) |
| Lead / proposta | PIN numérico 6 dígitos enviado por e-mail/SMS | N/A | Cookie cifrado `proposal_public_access`; TTL 24 h; lockout 5 falhas → 15 min; rate 10/h por IP+slug (`PADRÃO PROPOSTO`, cópia do sales) |
| API revenda | Bearer Sanctum (`identity.api_tokens`) | N/A | Token hashed; plaintext uma vez; abilities; expiração 90 dias (`PADRÃO PROPOSTO`) |
| Webhooks | HMAC | N/A | Sem sessão |

Login genérico `cliente.vc1.com.br`: autentica, descobre o tenant do usuário e **redireciona 302** para `{slug}.vc1.com.br`. Não mantém sessão no host genérico.

PIN **não** vai na URL. O legado Intelbras usa `/boas-vindas/{token}` e `/assinar/{token}` (`VC1-Intelbras/routes/web.php`) — não replicar.

Reset de senha: token de uso único, TTL curto, nunca logado. Falhas de login **não** revelam se o e-mail existe.

---

## 3. Autorização (authZ)

### 3.1 Papéis (brief §3)

| Papel | Escopo | Pode | Não pode |
|-------|--------|------|----------|
| `vc1_admin` | global | Tudo no painel VC1, inclusive impersonation e allowlist LAB | Deploy / SSH (fora do app) |
| `vc1_finance` | global | Faturas, pagamentos, repasses, NFCom, write-off | Provisionar SIP; impersonation |
| `vc1_ops` | global | Onboarding revenda, STFC, conectores, DNS, allowlist | Ver CDR nominais sem ticket; pagar repasse |
| `vc1_support` | global | Leitura + impersonation com banner | Alterar preço/base; emitir NF; aprovar repasse; **block/unblock manual** (RN-121) |
| `reseller_owner` | tenant | Tudo da revenda (marca, faixas, planos, usuários, faturas, STFC upload) | Ver custo VC1 detalhado além da tabela de faixas; dados de outra revenda |
| `reseller_admin` | tenant | Operação e usuários, sem encerrar a revenda | Terminar contrato VC1 |
| `reseller_finance` | tenant | Faturas/extratos, crédito, payout (modo `vc1_invoices`) | Planos e funil |
| `reseller_seller` | tenant | Funil, propostas, kanban | Faturas, STFC, markup global |
| `customer_admin` | org no tenant | App: usuários da org, portabilidade, extrato, recarga | Preço de atacado; outros clientes |
| `customer_user` | org no tenant | Extrato, senha SIP própria, DID da org (leitura) | Usuários; portabilidade |

Policies Laravel (`ResellerPolicy`, `CustomerPolicy`, `ProposalPolicy`, `InvoicePolicy`, `PayoutPolicy`, `StfcLicensePolicy`, `SipAccountPolicy`, `CdrPolicy`). Toda query de negócio recebe `reseller_id` do `Host` (staff escolhe o tenant no Filament; nunca “null = todos” em listagens de PII).

### 3.2 Isolamento de tenant (teste obrigatório)

Critério: **é impossível** um ator do tenant A ler ou escrever recurso do tenant B.

Pest (mínimo, CI vermelho se falhar):

1. Host `{a}.lab.vc1.com.br` autenticado **não** `GET` `/api/v1/customers/{uuid-de-B}` → 404 (não 403, para não vazar existência).
2. Troca do header `Host` com cookie de A → 401/404.
3. Token de A no Host de B → 401.
4. UUID de proposta B no funil de A → 404.
5. Staff Filament **sem** impersonation não altera `billing_mode` via request forjado de outro resource.
6. LAB: `LabGuardedProvisioningAdapter` recusa `legacy_id` fora de `integration.lab_allowlist`.

### 3.3 Impersonation

Só `vc1_admin` e `vc1_support` impersonam usuário de **revenda** ou **cliente**. Nunca staff. Sessão em `identity.impersonation_sessions` (TTL **30 min**). Banner persistente. `audit.audit_log` grava `impersonator_user_id`, `user_agent`, `impersonation_session_id`. Proibido: trocar senha, emitir token API, alterar 2FA, baixar ZIP de portabilidade em massa, pagar/estornar, **block/unblock telco** (`vc1_support` = leitura + impersonate apenas — alinhado RN-121).

---

## 4. Segredos

### 4.1 Onde vivem

| Ambiente | Cofre | Quem rotaciona |
|----------|-------|----------------|
| GitHub Actions | GitHub Secrets (mínimo: `ANTHROPIC_API_KEY` para Merlin) | Operador |
| LAB / PROD meCloud | cloud-init + secret store do host (não no disco do git); **nunca** `.env` commitado | Operador |
| Local | `.env` gitignored; só stubs | Dev |

Valores reais **nunca** são lidos, copiados ou impressos pelo agente. Cloud Agents usam fixtures.

### 4.2 Nomes de variáveis (valores omitidos)

| Variável | Uso |
|----------|-----|
| `APP_KEY` | Cifra Laravel (cookies, PIN storage) |
| `DB_*` / `REDIS_*` | PG 16 / Redis 7 |
| `SANCTUM_STATEFUL_DOMAINS` | Hosts SPA |
| `LEGACY_ERP_BASE_URL` | `api/white_label` |
| `LEGACY_ERP_TOKEN` | Token WL — **header/body, nunca query** |
| `SIPPULSE_SOAP_URL` | VPN SOAP |
| `SIPPULSE_SOAP_USERNAME` / `SIPPULSE_SOAP_PASSWORD` | SOAP |
| `SIPPULSE_REPORTS_HOST` / `SIPPULSE_REPORTS_USERNAME` / `SIPPULSE_REPORTS_PASSWORD` / `SIPPULSE_REPORTS_DATABASE` | CDR read-only |
| `MEPAY_BASE_URL` / `MEPAY_API_KEY` / `MEPAY_WEBHOOK_SECRET` | Cobrança + solicitação NFCom 62 (E5) |
| `MESIGN_BASE_URL` / `MESIGN_API_KEY` / `MESIGN_WEBHOOK_SECRET` | Equivale a `PACO_ASSIGN_*` no sales |
| `MEDNS_API_URL` / `MEDNS_API_KEY` | Wildcard TLS DNS-01 |
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_BUCKET` / `AWS_DEFAULT_REGION` | Bucket **privado** |
| `SMS_*` / `MAIL_*` | NotificationPort |
| `LAB_ENFORCE_ALLOWLIST` | `true` em lab |

Rotação: tokens de API da revenda 90 dias; `APP_KEY` só com plano de recifrar cookies; webhook secrets a cada incidente ou 12 meses (`PADRÃO PROPOSTO`). TLS: todo HTTP client dos adapters com verify on — teste Pest `it('refuses adapters with tls verify disabled')`.

---

## 5. Webhooks

| Fonte | Rota | Header | Verificação |
|-------|------|--------|-------------|
| meSign | `POST /api/v1/webhooks/paco-assign` | `X-Paco-Signature: sha256=…` | `hash_hmac('sha256', rawBody, secret)` + `hash_equals` |
| mePay | `POST /api/v1/webhooks/mepay` (`PADRÃO PROPOSTO`; path **A VERIFICAR**) | HMAC próprio quando a spec existir | Idemem |

Pipeline: (1) secret configurado senão 503; (2) HMAC; (3) `event_id` + timestamp; (4) insert `integration.inbox_webhooks` unique(`provider`,`event_id`); (5) job idempotente. Replay fora da janela de 5 min → 401. Eventos desconhecidos → 200 `{received:true}` sem efeito colateral.

---

## 6. Integridade de input e preço

Dívida R1 (`PLAN-vc1-wholesale.md` §7; `ANALYSIS-vc1-wholesale-pivot.md` §4; `VC1-Intelbras/app/Http/Controllers/Intelbras/ContratacaoController.php` L126 lê `totalCompra` do browser).

Regras:

1. O cliente/SPA envia **itens** (plano, DIDs, canais, franquia), nunca o total.
2. O servidor recalcula com a versão vigente de `pricing.*` / `catalog.plan_versions`.
3. Ao emitir (`proposal.stage = sent`) grava snapshot JSON imutável em `sales.proposals` (preço, marca, cláusula de contratante, modo `billing_mode`).
4. Aceite e contrato usam o snapshot, não a tabela atual.
5. Fatura usa rating 30/6 + `max(consumo, compromisso)` no servidor (`PRODUCTS-vc1.md` exemplo R$ 780 → cobra R$ 1.000).
6. Teste: PATCH público com `total_cents` menor que o snapshot → 422 e total inalterado.

---

## 7. Arquivos

| Tipo | Tabela | Disco |
|------|--------|-------|
| Conta telefônica / docs de portabilidade | `customers.portability_files` | Bucket **privado** (não `public/storage`) |
| Outorga STFC | `tenancy.stfc_licenses` | Idem |
| PDF proposta / contrato assinado | `sales.*` | Privado; PDF gerado no servidor (DomPDF) |

Dívida R3: `VC1-Intelbras/.../ContratacaoController.php` L753–755 faz `storeAs('portabilidades', $nomeArquivo)` e `asset('storage/portabilidades/'.$nomeArquivo)` — URL pública previsível (`nome-numero.ext`). **Não replicar.**

`PADRÃO PROPOSTO`:

- Nome de objeto = UUID; MIME allowlist (`application/pdf`, `image/jpeg`, `image/png`); max 10 MB.
- Download só via URL assinada TTL 15 min.
- Antivírus **ClamAV** no worker de upload; infectado → reject + audit.
- Retenção: portabilidade 24 meses após `completed_at` (D-DB-12); STFC enquanto a revenda estiver `active` + 5 anos; depois delete do bucket + soft-delete metadados.

---

## 8. LGPD

Controladora do tratamento de dados pessoais do **cliente final** depende de `reseller.billing_mode` (não há split):

| Modo | Controladora dos dados do cliente final | Operadora | Emissora fiscal |
|------|-----------------------------------------|-----------|-----------------|
| `vc1_invoices` | **VC1** (contrato com o cliente é da VC1) | Revenda (atendimento N1 e gestão comercial, cláusula na proposta) | **RLGR** CNPJ 57.675.896/0001-26 (emitente legal; emissão via mePay) |
| `reseller_invoices` | **Revenda** (contrato da revenda; exige STFC) | VC1 (provisionamento, rating, extrato tarifado) | Revenda (commerce-vc1 **não** emite NF ao cliente; só statement) |

DPA escrito com cada revenda antes de `status = active`. No modo `vc1_invoices` a revenda é operadora; no modo `reseller_invoices` a VC1 é operadora. RLGR é operadora fiscal (emissão NFCom 62) quando a VC1 fatura.

### 8.1 Inventário de dados pessoais

| Dado | Tabela | Finalidade | Base legal (LGPD) | Retenção (`PADRÃO PROPOSTO`) | Quem acessa |
|------|--------|------------|-------------------|------------------------------|-------------|
| Nome, e-mail, telefone de usuário | `identity.users` | Conta e 2FA | Execução de contrato, art. 7º V | Conta + 12 meses após exclusão | O próprio; admin do tenant; staff suporte |
| Razão social, CPF/CNPJ, endereço | `customers.customers` | Contrato, fatura, NF | Contrato art. 7º V; obrigação legal art. 7º II | 5 anos após encerramento (fiscal) | Finance/owner do controlador; `vc1_finance` se `vc1_invoices` |
| Contatos da proposta | `customers.contacts`, `sales.proposals` | Funil e PIN | Contrato / pré-contratual 7º V | Proposta perdida: 12 meses; ganha: segue o contrato | Seller + owner do tenant |
| Docs de portabilidade | `customers.portability_files` | Portabilidade numérica | Contrato 7º V | 24 meses após `completed_at` (D-DB-12) | Ops da revenda; `vc1_ops` |
| Outorga STFC | `tenancy.stfc_licenses` | Elegibilidade `reseller_invoices` | Obrigação legal / contrato | Vida da revenda + 5 anos | `vc1_ops`, `reseller_owner` |
| Conta SIP, DID | `telco.sip_accounts`, `telco.dids` | Prestação do serviço | Contrato 7º V | Vida da assinatura + 12 meses | App cliente; ops |
| **CDR** (números A/B, horário, duração) | `rating.cdr_imports`, `rating.rated_calls` | Tarifação 30/6, contestação | Contrato 7º V; dever de guarda setorial | Bruto 13 meses; tarifado 5 anos (D-DB-06) | Rating job; finance do controlador; **nunca** seller em massa |
| Faturas, pagamentos, wallet | `billing.*` | Cobrança, repasse | Contrato 7º V; legal 7º II | 5 anos | Finance do controlador |
| NFCom / chave 44 dígitos | `fiscal.fiscal_documents` | Obrigação fiscal | Art. 7º II | 5 anos | `vc1_finance`; mePay (operador) + emissor terceiro (suboperador mePay) |
| Envelope meSign | `sales.signature_envelopes` | Prova de aceite | Contrato 7º V | 5 anos | Partes do contrato |
| Logs de auditoria (financeiro/admin) | `audit.audit_log` | Segurança, LGPD art. 46 | Legítimo interesse 7º IX | **5 anos** | `vc1_admin`, `vc1_finance` |
| IP / User-Agent de login e acesso | `audit.audit_log` | Abuso | Legítimo interesse 7º IX | **12 meses** | `vc1_admin` |

CDR **é dado pessoal** (números e padrão de tráfego). Não vai para data lake sem anonimização. Jobs de reconciliação usam agregados quando possível.

### 8.2 Direitos do titular

Canal: e-mail do controlador (VC1 ou revenda, conforme o modo) + tela “meus dados” no app. Prazos LGPD (15 dias, prorrogáveis). Operações: acesso, correção, portabilidade (JSON da org), exclusão quando cabível, oposição a marketing (MVP sem marketing). Fiscal e CDR no prazo de guarda **não** se apagam — resposta explica a base legal. Staff registra o pedido em `audit.audit_log`.

### 8.3 Mascaramento em logs

Nunca logar: senha, PIN, `APP_KEY`, tokens, corpo de webhook cru com PII, CDR completo. CPF/CNPJ mascarados (`***` + 4 finais). E-mail `a***@dominio`. Telefone DDD + 4 finais. `dd()` / stack trace **proibidos** em resposta HTTP (dívida SEC-001 do cron WP).

---

## 9. Trilha de auditoria (`audit.audit_log`)

Campos: `id`, `actor_id`, `impersonated_user_id`, `reseller_id`, `action`, `entity_type`, `entity_id`, `before_json` (sem segredos), `after_json`, `ip`, `user_agent`, `created_at`. Append-only.

Eventos mínimos: login ok/falha, 2FA, reset senha, impersonation start/end, mudança de `billing_mode` / STFC / price base, emit/accept/cancel proposta, snapshot, recarga, block/unblock SIP, payout, NFCom import/autorização, upload/download arquivo, CRUD de token API, hit/miss allowlist LAB, recusa TLS, webhook HMAC fail.

---

## 10. Rate limiting (`PADRÃO PROPOSTO`)

| Endpoint | Limite |
|----------|--------|
| Global autenticado | 100 req/min / user |
| Login / forgot-password | 5 / min / IP |
| PIN proposta | 10 / hora / IP+slug + lockout 5/15 min |
| Upload | 10 / min / user |
| Webhook | 60 / min / IP; HMAC inválido 10 / min |
| API token | 60 / min / token |
| Export CDR/extrato | 5 / min |

Resposta 429 **sem** `exception` / `trace` (já testado no sales).

---

## 11. Backups e criptografia

PG e bucket: backups cifrados no PBS `backup.beesy.com.br` (brief §5). TLS em trânsito (wildcard ACME DNS-01 meDNS). Disco de VM cifrado `PADRÃO PROPOSTO` (meCloud). Restore testado trimestralmente pelo operador — o agente **não** dispara restore.

---

## 12. Regras de LAB

1. Só IDs em `integration.lab_allowlist` (projeto WL demo + subscribers SIP demo).
2. Qualquer write fora → adapter recusa + audit; teste Pest obrigatório.
3. Sem dados reais de cliente; fixtures anonimizados (CNPJ 00000000000191, nomes fictícios, CDRs sintéticos).
4. Criar conta demo **no legado de produção** = aprovação do operador.
5. Hosts `*.lab.vc1.com.br`; secrets distintos de PROD.
6. Sem wildcard DNS público extra sem piloto.

---

## 13. Dívidas de segurança do legado — não replicar

| Dívida | Evidência | O que commerce-vc1 faz |
|--------|-----------|------------------------|
| Totais calculados no browser; servidor aceita `totalCompra` | `VC1-Intelbras/resources/views/pages/intelbras/contratacao/contratacao.blade.php` L316; `ContratacaoController.php` L126; ANALYSIS R1; PLAN R1 | Totais só servidor; snapshot imutável |
| TLS verify off | `VC1-Intelbras/app/Models/Api.php` (`CURLOPT_SSL_VERIFYPEER, false` em todos os métodos, ex. L42); `easy_cicerone/docs/FINDINGS.md` SEC-003 (`faturamento.php` SyncNFe/Omie) | Verify on; teste de regressão |
| Token na query string | `VC1-Intelbras/app/Models/Api.php` L32: `"planos-intelbras?".http_build_query(['TOKEN' => ...])`; rotas `/boas-vindas/{token}` | Bearer / body; PIN nunca na URL |
| Docs de portabilidade públicos e nome previsível | `ContratacaoController.php` L753–755 `asset('storage/portabilidades/'.$nomeArquivo)` | Bucket privado + UUID + URL assinada |
| Cron NF sem autenticação (`dd()` vaza id) | FINDINGS SEC-001 `gerar_nota_fiscal_mensal_vc1.php` | Jobs CLI autenticados por scheduler; sem HTTP público de emissão |
| JSON fiscal concatenado (injection) | FINDINGS SEC-002 (legado SyncNFe) | `json_encode` no adapter mePay; nunca concatenação manual |
| Import NFCom sem consultar autorização (~1.600 notas em erro) | FINDINGS QA-009 | `ConsultaNFModelo62` + chave 44 dígitos antes de `authorized` |
| Pasta pública do funil `wl_label` | PLAN R3 | Não reusar `storage` público do funil |

---

## 14. Plano de testes de segurança

1. **Pest** — authN/authZ, PIN lockout, HMAC, replay, idempotência, cross-tenant (404), allowlist LAB, totais servidor, TLS verify, mascaramento de log, 429 sem stack.
2. **Dependency scan no CI** — `composer audit` e `npm audit --audit-level=high` (`~/.cursor/skills/capabilities/security-conventions.md`); HIGH/CRITICAL quebram o PR.
3. **Workflows Beesy** (`~/.cursor/skills/ci-automations`): `beesy-pr-security-review.yml` (Merlin, `ANTHROPIC_API_KEY`), `beesy-validation-gate.yml` se não houver CI nativa, coverage. PRs de fork = no-op (sem `pull_request_target`).
4. **SAST** Semgrep no CI (padrão Beesy).
5. **Pentest externo antes do go-live** (`PADRÃO PROPOSTO`) cobrindo as 8 superfícies; findings viram `docs/ISSUES.md` e bloqueiam produção se HIGH/CRITICAL.
6. Nenhuma emissão fiscal, SOAP de block ou cron WP alterado sem gate do operador.

---

## Itens para 10-DECISIONS

| ID | Linha | Default (`PADRÃO PROPOSTO`) |
|----|-------|-----------------------------|
| D-SEC-01 | 2FA usuários da revenda | Opcional; recomendado para `reseller_owner` |
| D-SEC-02 | 2FA usuários do cliente final | Opcional |
| D-SEC-03 | Plugin 2FA staff | TOTP Filament 4 nativo (ou plugin equivalente); **obrigatório** |
| D-SEC-04 | Timeout sessão staff | 8 horas |
| D-SEC-05 | PIN proposta | 6 dígitos; 5 falhas → 15 min; 10 tentativas/h por IP+slug; cookie TTL 24 h (sales-mework360) |
| D-SEC-06 | Expiração token API | 90 dias; prefixo `vc1_`; hashed |
| D-SEC-07 | Impersonation | Só `vc1_admin` e `vc1_support`; 30 min; banner + audit; sem download em massa |
| D-SEC-08 | Janela de replay de webhook | 5 minutos; unique `(provider, event_id)` |
| D-SEC-09 | Path HMAC mePay | `POST /api/v1/webhooks/mepay` quando a spec existir (**A VERIFICAR**) |
| D-SEC-10 | TTL URL assinada | 15 minutos |
| D-SEC-11 | Antivírus de upload | ClamAV no worker; reject + audit |
| D-SEC-12 | Retenção CDR | Bruto **13 meses**; tarifado 5 anos (ver D-DB-06) |
| D-SEC-13 | Retenção cadastro/fatura/NF | 5 anos após encerramento |
| D-SEC-14 | Retenção portabilidade | 24 meses após `completed_at` (ver D-DB-12) |
| D-SEC-15 | Retenção audit log | Trilha financeira/admin **5 anos**; autenticação/acesso **12 meses** |
| D-SEC-16 | Rotação webhook secrets | 12 meses ou pós-incidente |
| D-SEC-17 | Rate limit login | 5 / min / IP |
| D-SEC-18 | Pentest pré go-live | Obrigatório; HIGH/CRITICAL bloqueiam produção |
| D-SEC-19 | Disco de VM cifrado | Sim, meCloud |
| D-SEC-20 | 404 vs 403 cross-tenant | Sempre 404 (não vazar existência) |
| D-SEC-21 | Modelo DPA | Assinado antes de `reseller.status = active`; papéis conforme `billing_mode` |
| D-SEC-22 | Custo de fatura inadimplente no mínimo (`vc1_invoices`) | Só conta o **pago** (já no brief; impacto LGPD/financeiro, não de acesso) |
