Files
claude-plugins/wordpress/skills/seguranca-descomplicar/SKILL.md
T
Claude Code 4a55d51329 feat(wordpress): EMCP Tools workflow skills + widget catalogs + pending wordpress skills
- emcp-page-building, emcp-content-ops, emcp-site-audit: EMCP Tools MCP
  workflows verified live (atomic/legacy interplay, apply-template overwrite
  risk, create-theme-template goes live immediately, false-positive malware
  pattern in scan-security, change ledger + rollback)
- elementor-pro-widgets: 30+5 native Elementor Pro widgets (curated catalog)
- elementskit-widgets / powerpack-widgets: 42 + 97 third-party widgets,
  widgetType extracted from plugin source (not guessed by convention)
- plugin.json bumped 1.2.0 -> 1.3.0, keywords + description updated
- commits pending wordpress skills already present as untracked files
  (emcp-tools, wordfence, wp-activity-log, seguranca-descomplicar,
  webp-express, wp-fastest-cache, wp-font-perf, wp-meteor,
  wp-activity-log, redis-object-cache, app-for-cloudflare) and pending
  edits (rank-math, wp-cli, wp-content-seo-gate)
2026-08-19 03:44:00 +01:00

31 KiB
Raw Blame History

name, description
name description
seguranca-descomplicar Gestão do plugin próprio "Segurança Descomplicar" (seguranca-descomplicar, v1.4.2) via WP-CLI e REST API nos 7 sites do bundle CWP. Cobre os 5 módulos — Security Headers (CSP/XFO/security.txt), Wordfence Agent (endpoint REST autenticado para sites sem SSH), Login/dash (URL de login customizada), Descomplicar Monitor (telemetria para dash.descomplicar.pt), Admin Hardening (Classic Editor, emails de update, nags) — com comandos `wp seguranca-descomplicar`, options `segdesc_*`, e gotchas reais de produção. Usar quando "seguranca-descomplicar", "plugin de segurança Descomplicar", "CSP", "X-Frame-Options", "security.txt", "wordfence agent", "login dash", "/dash", "descomplicar monitor", "admin hardening", "classic editor forçado", "esconder avisos de update", "token do agente", "allowlist IP wordfence agent".

/seguranca-descomplicar — Plugin próprio de segurança WordPress (5 módulos)

Plugin normal (não mu-plugin, decisão explícita), instalado em wp-content/plugins/seguranca-descomplicar/, gerido inteiramente pela Descomplicar® — código-fonte único, versionado em 71.Seguranca/plugin/seguranca-descomplicar/seguranca-descomplicar.php, distribuído por SCP/cp aos 7 sites do bundle. Substitui: wordsense-agent.php (plugin antigo), mu-plugin descomplicar-security-headers.php, plugin autónomo descomplicar-monitor, e todos os snippets WPCode/Code Snippets de login /dash e admin hardening que estavam clonados byte-a-byte em 6 dos 7 sites.

Fonte: código-fonte completo lido nesta sessão (1494 linhas) + CONFIG-Plugins-Referencia.md §2 (config ao vivo verificada nos 7 sites) + index.md (changelog de 8 sessões de produção, bugs reais e correcções).

Gerido/consumido por (ferramentas externas, fora desta skill): 71.03-SecHeaders/tools/secheaders.py (módulo 1) e 71.01-Wordsense/tools/wordsense.py, transporte http (módulo 2).


Contexto CWP — sempre obrigatório

# Local (quando o path existe no filesystem do agente)
wp seguranca-descomplicar <comando> --path=$PATH

# Via SSH (servidor real)
ssh -p 9443 root@server.descomplicar.pt \
  "wp seguranca-descomplicar <comando> --path=$PATH --allow-root"

Namespace REST do plugin: seguranca-descomplicar/v1 (todos os endpoints exigem Authorization: Bearer <token>, token de 64 chars hex em segdesc_agent_token).


Decision tree — qual módulo/comando usar

Preciso de Módulo Comando/opção
Ver/mudar CSP, XFO, security.txt 1 wp seguranca-descomplicar headers status|set
Diagnosticar/ajustar Wordfence sem SSH (via HTTP) 2 REST seguranca-descomplicar/v1/wordfence/*
Ligar/mudar a URL de login custom 3 wp seguranca-descomplicar login status|enable|disable|slug|block-wp-login
Ver estado da telemetria para o dashboard 4 wp option get descomplicar_monitor_* (sem comando CLI próprio)
Ligar/desligar Classic Editor, emails de update, nags 5 wp seguranca-descomplicar hardening status|enable|disable
Rodar o token do agente REST — wp seguranca-descomplicar token --rotate
Restringir o REST endpoint a IPs específicos — wp seguranca-descomplicar allowlist --set=IP1,IP2

Referência de constantes (SEGDESC_*)

Todas as constantes definidas no plugin (topo do ficheiro, antes de qualquer hook), agrupadas por função — confirma cobertura total do código-fonte (26 constantes no total).

Identificação

Constante Valor Nota
SEGDESC_VERSION '1.4.2' Devolvida em wordfence/status.agent_version
SEGDESC_NS 'seguranca-descomplicar/v1' Namespace REST

Options (segdesc_*, persistidas em wp_options) — ver tabelas de cada módulo abaixo para defaults/comportamento; nomes reais: segdesc_agent_token, segdesc_headers_enabled, segdesc_csp, segdesc_xfo, segdesc_csp_report_only, segdesc_security_txt, segdesc_ip_allowlist, segdesc_nginx_csp_covered, segdesc_login_enabled, segdesc_login_slug, segdesc_login_block_wplogin, segdesc_hardening_enabled, segdesc_hardening_classic_editor, segdesc_hardening_update_emails, segdesc_hardening_update_nags.

Defaults/valores hardcoded (literais PHP usados como fallback, não são options)

Constante Valor
SEGDESC_DEFAULT_LOGIN_SLUG 'dash'
SEGDESC_DEFAULT_CSP CSP de enforcement (ver secção 1)
SEGDESC_DEFAULT_XFO 'SAMEORIGIN'
SEGDESC_DEFAULT_CSP_REPORT_ONLY CSP Report-Only (ver secção 1)
SEGDESC_DANGEROUS_KEYS serialize(['waf_status', 'isPaid', 'apiKey']) — chaves wfconfig que exigem force_dangerous=true em /wordfence/config

Rate-limit do endpoint REST (só editável no código, sem option/wp-cli)

Constante Valor Nota
SEGDESC_RATE_LIMIT_WINDOW 60 (segundos) Janela do tecto geral de pedidos
SEGDESC_RATE_LIMIT_MAX 60 Pedidos nominais/IP/janela (~30 reais, ver nota da dupla invocação)
SEGDESC_RATE_LIMIT_FAIL_MAX 10 Falhas nominais/IP/janela antes do lockout (~5 reais)
SEGDESC_RATE_LIMIT_FAIL_LOCKOUT 900 (segundos = 15 min) Duração do lockout por IP

1. Security Headers — CSP, X-Frame-Options, security.txt

Hook send_headers (envia CSP/XFO/CSP-Report-Only) + hook init (serve /.well-known/security.txt, RFC 9116).

Options (segdesc_*)

Option Default Nota
segdesc_headers_enabled '1' (definida na activação) Liga/desliga o módulo inteiro
segdesc_csp SEGDESC_DEFAULT_CSP (definida na activação) CSP de enforcement — só é enviada se nginx_csp_covered ≠ '1'
segdesc_xfo SAMEORIGIN (definida na activação) Sempre enviada, independente do nginx
segdesc_csp_report_only SEGDESC_DEFAULT_CSP_REPORT_ONLY (fallback em código, não persistida na activação) Sempre enviada — telemetria; único mecanismo de report em sites sem template nginx
segdesc_security_txt '' Sem default — vazio = ficheiro não é servido; conteúdo bruto do .well-known/security.txt
segdesc_ip_allowlist '' (vazio = sem restrição) CSV de IPs — só afecta os endpoints REST do módulo 2, não o site público
segdesc_nginx_csp_covered '0' '1' em sites com o template nginx aplicado — desliga o CSP de enforcement do plugin (evita intersecção de directivas entre 2 CSPs); XFO continua sempre activo

Comandos

# Estado
wp seguranca-descomplicar headers status --path=$PATH

# Actualizar CSP/XFO
wp seguranca-descomplicar headers set --csp="default-src 'self'; script-src 'self' https:" --path=$PATH
wp seguranca-descomplicar headers set --xfo=SAMEORIGIN --path=$PATH

# security.txt a partir de ficheiro local
wp seguranca-descomplicar headers set --security-txt-file=/tmp/security.txt --path=$PATH

# Ligar/desligar o módulo inteiro
wp seguranca-descomplicar headers set --enabled=1 --path=$PATH

# Opções que o wp-cli custom NÃO cobre — usar wp option directo
wp option get segdesc_nginx_csp_covered --path=$PATH
wp option update segdesc_nginx_csp_covered 1 --path=$PATH   # sites com template nginx aplicado
wp option get segdesc_ip_allowlist --path=$PATH

CSP real em produção (v1.4.2 — permissivo por directiva, sem lista branca de domínios)

default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;
style-src 'self' 'unsafe-inline' https:;
img-src 'self' data: https:;
font-src 'self' data: https:;
frame-src https:;
frame-ancestors 'self';
object-src 'none'

Decisão explícita do utilizador (10ª sessão): uma lista branca por domínio (frame-src youtube.com google.com ...) é manutenção infinita — cada embed novo obriga a voltar a editar. frame-src https: permite qualquer iframe servido por HTTPS; o que interessa bloquear é http:// não encriptado e data:/javascript:. object-src 'none' e frame-ancestors 'self' mantêm-se restritos (sem caso de uso legítimo / protecção anti-clickjacking deste site).

Nota de produção (16-08-2026): na prática, no bundle CWP, CSP/XFO são geridos maioritariamente pelo template nginx (71.03-SecHeaders/, nível de servidor, sobrevive a cache/preload de plugin) nos sites que o têm aplicado — o módulo do plugin fica então limitado a security.txt + CSP Report-Only (telemetria, ver segdesc_nginx_csp_covered). Em emanuelalmeida.pt (piloto desde a 11ª sessão), os headers passaram a ser servidos por Cloudflare Transform Rules (token do plugin App for Cloudflare®) — nginx e este plugin continuam a enviar os seus próprios headers, mas ficam invisíveis ao tráfego real porque o set do Cloudflare se sobrepõe antes de chegar ao browser; servem só de fallback para acesso directo à origem (bypass do Cloudflare).


2. Wordfence Agent — endpoint REST autenticado (sites sem SSH)

8 rotas REST sob seguranca-descomplicar/v1, autenticação partilhada via segdesc_check_auth() (allowlist → lockout → rate-limit → Bearer token). Registadas sempre; /wordfence/status indica se o Wordfence está mesmo activo no site.

Rota Método Payload Função
/headers/status GET — Estado do módulo 1
/headers/set POST csp, xfo, security_txt, enabled Ajusta módulo 1 remotamente
/wordfence/status GET — wordfence_active, version, db_prefix
/wordfence/query POST sql Só SELECT/SHOW contra exactamente 1 tabela wf* — bloqueia JOIN, subquery, UNION, INSERT/UPDATE/DELETE/DROP/etc.
/wordfence/config POST key, value, force_dangerous UPSERT em {prefix}wfconfig; chaves perigosas (waf_status, isPaid, apiKey) exigem force_dangerous=true
/wordfence/unblock-ip POST ip DELETE FROM {prefix}wfblockediplog WHERE IP = INET6_ATON(ip)
/wordfence/whitelist-ip POST ip, reason Acrescenta "{ip} #{reason}" a neverBlockIP em wfconfig
/wordfence/scan POST — wfScanEngine::startScan() se a classe existir; senão do_action('wordfence_doScan') (fallback cron)

/headers/* — nota de proveniência: os 2 endpoints REST de headers (status/set) foram desenhados para o transporte http de 71.03-SecHeaders/tools/secheaders.py (comentário no código-fonte: "usados por secheaders.py transporte 'http' (futuro, hoje só SSH)") — presentes e funcionais, mas ainda não consumidos em produção: secheaders.py aplica correcções via SSH/wp-cli directo, não via este REST. Único consumidor real hoje é o próprio wp seguranca-descomplicar headers.

Autenticação e rate-limit (importante para clientes REST)

Authorization: Bearer <segdesc_agent_token>
  • Allowlist de IP (opcional, segdesc_ip_allowlist) verificada primeiro.
  • Rate-limit por IP: 60 pedidos/60s nominal — mas o WP_REST_Server invoca permission_callback DUAS VEZES por pedido HTTP real (confirmado ao vivo em carstuff.pt), pelo que o limite prático é ~30 pedidos reais, não 60.
  • Lockout por falhas de auth: 10 falhas nominais (~5 reais) → lockout de 15 min (900s), chaveado por IP via transient.
  • IP do cliente (segdesc_client_ip()) só confia em X-Forwarded-For quando REMOTE_ADDR é um proxy local confiável (127.0.0.1/::1) — as portas backend do CWP (2083/2031/2095) são acessíveis directamente da internet, por isso um pedido directo ao backend usa sempre REMOTE_ADDR, nunca o XFF do atacante.

Comandos WP-CLI relacionados

# Ver/rodar o token de autenticação do agente
wp seguranca-descomplicar token --path=$PATH
wp seguranca-descomplicar token --rotate --path=$PATH

# Allowlist de IP para os endpoints REST
wp seguranca-descomplicar allowlist --path=$PATH
wp seguranca-descomplicar allowlist --set=203.0.113.5,203.0.113.6 --path=$PATH
wp seguranca-descomplicar allowlist --clear --path=$PATH

Exemplo de pedido real (curl)

TOKEN=$(wp seguranca-descomplicar token --path=$PATH)
curl -s https://site.pt/wp-json/seguranca-descomplicar/v1/wordfence/status \
  -H "Authorization: Bearer $TOKEN"

3. Login /dash — URL de login customizada e segura

Substitui os antigos snippets WPCode "Alterar /wp-admin para /dash" (que deixaram de executar por E_DEPRECATED em PHP 8.2 — ver Gotchas). Handler em template_redirect prioridade 1 (antes do redirect_canonical, prioridade 10).

Comportamento quando segdesc_login_enabled = '1'

  • /<slug> (default dash): mostra o form nativo de login WP se não autenticado; wp_redirect(admin_url()) se autenticado.
  • wp-login.php directo (GET): 404 para não autenticados. O POST do form (submissão real do login) continua a funcionar — o bloqueio só intercepta GET.
  • /wp-admin/ directo: 404 para não autenticados, excepto admin-ajax.php (necessário para plugins front-end).

Hooks internos

Hook Prioridade Função
init → segdesc_login_rewrite_rule default (10) Regista add_rewrite_rule('^<slug>/?$', 'index.php?segdesc_login=1', 'top') — só se o módulo estiver ligado
query_vars → segdesc_login_query_var default (10) Regista a query var segdesc_login (necessária para o WP reconhecer o parâmetro da rewrite rule)
template_redirect → segdesc_login_handle 1 Handler principal — antes do redirect_canonical (prioridade 10)
init → segdesc_login_block_wp_login default (10) Bloqueio de wp-login.php directo (GET)
init → segdesc_login_block_wp_admin default (10) Bloqueio de /wp-admin/ directo (excepto admin-ajax.php)
update_option_segdesc_login_slug → segdesc_login_flush_rules_on_slug_change default (10) flush_rewrite_rules() automático sempre que segdesc_login_slug muda — mesmo por wp option update directo, não só pelo subcomando login slug

Options

Option Default Nota
segdesc_login_enabled '0' Módulo desligado por omissão — só activo onde pedido
segdesc_login_slug 'dash' (constante SEGDESC_DEFAULT_LOGIN_SLUG, não persistida a menos que alterada) Sanitizado para [a-z0-9_-]
segdesc_login_block_wplogin '1' Bloqueia wp-login.php directo (GET)

Comandos

wp seguranca-descomplicar login status --path=$PATH
wp seguranca-descomplicar login enable --path=$PATH     # faz flush_rewrite_rules() automático
wp seguranca-descomplicar login disable --path=$PATH
wp seguranca-descomplicar login slug --set=acesso --path=$PATH
wp seguranca-descomplicar login block-wp-login --on --path=$PATH
wp seguranca-descomplicar login block-wp-login --off --path=$PATH

status devolve enabled, slug, block_wp_login, login_url (URL completo já resolvido com home_url()).

Nota carstuff.pt (achado 10ª sessão, fora do âmbito deste módulo): /dash naquele site serve login e wp-login.php redirecciona para /dash por um mecanismo que não é o Módulo 3 (nunca activado em carstuff.pt até à migração completa) nem o snippet WPCode legado (sempre em draft) — candidato mais provável é o branda-white-labeling (login-screen custom). Login do admin funciona normalmente; não confundir com este módulo ao diagnosticar /dash nesse site especificamente.


4. Descomplicar Monitor — telemetria para dash.descomplicar.pt

Classe Descomplicar_Monitor (singleton), funde o antigo plugin autónomo descomplicar-monitor — reutiliza as MESMAS options (descomplicar_monitor_*, prefixo diferente do resto do plugin, propositado para preservar dados na migração) e o MESMO cron (descomplicar_monitor_cron). Guard is_plugin_active('descomplicar-monitor/descomplicar-monitor.php'): só instancia se o plugin antigo não estiver activo (evita duplicar envios).

Sem comando WP-CLI próprio — este módulo não regista WP_CLI::add_command. Interage-se via wp option, wp eval, ou a página de definições em Definições → Descomplicar Monitor (add_options_page, capability manage_options; grupo de settings descomplicar_monitor_settings via register_settings, hook admin_init; AJAX descomplicar_monitor_test / descomplicar_monitor_send_now, ambos com nonce próprio via check_ajax_referer).

Hooks internos

Hook Função
admin_menu → add_admin_menu Regista a página Definições → Descomplicar Monitor
admin_init → register_settings Regista o grupo descomplicar_monitor_settings (campos api_key, enabled)
descomplicar_monitor_cron → send_monitoring_data Handler do cron — envia a telemetria
cron_schedules → add_cron_interval Regista um intervalo próprio chamado twice_daily (43200s = 12h) — não é o twicedaily nativo do WordPress (nomes diferentes, mesmo valor); wp_schedule_event usa este nome custom
wp_ajax_descomplicar_monitor_test → ajax_test_connection AJAX "Testar Conexão"
wp_ajax_descomplicar_monitor_send_now → ajax_send_now AJAX "Enviar Agora"
init → ensure_cron Reagenda descomplicar_monitor_cron se wp_next_scheduled() devolver vazio

Sem register_deactivation_hook (nenhum módulo do plugin tem) — ao desactivar o plugin, o evento descomplicar_monitor_cron continua agendado no wp-cron, mas o código deixa de carregar, logo o disparo do evento não faz nada (sem handler registado) — consome um ciclo de cron sem efeito, sem erro visível. Reactivar retoma o envio automaticamente, sem reconfigurar nada (options preservadas).

Options

Option Nota
descomplicar_monitor_enabled '1'/'0' — sem module flag segdesc_* (nome legado preservado)
descomplicar_monitor_api_key Chave enviada como header X-API-Key
descomplicar_monitor_last_sent Timestamp Y-m-d H:i:s, actualizado em cada tentativa (sucesso ou falha)
descomplicar_monitor_last_status 'success' ou a mensagem de erro

Dados recolhidos (POST JSON para https://dash.descomplicar.pt/api/wp-monitor)

URL, nome, email de admin, versão WP/PHP/MySQL, flag multisite, timestamp ISO 8601 (current_time('c')) e timezone do site, tema (+ parent), lista de plugins activos c/ versão, updates pendentes (core/plugins/themes, com bloco counts agregado), contagem de posts/páginas/utilizadores/comentários, estatísticas da BD (nº de tabelas {prefix}*, tamanho em MB, prefix, transients expirados, tamanho do autoload em KB), memory_limit, upload máximo, debug mode, SSL, cron disabled, cache habilitada, e um bloco health (status good/warning/critical com lista de issues: WP_DEBUG sem log, sem SSL, uploads sem permissão de escrita, memória PHP < 64MB).

Nota: get_pending_updates() chama wp_update_plugins() e wp_update_themes() directamente antes de ler get_plugin_updates()/ get_theme_updates() — força uma verificação síncrona a api.wordpress.org em cada execução do cron de 12h, mesmo que o WordPress já tivesse um resultado em cache válido. Corre em contexto de cron (não bloqueia carregamento de página), por isso não é o mesmo problema de performance do Gotcha #3 (que era em admin_init/page load).

Comandos úteis

# Estado
wp option get descomplicar_monitor_enabled --path=$PATH
wp option get descomplicar_monitor_last_status --path=$PATH
wp option get descomplicar_monitor_last_sent --path=$PATH

# Forçar envio imediato (fora do cron 12h)
wp eval 'echo json_encode(Descomplicar_Monitor::get_instance()->send_monitoring_data());' --path=$PATH

# Confirmar que o cron está agendado (reagenda-se sozinho em cada 'init' se limpo)
wp cron event list --path=$PATH --fields=hook,next_run_relative | grep descomplicar_monitor_cron

5. Admin Hardening — Classic Editor, emails de update, nags

Substitui 3 snippets WPCode/Code Snippets clonados byte-a-byte (md5sum idêntico) em 6 dos 7 sites CWP (origem: tutorial WPBeginner). O snippet de emails tinha um bug real herdado: add_filter('auto_core_update_send_email', 'wpb_stop_auto_update_emails', ...) referenciava uma função nunca definida (wpb_stop_update_emails, sem _auto), gerando E_WARNING silencioso em cada auto-update. O módulo simplifica para __return_false directo.

Options

Option Default (quando hardening_enabled='1') Nota
segdesc_hardening_enabled '0' Módulo desligado por omissão
segdesc_hardening_classic_editor '1' use_block_editor_for_post{,_type} → __return_false
segdesc_hardening_update_emails '1' auto_{core,plugin,theme}_update_send_email → __return_false
segdesc_hardening_update_nags '1' Wipe de admin_notices/all_admin_notices/network_admin_notices (incl. avisos Wordfence) + CSS para esconder badges de contagem

Comandos

wp seguranca-descomplicar hardening status --path=$PATH
wp seguranca-descomplicar hardening enable --path=$PATH
wp seguranca-descomplicar hardening enable --no-classic-editor --path=$PATH   # liga o módulo mas deixa o bloco editor
wp seguranca-descomplicar hardening enable --no-update-emails --path=$PATH
wp seguranca-descomplicar hardening enable --no-update-nags --path=$PATH
wp seguranca-descomplicar hardening disable --path=$PATH

Como o bloqueio de nags funciona de facto (decisão de negócio: agressivo)

Decisão do Emanuel: bloquear TAMBÉM avisos Wordfence e visibilidade de updates pendentes — clientes reencaminhavam esses avisos/emails achando que eram falha da Descomplicar. Dois mecanismos distintos, nunca tocando nos transients de update (ver Gotchas #3):

  1. in_admin_header prioridade 999 → unset($wp_filter['admin_notices']) (+ all_admin_notices + network_admin_notices). Tem de ser in_admin_header, não admin_init (ver Gotchas #2).
  2. admin_head → injecta <style> que esconde #adminmenu .update-plugins, #wpadminbar .update-plugins, #adminmenu .wf-menu-badge (o badge "Wordfence N" no menu não é um hook — é HTML injectado directamente no título do item de menu por wordfenceClass.php; só CSS apanha).

Deploy aos 7 sites do bundle

Fonte do plugin canónico: 71.Seguranca/plugin/seguranca-descomplicar/ → copiado para wp-content/plugins/seguranca-descomplicar/ em cada site (tools/distribuir_e_activar_plugin.py: cp -r local se o path existir no filesystem, senão mkdir -p + scp -P 9443 + ssh ... wp plugin activate via root@server.descomplicar.pt).

Domínio Conta CWP Path
descomplicar.pt ealmeida /home/ealmeida/public_html
emanuelalmeida.pt ealmeida /home/ealmeida/emanuelalmeida.pt
carstuff.pt carstuff /home/carstuff/public_html
familyclinic.pt familycl /home/familycl/public_html
ignitionvortex.pt ignition /home/ignition/public_html
solarfvengenharia.com solarfv /home/solarfv/public_html
watercontrol.pt wtc /home/wtc/public_html
python3 tools/distribuir_e_activar_plugin.py

tools/executar_pipeline_completa.py orquestra o ciclo completo de segurança nos mesmos 7 domínios (SITES_CONFIG, campo tipo: "nossos_sites"): 1) diagnóstico externo (seguranca-check.py) → 2) diagnóstico de postura interno (postura.py → wordsense + softcheck) → 3) correcção automática de headers (secheaders.py, template nginx ou este plugin) → 4) re-análise de verificação → 5) relatório visual. Usa --confirm-fix para aplicar correcções (sem a flag, é dry-run/diagnóstico).

Estado ao vivo confirmado (16-08-2026): 5 módulos activos e coerentes nos 7 sites — headers_enabled=1, login_enabled=1 (7/7, antes eram 5/7), hardening_enabled=1 (7/7), bridge/monitor migrado 7/7. Único desvio real por site: segdesc_nginx_csp_covered só é '1' nos sites com o template nginx aplicado (todos excepto casos pontuais em rollout).

Scripts sem flags de plugin: distribuir_e_activar_plugin.py não aceita argumentos CLI — itera sempre a lista SITES completa hardcoded (cp -r local se o path existir, senão mkdir -p + scp -P 9443 + ssh ... wp plugin activate via root@server.descomplicar.pt, porta 9443). executar_pipeline_completa.py só tem uma flag, --confirm-fix (sem ela = dry-run/diagnóstico só); a lista SITES_CONFIG mapeia cada domínio para wordsense_site/softcheck_account — usados por postura.py/wordsense.py (fora desta skill), não por este plugin directamente.


Gotchas / erros reais corrigidos em produção

# Sintoma Causa raiz Correcção
1 Actualizar post_content de um snippet WPCode via $wpdb->update directo não desliga o snippet legado O WPCode mantém uma cache compilada em wp_options.wpcode_snippets, separada de wp_posts — não invalida por escrita SQL directa Apagar a option wpcode_snippets ou chamar wpcode()->cache->cache_all_loaded_snippets() depois de qualquer edição directa em BD
2 Aviso "ElementsKit Lite Premium" (ou outro plugin) sobrevive ao wipe de admin_notices Alguns plugins registam o callback em admin_notices de dentro de outro callback pendurado em admin_head — que corre depois de admin_init na sequência de bootstrap (admin_init → load-{page} → admin_head → in_admin_header → admin_notices) Wipe tem de estar em in_admin_header prioridade 999 (o último ponto seguro antes de admin_notices disparar), nunca em admin_init
3 Regressão de performance real: ~1,3s extra em todo o carregamento do wp-admin (medido: core 0,4s + plugins 0,5s + temas 0,3s) Bloquear pre_site_transient_update_* para esconder nags impede a cache nativa de 12h do WordPress de "pegar" — _maybe_update_core/plugins/themes() vêem sempre "nunca verificado" e disparam pedido HTTP síncrono a api.wordpress.org em cada page load Nunca tocar nos transients de update; esconder badges/contadores só por CSS (.update-plugins)
4 Badge "Wordfence N" no menu lateral continua visível apesar do wipe de admin_notices Não vem de nenhum hook — é HTML injectado directamente no título do item de menu por wordfenceClass.php Só CSS resolve: #adminmenu .wf-menu-badge { display: none !important; }
5 /dash dá 404, wp-login.php também dá 404 — login completamente inacessível Snippet WPCode legado usava basename(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)) sem protecção — em PHP 8.2 parse_url pode devolver null, basename(null) dispara E_DEPRECATED; o WPCode apanha o erro e marca o snippet em wpcode_snippets_errors, deixando de o executar por completo (rewrite rule, query var e redirect todos mortos) Migrado para código de plugin normal (Módulo 3), sem essa fragilidade; wp rewrite flush --hard depois de qualquer mudança de slug
6 Regra de rewrite /<slug> não persistia em alguns sites recém-activados Rewrite rules não flushed após login enable em certas condições de cache de permalinks wp rewrite flush --hard explícito após activar o módulo 3
7 security.txt continha mailto:seguranca@descomplicar.pt — mailbox inexistente Valor hardcoded em tools/executar_pipeline_completa.py (--contact seguranca@descomplicar.pt), replicado para os 7 sites no deploy inicial. Não foi apanhado pela auditoria automática (só validava presença/formato do ficheiro, não se o mailbox era real) Corrigido nos 7 sites (wp_options ao vivo) e na origem (script) para mailto:it@descomplicar.pt — mesmo endereço usado em alertEmails do Wordfence, rua/ruf do DMARC, e utilizador only-me do WSAL
8 reCAPTCHA/YouTube bloqueados por CSP mesmo com frame-src a permitir CSP duplicado (nginx + plugin) — browsers aplicam a directiva mais restritiva de cada CSP na intersecção; o nginx não declarava frame-src, caindo no fallback mais restrito segdesc_nginx_csp_covered = '1' nos sites com template nginx — nginx passa a ser a única autoridade de enforcement, plugin só mantém XFO + Report-Only
9 WP_REST_Server conta o dobro dos pedidos reais nos limiares de rate-limit permission_callback é invocado duas vezes por pedido HTTP real (confirmado por transient antes/depois de 1 pedido) Limiares nominais (FAIL_MAX=10, MAX=60) valem, na prática, metade (~5 e ~30) — mantido assim de propósito, mais conservador não prejudica uso legítimo
10 X-Forwarded-For falsificável permitia contornar allowlist/rate-limit/lockout do endpoint REST Portas backend do CWP (2083/2031/2095) acessíveis directamente da internet — confiar incondicionalmente em XFF permitia a um atacante que batesse directo no backend falsificar o IP visto segdesc_client_ip() só confia em XFF quando REMOTE_ADDR é um proxy local confiável (127.0.0.1/::1), e só no último valor da cadeia (o que o próprio nginx anexa)
11 Desactivar o plugin não limpa cron/options/rewrite rules Nenhum módulo regista register_deactivation_hook — só existe register_activation_hook (gera token + activa headers com CSP/XFO default) descomplicar_monitor_cron fica agendado mas sem handler (evento inofensivo, sem efeito); opções segdesc_*/descomplicar_monitor_* e a rewrite rule do slug de login persistem; reactivar retoma o estado exactamente onde ficou — desejável para reinstalação, mas exige limpeza manual (wp option delete) numa desinstalação definitiva

Fonte

  • 71.Seguranca/plugin/seguranca-descomplicar/seguranca-descomplicar.php (v1.4.2, 1494 linhas) — código-fonte completo, relido linha a linha nesta expansão: todas as 26 constantes SEGDESC_*, os 6 hooks do Módulo 3, os 7 hooks do Módulo 4 (incl. cron_schedules custom), os 8 endpoints REST (não 7 — corrigido nesta expansão), e os 5 subcomandos WP-CLI (token, allowlist, headers, login, hardening) confirmados exaustivos (sem WP_CLI::add_command adicional no ficheiro).
  • 71.Seguranca/CONFIG-Plugins-Referencia.md §2 (seguranca-descomplicar) — config ao vivo verificada via wp-cli nos 7 sites, 16-08-2026.
  • 71.Seguranca/tools/distribuir_e_activar_plugin.py e tools/executar_pipeline_completa.py — relidos por completo; confirmado que nenhum dos dois expõe flags de configuração do plugin em si (só --confirm-fix no pipeline, sem argumentos no script de distribuição).
  • 71.Seguranca/index.md — lido na íntegra (linhas 1-627, não só 183-636 como na versão anterior desta skill); confirmou a contagem real de 8 rotas REST (tabela de bundle da 12ª sessão: "8 rotas REST (/wordfence/* × 6, /headers/* × 2)"), a nota de que CSP/XFO ficam hoje maioritariamente a cargo do nginx/Cloudflare (módulo do plugin na prática só serve security.txt nos sites com essa cobertura), e o caso carstuff.pt (mecanismo de /dash alheio a este plugin).