Files
claude-plugins/wordpress/skills/wordfence/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

42 KiB
Raw Blame History

name, description
name description
wordfence Auditoria e gestão completa do Wordfence Security via SQL directo em servidores CWP — cobre as 313 chaves de `wfconfig`, a tabela separada `wfls_settings` do módulo Login Security (2FA/passkeys/CAPTCHA/XML-RPC), armazenamento do WAF em ficheiros (`wp-content/wflogs/config*.php`), WAF (modos, categorias de regra, blacklist premium), bloqueio de IPs/países/padrões (UA/referrer/hostname), Live Traffic, os 5 limitadores de Rate Limiting, Diagnostics, Import/Export de config e Wordfence Central. Detecção de IP real atrás do Cloudflare (howGetIPs), scan settings e interacção com plugins de cache. Usar quando "wordfence", "waf wordpress", "firewall wordpress", "2fa wordpress", "passkeys wordpress", "bloqueio de login", "wfconfig", "wfls_settings", "cloudflare real ip wordfence", "howGetIPs", "scan malware wordpress", "brute force wordpress", "rate limiting wordpress", "live traffic wordfence", "wordfence central", "bloqueio de país wordpress".

/wordfence — Auditoria e Configuração Wordfence via SQL

Gestão do Wordfence Security via wp db query directo no servidor CWP. A configuração NÃO vive toda no mesmo sítio:

  • Grosso da config (WAF nível aplicação, scan, alertas, blocking, rate limiting) → tabela própria wfconfig (chave/valor, colunas name/val), não wp_options — wp option get/patch não funciona para nada disto.
  • 2FA, passkeys, CAPTCHA de login, XML-RPC, "remember device" (módulo Login Security) → tabela separada wfls_settings (colunas name/value — nomes de coluna diferentes de wfconfig). Ver §6.
  • Config do motor WAF usada para arrancar antes do WordPress (Extended Protection/auto-prepend) → ficheiros PHP serializados em wp-content/wflogs/config*.php, fora de qualquer tabela SQL, sincronizados automaticamente a partir de wfconfig. Ver §10.

Fonte: CONFIG-Plugins-Referencia.md §8 (Wordfence, mapeado 16-08-2026, site piloto emanuelalmeida.pt, versão 9.0.0 gratuita) + leitura completa do código-fonte do plugin (466 ficheiros PHP) e verificação SQL/SSH ao vivo feitas nesta sessão — detalhe completo da cobertura no rodapé "Fonte" no final do documento.


Contexto CWP — sempre obrigatório

PATH=/home/USER/public_html
PREFIX=$(wp db prefix --allow-root --path=$PATH)   # ex.: wpne_, wpah_ — nunca assumir wp_

Acesso via SSH: server.descomplicar.pt, porta 9443, user root. Servidor executa comandos como o utilizador do site: sudo -u USER wp ... --path=$PATH.

Tabela de config real (schema confirmado):

DESCRIBE ${PREFIX}wfconfig;
name      varchar(100)  PRI
val       longblob             -- valor (string, número ou JSON serializado)
autoload  enum('no','yes')

Não há wp option patch equivalente — é sempre SELECT/UPDATE directo sobre name/val. 313 linhas de config no site piloto.


Decision tree — qual comando usar

Operação Comando
Ler 1 chave de config wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name='...';"
Ler várias chaves SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN (...);
Alterar 1 chave (produção) UPDATE ${PREFIX}wfconfig SET val='...' WHERE name='...'; — preferir sempre a UI do wp-admin para chaves que disparam hooks (wordfence_changed_ip_source, wordfence_changed_license_key)
Contar utilizadores com 2FA SELECT COUNT(*) FROM ${PREFIX}wfls_2fa_secrets; (tabela moderna) — não usermeta (legado, pode ficar desactualizado)
Ver todas as tabelas do plugin SHOW TABLES LIKE '${PREFIX}wf%';
Ver issues de scan pendentes SELECT COUNT(*) FROM ${PREFIX}wfpendingissues;
Ver IPs bloqueados pelo WAF SELECT COUNT(*) FROM ${PREFIX}wfblockediplog;
Confirmar recomendação de proxy do próprio Wordfence SELECT val FROM ${PREFIX}wfconfig WHERE name='detectProxyRecommendation';
Ler settings do módulo Login Security (2FA/passkeys/CAPTCHA) SELECT name, value FROM ${PREFIX}wfls_settings ORDER BY name; — tabela diferente, colunas name/value (não val) — ver §6
Ver ficheiros de config do WAF (bootstrap fora do WordPress) cat wp-content/wflogs/config.php / config-synced.php — não é SQL, é PHP serializado com __halt_compiler() — ver §10

1. Auditoria de estado (leitura, sem risco)

PATH=/home/USER/public_html
PREFIX=$(wp db prefix --allow-root --path=$PATH)

# Estado geral do WAF/firewall
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('firewallEnabled','waf_status','disableWAFIPBlocking','isPaid');" --allow-root --path=$PATH

# Login security (rate-limit + 2FA)
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('loginSec_maxFailures','loginSec_countFailMins','loginSec_lockoutMins',
'loginSec_lockInvalidUsers','allowLegacy2FA','loginSec_requireAdminTwoFactor');" --allow-root --path=$PATH

# Quantos utilizadores TÊM 2FA activo de facto (tabela moderna do módulo login-security)
wp db query "SELECT COUNT(*) AS total_2fa FROM ${PREFIX}wfls_2fa_secrets;" --allow-root --path=$PATH

# Detecção de IP real (crítico atrás de proxy/CDN)
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('howGetIPs','howGetIPs_trusted_proxies','howGetIPs_trusted_proxy_preset',
'detectProxyRecommendation');" --allow-root --path=$PATH

# Scans activos
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'scansEnabled_%';" --allow-root --path=$PATH

# Alertas/email
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('alertEmails','email_summary_enabled','liveTrafficEnabled','blockFakeBots','autoUpdate','cacheType');" --allow-root --path=$PATH

# Volume de actividade (contexto do site)
wp db query "SELECT COUNT(*) FROM ${PREFIX}wfblockediplog;" --allow-root --path=$PATH   # IPs bloqueados pelo WAF
wp db query "SELECT COUNT(*) FROM ${PREFIX}wfpendingissues;" --allow-root --path=$PATH   # issues de scan em aberto
wp db query "SHOW TABLES LIKE '${PREFIX}wf%';" --allow-root --path=$PATH                 # inventário de tabelas do plugin

Valores reais confirmados (emanuelalmeida.pt, gratuito, 16-08-2026)

Chave Valor Avaliação
firewallEnabled 1 ✅
waf_status enabled ✅ WAF a funcionar
disableWAFIPBlocking 0 ✅ bloqueio de IP activo (325 IPs bloqueados no log)
isPaid vazio Versão gratuita — sem WAF em tempo real (updates de regras com atraso), sem scan de reputação de IP premium
alertEmails it@descomplicar.pt ✅
email_summary_enabled 1 ✅
liveTrafficEnabled 1 ✅
blockFakeBots 1 ✅ bloqueia falsos Googlebot/etc.
autoUpdate 1 ✅
cacheType disabled ✅ deliberado — ver §4
scansEnabled_core/plugins/themes/malware/diskSpace/suspiciousAdminUsers/wafStatus/... todos 1 ✅ cobertura de scan quase completa
scansEnabled_highSense 0 Sensibilidade alta de scan desligada — reduz falsos positivos, aceitável
scansEnabled_scanImages 0 Não faz scan binário a imagens — aceitável, baixo risco típico
scansEnabled_checkHowGetIPs 1 O próprio Wordfence já tem um scan check dedicado a esta má-configuração (ver §3)
loginSec_maxFailures / _countFailMins / _lockoutMins 5 / 60 / 240 Bloqueia ao fim de 5 falhas em 60 min, lockout de 4h
loginSec_lockInvalidUsers 0 Não bloqueia por tentar logins com username inexistente (só falhas de password em conta real)
allowLegacy2FA 0 ✅ não permite métodos 2FA fracos/antigos

2. GAP real — 2FA não forçado, zero utilizadores protegidos

wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name='loginSec_requireAdminTwoFactor';" --allow-root --path=$PATH
# → 0 = 2FA NÃO é obrigatório para administradores

wp db query "SELECT COUNT(*) FROM ${PREFIX}wfls_2fa_secrets;" --allow-root --path=$PATH
# → 0 = zero utilizadores com 2FA activo, incluindo o admin

Achado real (emanuelalmeida.pt): loginSec_requireAdminTwoFactor=0 e zero linhas em wfls_2fa_secrets — o login continua protegido só por password + rate-limit (5 tentativas/60min/lockout 4h), sem segundo factor, mesmo sendo o site pessoal do fundador da empresa.

Forçar 2FA para administradores

Auditoria/leitura sempre segura. Escrita é mutação em produção — confirmar autorização antes de aplicar:

# Ler estado actual antes de alterar
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='loginSec_requireAdminTwoFactor';" --allow-root --path=$PATH

# Forçar 2FA para o role Administrator
wp db query "UPDATE ${PREFIX}wfconfig SET val='1' WHERE name='loginSec_requireAdminTwoFactor';" --allow-root --path=$PATH

Cuidado operacional: activar isto sem um administrador já ter um autenticador configurado bloqueia o próprio acesso ao wp-admin no login seguinte (o Wordfence força a configuração do 2FA no momento do login, não tranca por fora — mas confirmar sempre com o utilizador antes, e nunca aplicar em sessão headless sem acesso de recuperação garantido, ex. acesso SSH/SQL directo para reverter o valor para 0 se algo correr mal).


3. GAP real — howGetIPs vazio atrás do Cloudflare

Este é o achado mais concreto desta auditoria. O próprio Wordfence já tinha calculado a recomendação correcta e guardado numa chave separada, mas nunca a aplicou à chave activa:

wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('howGetIPs','detectProxyRecommendation');" --allow-root --path=$PATH
name                        val
detectProxyRecommendation   HTTP_CF_CONNECTING_IP
howGetIPs                   (vazio)

detectProxyRecommendation é preenchido automaticamente pelo Wordfence quando detecta que o tráfego chega via um proxy conhecido (Cloudflare, neste caso) — mas fica só como sugestão; howGetIPs (a chave que o WAF realmente usa para decidir qual IP é o "real") continua vazia. Com howGetIPs vazio, o Wordfence cai no fallback de ordem de headers (lib/wfUtils.php: HTTP_CF_CONNECTING_IP → HTTP_X_REAL_IP → REMOTE_ADDR → HTTP_X_FORWARDED_FOR), o que na maioria dos casos até funciona, mas não é o comportamento explícito/auditável e não activa o scan check dedicado (scansEnabled_checkHowGetIPs=1 já está ligado — é precisamente este scan que devia estar a assinalar isto como issue, mas wfpendingissues está vazia, i.e. o scan periódico pode não ter corrido recentemente ou o alerta expirou).

Valores válidos de howGetIPs (confirmado no código, wfConfig.php/wfUtils.php)

Cabeçalhos HTTP reconhecidos pelo Wordfence para determinar o IP real:

Valor Quando usar
HTTP_CF_CONNECTING_IP Site atrás de Cloudflare — este é o caso do bundle Descomplicar® (todos os sites usam App for Cloudflare®)
HTTP_X_REAL_IP Proxy tipo nginx reverso simples (sem CDN)
HTTP_X_FORWARDED_FOR Load balancer/proxy genérico
(vazio) REMOTE_ADDR directo — só correcto se não houver proxy/CDN nenhum à frente

Existe ainda howGetIPs_trusted_proxy_preset, que aponta para uma entrada em ipResolutionList (JSON de ~8KB, sincronizado da API Wordfence, contém blocos de IP de CDNs conhecidas — ex. cloudflare, cloudfront — usados para validar que o pedido realmente vem de um proxy confiável antes de aceitar o header). Consultar presets disponíveis:

wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='ipResolutionList';" --allow-root --path=$PATH \
  | python3 -c "import json,sys; print(list(json.load(sys.stdin).keys()))"

Corrigir (copiar a própria recomendação do Wordfence)

# Ler antes de escrever
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='detectProxyRecommendation';" --allow-root --path=$PATH

# Aplicar — usa o valor que o próprio Wordfence já recomendou
wp db query "UPDATE ${PREFIX}wfconfig SET val='HTTP_CF_CONNECTING_IP' WHERE name='howGetIPs';" --allow-root --path=$PATH

# Confirmar
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name='howGetIPs';" --allow-root --path=$PATH

Sem isto configurado, o rate-limiting de login, os bloqueios de IP do WAF e o Live Traffic podem estar a agir sobre o IP do proxy Cloudflare em vez do IP real do atacante — mais grave em sites com muito tráfego atrás do mesmo edge, onde o "IP" observado pode ser partilhado por múltiplos visitantes.


4. Interacção com cache de página — cacheType=disabled é deliberado

wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='cacheType';" --allow-root --path=$PATH
# → disabled

O Wordfence tem o seu próprio sistema de cache de página interno (Enable Falcon Engine), mas em sites do bundle Descomplicar® a cache de página já é feita por outro plugin dedicado (WP Fastest Cache ou equivalente). cacheType=disabled está correcto — dois sistemas de cache HTML activos ao mesmo tempo competem, servem versões desalinhadas e dificultam o purge coordenado. Nunca activar o cache Falcon do Wordfence num site que já tem WP Fastest Cache/WP Meteor a fazer cache de página — confirmar sempre qual plugin de cache está activo antes de alterar cacheType num site novo.


5. Scan settings — cobertura completa

wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'scansEnabled_%';" --allow-root --path=$PATH
Categoria Chave Estado (piloto)
Core WordPress scansEnabled_core, _coreUnknown, _oldVersions 1
Plugins/temas scansEnabled_plugins, _themes 1
Malware/ficheiros suspeitos scansEnabled_malware, _fileContents, _fileContentsGSB, _suspectedFiles 1
Espaço em disco scansEnabled_diskSpace 1
Utilizadores admin suspeitos scansEnabled_suspiciousAdminUsers 1
Opções de BD suspeitas scansEnabled_options, _suspiciousOptions 1
Passwords fracas scansEnabled_passwds 1
Comentários/posts (spam/malware injectado) scansEnabled_comments, _posts 1
GeoIP / Google Safe Browsing scansEnabled_geoipSupport, _checkGSB 1
Config legível externamente scansEnabled_checkReadableConfig 1
Detecção de IP mal configurada scansEnabled_checkHowGetIPs 1 — ver §3
Estado do WAF scansEnabled_wafStatus 1
WPScan (directory listing, full path disclosure) scansEnabled_wpscan_directoryListingEnabled, _wpscan_fullPathDisclosure 1
Sensibilidade alta scansEnabled_highSense 0 (desligado, reduz falsos positivos)
Scan binário de imagens scansEnabled_scanImages 0 (desligado)

Avaliação: cobertura de scan quase total (23 de 25 checks activos), única omissão relevante é a sensibilidade alta — aceitável para reduzir ruído em sites com actividade legítima elevada.


6. Two-Factor / Passkeys / CAPTCHA — tabela SEPARADA wfls_settings

Achado arquitectural importante desta expansão: o módulo "Login Security" (2FA, passkeys, CAPTCHA de login, XML-RPC, "remember device", integração WooCommerce, NTP) não guarda a sua configuração em wfconfig — vive numa tabela própria, wfls_settings, gerida pela classe WordfenceLS\Controller_Settings (código em modules/login-security/classes/controller/settings.php). Schema:

DESCRIBE ${PREFIX}wfls_settings;
name      varchar(191)  PRI
value     longtext            -- nota: coluna chama-se "value", não "val"
autoload  enum('no','yes')
wp db query "SELECT name, value FROM ${PREFIX}wfls_settings ORDER BY name;" --allow-root --path=$PATH

As chaves loginSec_requireAdminTwoFactor e loginSec_enableSeparateTwoFactor em wfconfig (§2) são a versão legada — um switch global único. A versão actual do plugin é baseada em papéis (roles): migrate_admin_2fa_requirements_to_roles() migra o switch legado para chaves por role (required-2fa-role.<role>) dentro de wfls_settings e depois apaga a chave legada do wfconfig. Um site "antigo" pode ainda ter o valor legado; um site já migrado tem-no ausente/zero e a fonte de verdade passa a ser wfls_settings + capabilities.

Chaves confirmadas em wfls_settings (emanuelalmeida.pt)

Chave Valor confirmado O que controla
xmlrpc-enabled 1 Exigir 2FA também em pedidos XML-RPC autenticados
allow-xml-rpc 1 Permitir XML-RPC de todo (independente do 2FA)
ip-source vazio (automatic) Fonte do IP real para o módulo login-security — independente de howGetIPs do wfconfig (§3); valores: automatic, REMOTE_ADDR, X-Forwarded-For, X-Real-IP
ip-trusted-proxies vazio Lista de proxies confiáveis (newline-separated) para o cálculo de IP deste módulo
2fa-user-grace-period 10 Dias de período de graça depois de 2FA se tornar obrigatório por role, antes de bloquear o login
require-2fa.administrator vazio Legado (ver acima); use required-2fa-role.administrator
passkey-sign-count-mode reject-lower Política anti-clonagem de passkeys: allow / reject-lower / reject-lower-and-zero
passkey-relying-party-override vazio Override do hostname RP para passkeys (multi-domínio)
remember-device vazio (false) "Lembrar este dispositivo" para saltar 2FA X dias
remember-device-duration 2592000 (30 dias) Duração do "remember device" em segundos
always-show-login-security-menu 1 Mostrar sempre o menu Login Security mesmo sem nada configurado
enable-auth-captcha vazio CAPTCHA (reCAPTCHA) no formulário de login
recaptcha-site-key / recaptcha-secret vazio Credenciais reCAPTCHA — só relevantes se enable-auth-captcha=1
recaptcha-threshold 0.5 Limiar de pontuação do reCAPTCHA v3 (0–1)
enable-woocommerce-integration / -account-integration vazio Aplicar 2FA/passkeys também na área de conta WooCommerce
use-ntp 1 Sincronizar relógio via NTP para códigos TOTP (evita drift do relógio do servidor)
whitelisted vazio IPs isentos de exigência de 2FA (não confundir com whitelisted do wfconfig, que é do WAF)
schema-version 3 Versão do schema interno do módulo — não editar manualmente

Modelo de permissões por role (capabilities, não settings)

"2FA obrigatório" e "2FA opcional" por role não são uma chave de configuração simples — são geridos como capabilities do WordPress, sincronizadas por WordfenceLS\Controller_Permissions:

Capability Significado
wf2fa_activate_2fa_self O utilizador pode activar/desactivar 2FA na própria conta
wf2fa_activate_2fa_others Pode activar/desactivar 2FA de outros utilizadores
wf2fa_manage_settings Pode editar as definições do módulo Login Security
wfls_manage_passkey_self / _others Equivalente para passkeys
wfls_show_login_security Capability interna que mostra/esconde o menu (sincronizada automaticamente)
wp eval 'var_export(get_role("administrator")->capabilities);' --allow-root --path=$PATH | grep -i 'wf2fa\|wfls'
# Confirmado em emanuelalmeida.pt: administrator TEM wf2fa_activate_2fa_self (pode activar),
# mas isso é OPCIONAL — só se torna obrigatório se required-2fa-role.administrator tiver timestamp > -1

O estado "obrigatório desde quando" fica em required-2fa-role.<role> (timestamp Unix) ou -1 (não obrigatório) — chave dinâmica dentro de wfls_settings, não aparece na lista de defaults porque só existe depois de alguém a definir pela UI.

Tabelas de dados do módulo (não config, mas relevantes para auditoria)

Tabela Conteúdo
wfls_2fa_secrets Segredos TOTP activos por utilizador (contagem = utilizadores com 2FA activo)
wfls_passkeys Passkeys (WebAuthn) registadas por utilizador
wfls_role_counts Cache de contagens de utilizadores por role, usada pela UI

7. Live Traffic — configuração completa

Todas as chaves vivem em wfconfig (ao contrário do módulo 2FA). Tabela de dados: wflivetraffichuman (visitas humanas) + wfhits, wfhoover, wfcrawlers, wflocs, wflogins (dados de suporte ao Live Traffic e ao dashboard de tráfego).

Chave Valor (piloto) O que controla
liveTrafficEnabled 1 Liga/desliga o registo de Live Traffic
liveTraf_ignorePublishers 1 Não regista visitas de utilizadores autenticados com capacidade de publicar (reduz ruído do próprio staff)
liveTraf_ignoreUsers vazio Lista de usernames (separados por vírgula) a ignorar
liveTraf_ignoreIPs vazio Lista de IPs (separados por vírgula) a ignorar
liveTraf_ignoreUA vazio User-agent a ignorar (substring)
liveTraf_maxRows 2000 Quantidade máxima de registos guardados
liveTraf_maxAge 30 Dias máximos de retenção dos registos
liveTraf_displayExpandedRecords 0 Preferência de UI (registos expandidos por omissão) — não afecta captura

Overrides que desligam Live Traffic independentemente da config: wfConfig::liveTrafficEnabled() força false se a constante WORDFENCE_DISABLE_LIVE_TRAFFIC estiver definida, ou se WF_IS_WP_ENGINE for verdadeiro (hosts WP Engine desactivam sempre este módulo, independentemente do valor em wfconfig) — útil para explicar a um cliente porque "liguei e não aparece nada" nesse tipo de hosting.

wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'liveTraf%' OR name='liveTrafficEnabled';" --allow-root --path=$PATH

8. Blocking completo — País, IP manual, Padrões (UA/Referrer/Hostname)

Todos os bloqueios (manuais e automáticos) vivem numa única tabela, wfBlocks7 (não em wfconfig), com um campo type que distingue a natureza do bloqueio (models/block/wfBlock.php):

Constante Valor Origem
TYPE_IP_MANUAL 1 Bloqueio de IP manual pelo admin
TYPE_WFSN_TEMPORARY 2 Resposta automática da Wordfence Security Network (rede partilhada entre sites)
TYPE_COUNTRY 3 Bloqueio por país
TYPE_PATTERN 4 Bloqueio avançado (IP range + hostname + user-agent + referrer)
TYPE_RATE_BLOCK 5 Gerado pelo rate limiting (§9), acção block
TYPE_RATE_THROTTLE 6 Gerado pelo rate limiting (§9), acção throttle
TYPE_LOCKOUT 7 Lockout de login (falhas de password/2FA)
TYPE_IP_AUTOMATIC_TEMPORARY / _PERMANENT 8 / 9 Bloqueios automáticos do WAF, temporário vs promovido a permanente por acção do admin
# Ver todos os bloqueios activos por tipo
wp db query "SELECT type, COUNT(*) FROM ${PREFIX}wfblocks7 WHERE expiration=0 OR expiration>UNIX_TIMESTAMP() GROUP BY type;" --allow-root --path=$PATH

Bloqueio por país

Campos guardados em parameters (JSON): blockLogin (bloquear só a página de login), blockSite (bloquear o resto do site), countries (array de códigos ISO). Comportamento configurado globalmente em wfconfig:

Chave Valor (piloto) O que controla
cbl_action block O que fazer a um país bloqueado: block (503) ou redirect (redireccionar)
cbl_redirURL vazio URL de destino se cbl_action=redirect
cbl_loggedInBlocked 0 Bloquear país mesmo que o visitante já tenha sessão iniciada
cbl_bypassRedirURL / cbl_bypassRedirDest vazio Um visitante de país bloqueado que aceda a este URL relativo é reencaminhado e recebe um cookie de bypass permanente
cbl_bypassViewURL vazio Um visitante permitido que veja este URL relativo recebe o cookie de bypass antecipadamente (para o caso de mudar de país/VPN depois)
cbl_cookieVal gerado automaticamente Valor único do cookie de bypass — regenerar invalida bypasses concedidos

Nenhum destes está configurado no piloto (site sem bloqueio de país activo) — confirmar sempre hasCountryBlock()/countryBlocks() antes de assumir que não há impacto em visitantes internacionais.

Bloqueio manual de IP / IP range

# Confirmar que um IP não está na whitelist antes de o bloquear (a UI faz isto automaticamente)
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='whitelisted';" --allow-root --path=$PATH

wfBlock::isWhitelisted() verifica dois níveis: a lista manual em whitelisted (wfconfig, uma entrada por linha, IP/CIDR) e uma lista de serviços conhecidos (whitelistedServices, JSON — vazio {} no piloto; quando preenchido guarda ranges de IP de serviços como monitorização externa que o Wordfence nunca bloqueia mesmo com regras activas).

Bloqueio avançado (Custom Pattern) — hostname / user-agent / referrer

Bloqueio combinável por 4 critérios (pelo menos 1 obrigatório), type=custom-pattern na validação (wfBlock::validate()):

Campo Formato Exemplo de uso
ipRange IP único, CIDR, ou range 1.2.3.4-1.2.3.9 (IPv4 e IPv6 não podem misturar-se no mesmo bloqueio) Bloquear uma subnet inteira
hostname wildcard * e . (regex ^[a-z0-9\.\*\-]+$) Bloquear por reverse-DNS de um datacenter conhecido
userAgent substring livre Bloquear scrapers com UA identificável
referrer substring livre Bloquear tráfego de referrer spam
wp db query "SELECT id, blockedTime, reason, expiration FROM ${PREFIX}wfblocks7 WHERE type=4 AND (expiration=0 OR expiration>UNIX_TIMESTAMP());" --allow-root --path=$PATH

Como o Wordfence decide não bloquear crawlers do Google

wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='neverBlockBG';" --allow-root --path=$PATH
# Confirmado no piloto: neverBlockVerified
Valor de neverBlockBG Comportamento
neverBlockVerified (default, activo no piloto) Verifica UA e faz PTR/reverse-DNS lookup para confirmar que é mesmo um IP do Google antes de isentar de bloqueio
neverBlockUA Confia só no User-Agent, sem verificação de IP — menos seguro (fácil de fingir), mas mais barato
treatAsOtherCrawlers Não dá tratamento especial a "Google" — pode ser bloqueado como qualquer outro crawler

9. Rate Limiting completo — 5 tipos, throttle vs block

models/block/wfRateLimit.php define 5 limitadores independentes, cada um com um par de chaves <nome> (limiar) + <nome>_action (throttle ou block). DISABLED como valor do limiar desactiva esse limitador especificamente.

Limitador Chave limiar Chave acção Valor confirmado (piloto) Aplica-se a
Global maxGlobalRequests maxGlobalRequests_action 240 / throttle Qualquer pedido, humano ou crawler — se activo, sobrepõe-se aos outros 4
Page views de crawlers maxRequestsCrawlers maxRequestsCrawlers_action DISABLED / throttle Só User-Agents identificados como crawler (wfCrawl::isCrawler)
404s de crawlers max404Crawlers max404Crawlers_action 60 / throttle Idem, mas só páginas 404
Page views de humanos maxRequestsHumans maxRequestsHumans_action DISABLED / throttle Visitantes não identificados como crawler
404s de humanos max404Humans max404Humans_action 60 / throttle Idem, mas só páginas 404
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'max%Requests%' OR name LIKE 'max404%' OR name='blockedTime';" --allow-root --path=$PATH

No piloto, o rate limit global (maxGlobalRequests=240) está ACTIVO — qualquer IP (humano ou bot) que exceda 240 pedidos na janela de 1 minuto (tabela wfTrafficRates, campo eMin) é afectado. Os limitadores dedicados a crawlers/humanos de page views estão desligados; só os de 404 (60 num minuto) estão activos, o que é uma configuração razoável (protege contra scans de 404 sem penalizar navegação normal).

throttle vs block não são só rótulos — têm durações e severidade diferentes:

  • throttle → wfBlock::createRateThrottle(), duração fixa de 60 segundos (wfRateLimit::rateLimitThrottleDuration()) — resposta 503 temporária, o visitante volta a conseguir aceder ao fim de 1 minuto.
  • block → wfBlock::createRateBlock(), duração = blockedTime (wfconfig, 300s / 5 min no piloto, configurável) — bloqueio mais longo, e pode ser promovido a permanente manualmente pelo admin na UI de Blocking (TYPE_RATE_BLOCK → TYPE_IP_AUTOMATIC_PERMANENT).

Excepção comum a 404s permitidos: allowed404s (wfconfig, uma entrada por linha, suporta wildcard *) — URLs que nunca contam para os limitadores de 404, mesmo que devolvam 404 de facto. Default do piloto: /favicon.ico, /apple-touch-icon*.png, /*@2x.png, /browserconfig.xml (ícones/manifestos pedidos por browsers que geram 404 legítimos e não devem disparar rate limiting).


10. WAF — arquitectura de armazenamento, modos e categorias de regra

Achado arquitectural crítico: para o WAF funcionar antes do WordPress carregar (Extended Protection / auto-prepend), o Wordfence não pode depender só da base de dados — mantém uma cópia da config relevante em ficheiros PHP no filesystem, fora de wp_content/plugins:

ls -la wp-content/wflogs/
# config.php          — config nuclear do WAF (wafStatus, authKey, versão do formato) — 6 chaves só
# config-synced.php   — MIRROR de chaves relevantes do wfconfig + bloqueios activos, sincronizado
#                        sempre que algo muda (país/IP/pattern blocks, howGetIPs, apiKey, etc.)
# config-livewaf.php  — config activa/em uso pelo processo WAF autoprepended
# config-transient.php— cache de dados de ataque (grande, ~1.7 MB no piloto)
# rules.php           — ficheiro PHP executável (não serializado) com as regras do WAF, incluído
#                        directamente no bootstrap; NÃO editar/apagar manualmente
# attack-data.php, ips.php — logs internos de ataques/IPs vistos pelo WAF

Estes ficheiros começam com <?php exit('Access denied'); __halt_compiler(); ?> seguido de dados serializados em PHP — só são legíveis fazendo substr() a partir do offset de __halt_compiler() e unserialize(). Nunca editar estes ficheiros manualmente; qualquer alteração de config deve passar pela UI ou por wfconfig, que depois é sincronizada automaticamente para estes ficheiros (via wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings(), chamado sempre que wfBlock::create*() corre).

O motor de armazenamento é escolhido por WFWAF_STORAGE_ENGINE (waf/bootstrap.php): file por omissão (confirmado no piloto — ficheiros em wflogs/ existem e têm conteúdo recente), ou mysqli forçado automaticamente em hosts WP Engine/Flywheel (ou por constante explícita), caso em que a config do WAF passa a viver numa ligação MySQL directa (bypass ao $wpdb) em vez de ficheiros.

Modos e níveis do WAF (models/firewall/wfFirewall.php)

Constante Valores possíveis Significado
firewallMode() disabled / learning-mode / enabled Estado geral; enabled no piloto (§1, waf_status)
protectionMode() basic / extended extended requer WFWAF_AUTO_PREPEND activo (ficheiro .user.ini/.htaccess a fazer auto_prepend_file para wordfence-waf.php antes do WordPress arrancar) — sem isto, o WAF só corre depois do muplugins_loaded, mais tarde no ciclo, "Basic Protection"
ruleMode() community / premium Depende de isPaid — piloto usa feed community (regras com atraso de propagação face a sites Premium)
blacklistMode() disabled / enabled "Real-Time IP Blocklist" — só disponível com isPaid, mesmo que disableWAFBlacklistBlocking=0
learningModeStatus() false / true / timestamp Se em Learning Mode, indica se há mudança automática agendada para Enabled (learningModeGracePeriodEnabled + learningModeGracePeriod)

Categorias de regra (fail-score) — confirmado no rules.php do piloto

O motor de regras usa 3 categorias de ataque com pontuação de risco própria ($this->failScores[...] em wflogs/rules.php):

ssh server "grep -o \"failScores\\['[a-z]*'\\]\" .../wp-content/wflogs/rules.php | sort -u"
# failScores['rce']   — Remote Code Execution
# failScores['sqli']  — SQL Injection
# failScores['xss']   — Cross-Site Scripting

Regras individuais podem ser desactivadas via disabledRules — essa chave vive dentro do storage do WAF (wp-content/wflogs/config.php no motor file), não em wfconfig; o piloto não tem nenhuma regra desactivada (chave ausente = todas activas). A "pontuação de segurança" mostrada no Dashboard (wfFirewall::overallStatus()) combina: % de regras activas (35%), blacklist premium activa (35%), Extended Protection (20%), Rate Limiting/Advanced Blocking ligado (10%) — pesado a favor do WAF (80% do total), com brute-force protection a contar só 20%.


11. Diagnostics (Tools → Diagnostics)

Gerado por lib/wfDiagnostic.php, sem persistência em BD — é sempre calculado on-demand quando a página carrega. Secções e o que cada uma verifica:

Secção O que reporta
Wordfence Status Versão instalada, versão da base GeoIP, jobs de cron em atraso (>30min)
Filesystem Se o webserver consegue ler/escrever wp-content/plugins/wordfence e wp-content/wflogs
Wordfence Config Teste de escrita/leitura real contra wfconfig (básico e serializado) — falha aqui = sem permissões de escrita na tabela
Wordfence Firewall Auto-prepend activo, motor de storage configurado vs activo, path dos logs, permissões de ficheiros, conteúdo de .htaccess/.user.ini relacionado com o WAF
MySQL Versão do MySQL + privilégios reais do utilizador da BD: SELECT/INSERT/UPDATE/DELETE/CREATE/ALTER/DROP/TRUNCATE (via SHOW GRANTS FOR current_user())
PHP Environment Versão PHP vs mínimo suportado, dono do processo PHP, suporte OpenSSL/cURL (versões, protocolos), display_errors
Connectivity Ligação de saída a servidores Wordfence (https), ligação de volta ao próprio site (IPv4 e IPv6 — útil para detectar Cloudflare a bloquear o próprio site)
Time Hora da rede Wordfence, hora do servidor, offset NTP, fonte de tempo usada para TOTP (2FA), fuso horário do WordPress

Casos de uso práticos:

  • userCanAlter/userCanDrop a false → migrações/upgrades do plugin podem falhar silenciosamente; verificar antes de reportar "scan não actualiza issues".
  • connectToSelf/connectToSelfIpv6 a falhar com sinais de Cloudflare (cf-mitigated: challenge, Cloudflare Ray ID no body) → o próprio Cloudflare está a desafiar o loopback request do Wordfence; relevante para sites do bundle Descomplicar® (todos atrás de Cloudflare via App for Cloudflare®) em diagnóstico de scans que nunca terminam.
  • A secção "WordPress" (via wfDiagnostic::getWordpressValues(), não listada acima por ser genérica ao core) expõe constantes como WP_DEBUG, DISALLOW_FILE_EDIT, WP_CACHE, FORCE_SSL_ADMIN — útil para confirmar hardening sem abrir wp-config.php directamente.

12. Tools → Import/Export de configuração

Não é um ficheiro local (ao contrário de outros plugins do bundle) — wfImportExportController (lib/wfImportExportController.php) faz export()/import() através da API do wordfence.com: o export gera um token remoto (export_options), que depois se cola manualmente no site de destino (import_options + token). Isto exige que ambos os sites tenham apiKey válida e liguem para fora ao wordfence.com — não funciona offline nem é um download/upload de ficheiro local.

O que é exportado: todas as wfConfig::getExportableOptionsKeys() (essencialmente os grupos checkboxes + otherParams do $defaultConfig, não o grupo defaultsOnly), o agendamento de scans (scanSched, serializado), e todos os registos da tabela de bloqueios (wfBlock::exportBlocks() — inclui bloqueios de país, IP e pattern, mas não lockouts nem bloqueios automáticos temporários).

Não é adequado para "clonar config para outro site" via SQL directo (copiar a tabela wfconfig entre sites não é suportado oficialmente e arrasta chaves específicas da instalação como encKey/longEncKey — preferir sempre o fluxo de token oficial acima ou reconfigurar via UI).


13. Wordfence Central — gestão remota multi-site

wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'wordfenceCentral%';" --allow-root --path=$PATH

Achado confirmado no piloto: este site ESTÁ ligado ao Wordfence Central (wordfenceCentralConnected=1), associado a it@descomplicar.pt desde 27/02/2025 (wordfenceCentralConnectTime=1740679066), com um wordfenceCentralSiteID próprio (UUID). Central é a consola de gestão remota multi-site do Wordfence (central.wordfence.com) — permite monitorizar/alertar e (em planos pagos) configurar Wordfence em vários sites a partir de um único painel, autenticado via JWT (wordfenceCentralAccessToken, wordfenceCentralUserSiteAccessToken) trocado através de um par de chaves pública/privada gerado no site (wordfenceCentralPK, wordfenceCentralSecretKey).

Chave relevante O que é
wordfenceCentralConnected 1/0 — liga/desliga a integração
wordfenceCentralConnectEmail / _ConnectTime Quem ligou e quando
wordfenceCentralPluginAlertingDisabled Se 1, silencia alertas duplicados no próprio wp-admin porque já chegam via Central
wordfenceCentralConfigurationIssue Flag interna — 1 indica falha na renovação do token JWT, útil para diagnosticar "Central parou de reportar"
wordfenceCentralSiteData Cache JSON da última resposta da API Central sobre este site

Widget do dashboard nativo do WordPress: email_summary_dashboard_widget_enabled (wfconfig, 1 no piloto) controla o widget "Activity Report" no /wp-admin/index.php (WordPress Dashboard nativo) — não confundir com o próprio Dashboard do Wordfence (/wp-admin/admin.php?page=Wordfence, componente Vue sem chave de config individual para mostrar/esconder secções).


Gotchas / erros comuns

Sintoma Causa Solução
wp option get wordfence_... devolve vazio/erro Config do Wordfence não vive em wp_options Usar sempre wp db query contra ${PREFIX}wfconfig (colunas name/val)
SELECT value FROM wfconfig falha (Unknown column 'value') A coluna chama-se val, não value SELECT name, val FROM ${PREFIX}wfconfig
Contagem de 2FA por usermeta.meta_key='wordfence_2fa_secret' parece desactualizada Essa é a chave legada; o módulo login-security moderno guarda em tabela própria Preferir SELECT COUNT(*) FROM ${PREFIX}wfls_2fa_secrets
howGetIPs vazio mas o site parece "funcionar" na mesma O Wordfence tem fallback de ordem de headers (HTTP_CF_CONNECTING_IP → HTTP_X_REAL_IP → REMOTE_ADDR → HTTP_X_FORWARDED_FOR) — mascara o problema em vez de o resolver Configurar explicitamente howGetIPs, não confiar no fallback silencioso
wfpendingissues vazia mesmo com scansEnabled_checkHowGetIPs=1 e howGetIPs mal configurado O scan periódico pode não ter corrido recentemente, ou o alerta já expirou/foi dispensado Não usar wfpendingissues vazia como prova de "nada errado" — cruzar sempre com leitura directa da config
Activar loginSec_requireAdminTwoFactor sem aviso prévio Bloqueia o fluxo normal de login até o admin configurar um autenticador Confirmar com o utilizador antes; garantir acesso alternativo (SSH/SQL) para reverter
Prefixo de tabela assumido como wp_ Sites CWP podem ter prefixo custom (confirmado wpne_ no piloto) wp db prefix --allow-root --path=$PATH sempre primeiro
Editar 2FA/passkeys/CAPTCHA via UPDATE ${PREFIX}wfconfig sem efeito Essas definições vivem em wfls_settings (colunas name/value, não name/val) — ver §6 UPDATE ${PREFIX}wfls_settings SET value='...' WHERE name='...';
Editar regras/bloqueios do WAF directamente em wp-content/wflogs/*.php São ficheiros PHP serializados com __halt_compiler(), regenerados a partir da BD — editar à mão desalinha-os da BD e pode partir o WAF Alterar sempre via wfconfig/UI; a sincronização para ficheiro é automática (§10)
Achar que loginSec_requireAdminTwoFactor=0 prova que 2FA nunca é obrigatório Essa chave é legada; sites migrados usam required-2fa-role.<role> dentro de wfls_settings Verificar sempre required-2fa-role.* e as capabilities wf2fa_* do role antes de concluir (§6)
Assumir que maxRequestsHumans/maxRequestsCrawlers="DISABLED" significa "sem rate limiting nenhum" Cada um dos 5 limitadores (§9) é independente — maxGlobalRequests pode estar activo e a aplicar-se a toda a gente mesmo com os outros 4 desligados Verificar sempre os 5 pares <limite>/<limite>_action, não só um

Fonte: CONFIG-Plugins-Referencia.md §8 (Wordfence) + verificação SQL/SSH ao vivo em emanuelalmeida.pt (16-08-2026, WP 7.0.4/PHP 8.2.31, Wordfence 9.0.0 gratuito) — schema de wfconfig (313 chaves confirmadas via SELECT DISTINCT name), contagem real de 2FA/passkeys, detectProxyRecommendation, valores de scan e login security. Expandido nesta sessão com leitura completa do código-fonte do plugin (466 ficheiros PHP, ~147k linhas) — lib/wfConfig.php (schema completo de defaults, 3 grupos: checkboxes/otherParams/defaultsOnly), lib/wfJavascriptBridge.php (labels humanos de todas as opções da UI), models/block/wfBlock.php e wfRateLimit.php (tipos de bloqueio e os 5 limitadores de rate limiting), models/firewall/wfFirewall.php (modos e scoring do WAF), lib/wfDiagnostic.php (secções de diagnóstico), lib/wfImportExportController.php (mecanismo real de import/export via token wordfence.com), lib/wfCentralAPI.php (integração Central) e o módulo modules/login-security/ completo — incluindo a descoberta de que 2FA/passkeys/CAPTCHA vivem numa tabela SQL separada (wfls_settings, colunas name/value) e que o WAF mantém uma cópia de config em ficheiros PHP serializados (wp-content/wflogs/config*.php), fora de wfconfig. Confirmado ao vivo: wfls_settings completa, capabilities wf2fa_*/wfls_* do role administrator, ficheiros WAF (config.php, config-synced.php, rules.php), categorias de regra (sqli/xss/rce) e ligação activa a Wordfence Central desde 27/02/2025.