WordPress Plugin Security Checklist — MIDDAG Account
Checklist focado em seguranca para o plugin middag-account. Claude Code: validar TODOS os itens antes de marcar codigo security-sensitive como completo.
Companion Rules
| Rule | Foco |
|---|---|
wp-plugin-best-practices.md | Checklist de qualidade (15 secoes) |
wp-plugin-patterns.md | Padroes de arquitetura |
wp-plugin-testing-strategy.md | Estrategia de testes (incluindo seguranca) |
wp-plugin-workflow.md | Ciclo de desenvolvimento |
1. Input Validation & Sanitization
REST API Parameters
- [ ]
sanitize_text_field()em todas entradas texto - [ ]
sanitize_email()em campos de email - [ ]
sanitize_textarea_field()em texto multi-linha - [ ]
absint()em IDs numericos e contadores - [ ]
sanitize_url()/esc_url_raw()em URLs - [ ]
wp_kses_post()em conteudo HTML (descricoes, notas) - [ ]
array_map('sanitize_text_field', $array)em arrays de input - [ ] Type casting:
(int),(float),(bool)em parametros tipados - [ ] Enum validation: verificar valor esta na lista permitida antes de usar
Form Submissions (Admin/Frontend)
- [ ]
wp_verify_nonce()em todo form handler - [ ]
wp_nonce_field()em todo form output - [ ]
check_admin_referer()em form submissions admin - [ ]
check_ajax_referer()em AJAX handlers
2. Output Escaping
HTML Context
- [ ]
esc_html()em toda saida texto em HTML - [ ]
esc_attr()em valores de atributos HTML - [ ]
esc_url()em atributos href/src - [ ]
esc_textarea()em conteudo de textarea - [ ]
wp_kses()em HTML submetido pelo usuario
JavaScript Context
- [ ]
esc_js()em strings JS inline - [ ]
wp_json_encode()para dados passados ao JS - [ ]
wp_add_inline_script()para dados PHP-to-JS (Inertia props)
SQL Context
- [ ]
$wpdb->prepare()em TODA SQL raw (sem excecoes) - [ ] Nunca concatenar input do usuario em strings SQL
- [ ] Usar
%d,%s,%fplaceholders - [ ] Preferir WP query APIs:
WP_Query,get_posts(),get_post_meta()
3. Authorization & Multi-Tenant Isolation
REST API
- [ ] Toda rota tem
permission_callback(nunca__return_truepara write ops) - [ ]
current_user_can()checks por capability, nao role - [ ] Object-level permissions: verificar usuario pode acessar recurso especifico
- [ ] Rate limiting em endpoints publicos
Organization-Scoped Permissions
- [ ] Header
X-Middag-Organizationvalidado em toda request API - [ ] Usuario pertence a Organization requisitada (verificar Collaborator membership)
- [ ] Collaborator role (admin, member, viewer) verificado antes da acao
- [ ] Dados de uma Organization NUNCA vazam para outra (hard boundary)
- [ ] Queries filtram por
organization_idem TODA consulta - [ ] Nenhum endpoint retorna dados cross-Organization sem permissao explicita
Admin Pages
- [ ]
current_user_can('manage_options')na registration de admin pages - [ ] Meta box access gated por capability relevante
- [ ] Bulk actions verificam capability por item
Frontend Actions (Portal NextJS)
- [ ] Toda acao via API autenticada (JWT ou nonce)
- [ ] Organization boundary enforced no backend, nao confiar no frontend
- [ ] Token expiry e rotation implementados
4. JWT RS256 Authentication
Token Issuance
- [ ] RSA key pair (RS256) — nunca HS256 com shared secret
- [ ] Private key armazenada em
wp-content/fora do webroot ou em variavel de ambiente - [ ] Private key com permissoes restritas (0600 ou equivalente)
- [ ] Public key disponivel para validacao por servicos externos
- [ ] Token payload inclui:
sub(user_id),iss(site_url),iat,exp,org(organization_id) - [ ] Token lifetime curto (15-30 minutos)
Token Refresh
- [ ] Refresh token com lifetime maior (7-30 dias)
- [ ] Refresh token armazenado server-side (nao apenas client)
- [ ] Refresh token revogavel (blacklist ou whitelist)
- [ ] Rotacao de refresh token a cada uso (single-use)
- [ ] Refresh endpoint nao aceita access token expirado ha mais de X horas
Token Validation
- [ ] Verificar assinatura RS256 em toda request
- [ ] Verificar
exp(expirado = rejeitar) - [ ] Verificar
iss(issuer = site_url) - [ ] Verificar
sub(user exists e esta ativo) - [ ] Verificar
org(usuario pertence a Organization) - [ ] Constant-time comparison para tokens
- [ ] Rejeitar tokens com claims invalidos ou inesperados
Key Management
- [ ] Rotacao de RSA keys suportada (key versioning)
- [ ] Keys anteriores aceitas por periodo de graca
- [ ] Alerta/log quando key rotation necessaria
- [ ] Nunca commitar private keys no repositorio
5. Data Protection
Sensitive Data
- [ ] Nenhuma credencial no source code
- [ ] Nenhuma API key no client-side JS
- [ ] PII do cliente acessivel apenas a usuarios autorizados
- [ ] Emails nao expostos em REST responses para usuarios nao-autorizados
- [ ] Organization data isolada (multi-tenant hard boundary)
File Operations
- [ ]
wp_upload_dir()para paths de upload (nunca hardcode) - [ ]
wp_check_filetype()em arquivos uploaded - [ ] Sem
eval(),create_function(), oupreg_replace()com modifiere - [ ] Sem
extract()em templates — usar$args['key']explicito - [ ]
realpath()validation em file paths para prevenir traversal
API Keys & Secrets
- [ ] HubSpot API key em
wp_options(encrypted ou obfuscated) - [ ] Stripe API keys em
wp_options(separadas live/test) - [ ] Banco Inter certificados em diretorio protegido
- [ ] Cloudflare API token em
wp_options - [ ] Nenhum secret logado em plain text
6. CSRF Protection
- [ ] Todas operacoes state-changing requerem nonce
- [ ] Nonce lifetime apropriado (default 24h geralmente OK)
- [ ] Nonce action names especificos:
middag_approve_{type}_{id}, nao generico - [ ] Admin AJAX:
check_ajax_referer()com action especifica
7. WordPress-Specific Security
Custom Post Types
- [ ] Nunca expor CPT data via WP REST API padrao (disable
show_in_restse usando API custom) - [ ] Meta keys com prefixo
_middag_para proteger de exposicao acidental - [ ]
register_meta()comauth_callbackpara meta fields sensiveis - [ ]
map_meta_cap => truepara controle granular por CPT
Post Meta Security
- [ ] Nunca confiar em meta values sem sanitizacao ao ler
- [ ]
update_post_meta()com valores sanitizados - [ ]
delete_post_meta()com capability check antes - [ ] Meta queries nao expoe dados cross-Organization
Settings
- [ ] Settings access gated por
manage_optionscapability - [ ] Post-save validation para enum/range values
- [ ] Secrets armazenados com
wp_optionsautoload=false
8. Webhook Signature Validation
HubSpot Webhooks
- [ ] Validar
X-HubSpot-SignatureouX-HubSpot-Signature-v3header - [ ] Signature calculada com app secret + request body
- [ ] Rejeitar requests sem signature valida (HTTP 401)
- [ ] Timestamp validation para prevenir replay attacks
- [ ] Log webhook rejections para auditoria
Stripe Webhooks
- [ ] Validar
Stripe-Signatureheader comwebhook_secret - [ ] Usar
\Stripe\Webhook::constructEvent()para verificacao - [ ] Rejeitar eventos com signature invalida
- [ ] Timestamp tolerance configuravel (default 300s)
- [ ] Webhook secret diferente por conta (live vs test)
- [ ] Idempotency: verificar
event.idnao processado anteriormente
Banco Inter Webhooks
- [ ] Validar assinatura do callback com certificado
- [ ] Rejeitar callbacks sem autenticacao valida
- [ ] Log todas operacoes financeiras
Regras Gerais de Webhook
- [ ] Endpoint aceita apenas POST
- [ ] Content-Type validation (
application/json) - [ ] Body size limit para prevenir abuse
- [ ] Rate limiting no endpoint de webhook
- [ ] Retornar 200 rapidamente, processar async se necessario
9. Error Handling
- [ ] Sem stack traces expostos ao usuario (verificar
WP_DEBUG) - [ ]
error_log()para erros internos, admin notices para user-facing - [ ] REST API retorna HTTP status codes apropriados, nao 200 com body de erro
- [ ] Nunca expor estrutura do banco em mensagens de erro
- [ ] Catch exceptions no boundary — nao deixar bubble ate WP
- [ ] JWT errors retornam 401 com mensagem generica (nao revelar motivo exato)
10. Third-Party Dependencies
- [ ] Composer packages auditados:
composer audit - [ ] npm packages auditados:
npm audit - [ ] Lock files commitados (
composer.lock,package-lock.json) - [ ] Dependencies pinned a major versions especificas
- [ ] Chatwoot PHP SDK (ramiroestrella/chatwoot-php-sdk) auditado
11. Distribution Security
- [ ] Sem arquivos
.envna distribuicao - [ ] Sem configs debug/dev no ZIP
- [ ] Sem credenciais ou fixtures de teste na distribuicao
- [ ]
.distignoreexclui:.claude/,.aiox-core/,tests/,docs/,.git/,migration/ - [ ] Verificar conteudo do ZIP antes do release
- [ ] RSA private keys NUNCA incluidas no ZIP
12. Multi-Tenant Data Isolation
Organization Boundary (Critical)
- [ ] Todo dado pertence a uma Organization (foreign key obrigatorio)
- [ ] Queries SEMPRE filtram por
organization_iddo usuario logado - [ ] REST API middleware valida Organization antes do controller
- [ ] Nenhum endpoint permite
organization_idcomo parametro do cliente (apenas header/token) - [ ] Testes de penetracao para IDOR (Insecure Direct Object Reference)
- [ ] Admin (superadmin) pode acessar cross-Organization com audit log
Collaborator Permissions
- [ ] Roles: admin, member, viewer — verificados em toda acao
- [ ] Admin pode convidar/remover collaborators
- [ ] Member tem acesso read/write limitado
- [ ] Viewer tem acesso read-only
- [ ] Permissoes verificadas no backend (nunca confiar no frontend)
Severity Levels
| Level | Resposta | Exemplo |
|---|---|---|
| CRITICAL | Bloquear release. Corrigir imediatamente. | SQL injection, auth bypass, XSS admin, JWT leak, cross-org data leak |
| HIGH | Corrigir antes do proximo release. | Missing nonce, capability check insuficiente, webhook sem validacao |
| MEDIUM | Corrigir no sprint. | Missing output escaping em campo low-risk, token lifetime longo |
| LOW | Rastrear e corrigir. | Informational disclosure, verbose errors |
Automated Checks
bash
# Static analysis identifica type errors e alguns problemas de seguranca
composer check:stan
# Code style garante padroes consistentes
composer check:style
# Triggers para revisao manual:
# - Novo REST endpoint adicionado
# - Novo form handler adicionado
# - Nova database query adicionada
# - Novo file upload handler adicionado
# - Novo webhook handler adicionado
# - Novo JWT claim adicionado
# - Alteracao em Organization boundary logic
# - Nova integracao externa adicionadaMIDDAG Account WordPress Plugin Security Checklist v1.0 — Morgan (@pm), Abril 2026