Files
claude-plugins/wordpress/skills/app-for-cloudflare/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

44 KiB

name, description
name description
app-for-cloudflare Gestão da zona Cloudflare a partir do wp-admin via App for Cloudflare® (`app-for-cf`, Digital Point) — SSL/TLS, Speed, Caching, Network, Scrape Shield, Bot Management, Super Bot Fight Mode, WAF real via Managed Ruleset, HSTS, Certificate Transparency, Leaked Credential Checks, Turnstile CAPTCHA, R2 media storage, Zero Trust Access, Page/Cache Rules, DMARC, Web Analytics, ferramentas de diagnóstico (request trace/IP/domain/WHOIS), purge de cache, guest page caching a nível de edge. Usar quando "app for cloudflare", "cloudflare wordpress", "zona cloudflare", "waf cloudflare", "bot management", "super bot fight mode", "hsts cloudflare", "rocket loader", "certificate transparency", "leaked credential checks", "bots de ia cloudflare", "ai_bots_protection", "is_robots_txt_managed", "managed ruleset", "purge cache cloudflare", "turnstile", "cloudflare r2", "cloudflare access", "page rules cloudflare", "cache rules cloudflare", "dmarc cloudflare", "cfPageCachingSeconds", "cfTurnstile", "app_for_cf", ou qualquer alteração a definições de zona Cloudflare/config do plugin num site do bundle Descomplicar.

/app-for-cloudflare — Gestão de zona Cloudflare via App for Cloudflare®

Plugin app-for-cf (Digital Point), versão 1.10.0 (APP_FOR_CLOUDFLARE_VERSION), instalado nos sites do bundle Descomplicar®. Não é um simples proxy visual — a maior parte das definições de "boas práticas" que o plugin lista não vivem em wp_options: são lidas e escritas directamente na Cloudflare API v4, usando um token scoped guardado numa única opção serializada. Confundir as duas camadas é o erro mais comum ao operar este plugin.

Fonte: CONFIG-Plugins-Referencia.md §1 (App for Cloudflare®) e BUNDLE-Excelencia-WP.md §2.2 e §2.4 (GEO/bots de IA) — mapeamento inicial feito em emanuelalmeida.pt (16-08-2026), zona 574b243ebabd8ea2b8d89e68921f52cc, plano Free Website. Expandido na mesma sessão com leitura completa do código-fonte (11.309 linhas em 61 ficheiros PHP, wp-content/plugins/app-for-cf/): Repository/Cloudflare.php (2178 linhas — getSettingsToManage(), setEasyMode(), updateSettings()), Setup.php (defaults de wp_options), Admin/Template/Settings.php (766 linhas — UI completa das 6 tabs), Admin/Base/Admin.php (estrutura das 14 páginas de menu), Base/Pub.php (657 linhas — guest page caching, purge, preload), Helper/Api.php (gate Free/Pro), Cron/Jobs.php e Cli/PurgeCache.php (automação de purge), e todos os 14 templates de página em Admin/Template/.


0. As duas camadas de config — não confundir

Camada Onde vive Como ler/escrever Exemplos
Config do plugin (não é Cloudflare) wp_options.app_for_cf, array PHP serializado, 18 chaves (ver §8) wp option get/patch cfZoneId, cfAccountId, cloudflareAuth.token, cfPageCachingSeconds, cloudflarePreload, cfPurgeCacheOnAdminBar, cfTurnstile, cfProxy, cfR2Bucket
Config de zona Cloudflare (as ~52 "boas práticas" + Bot Management + WAF, ver §1) Na Cloudflare, não no WordPress API v4 (https://api.cloudflare.com/client/v4), bearer token = cloudflareAuth.token da opção acima ssl, security_header (HSTS), waf, bot_management, rocket_loader, cache_level, hotlink_protection, etc.

Confirmado por leitura completa do código: não existe namespace WP REST próprio exposto pelo plugin (app-for-cf não aparece em rest_api_init em nenhum dos 61 ficheiros PHP) — todas as chamadas de configuração de zona são directas à Cloudflare API v4 a partir do backend PHP do plugin (Api/Cloudflare.php), usando o token guardado localmente. Para automatizar fora do wp-admin, replicar essa mesma chamada directa (curl/PHP) em vez de procurar um endpoint local que não existe.

A opção cloudflareAuth suporta dois modos de autenticação (ver Setup::defaults()): type: 'token' (API token scoped, o único usado nos sites do bundle) ou o modo legado email + api_key (Global API Key — não usar, é o modo antigo e menos seguro da Cloudflare, mantido só por compatibilidade retroativa no plugin).

Ler a config do plugin (wp-cli)

PATH=/home/USER/public_html

# Opção completa (JSON)
wp option get app_for_cf --format=json --allow-root --path=$PATH

# Uma sub-chave
wp option pluck app_for_cf cfZoneId --allow-root --path=$PATH
wp option pluck app_for_cf cfPageCachingSeconds --allow-root --path=$PATH

# Editar uma sub-chave sem apagar as restantes (SEMPRE patch, nunca update, em opção serializada)
wp option patch update app_for_cf cfPageCachingSeconds 21600 --allow-root --path=$PATH
wp option patch update app_for_cf cloudflarePreload 1 --allow-root --path=$PATH

cfProxy, cfR2Bucket, cloudflareAuth, cfTurnstile são sub-arrays — wp option patch só edita um nível; para sub-arrays aninhados usar wp eval com update_option() sobre o array completo lido primeiro.

Extrair o token para chamar a Cloudflare API directamente

TOKEN=$(wp eval 'echo maybe_unserialize(get_option("app_for_cf"))["cloudflareAuth"]["token"];' --allow-root --path=$PATH)
ZONE=$(wp eval 'echo maybe_unserialize(get_option("app_for_cf"))["cfZoneId"];' --allow-root --path=$PATH)

curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/settings" | jq .

1. As definições de zona (por categoria) — lista completa do código

Fonte: Repository/Cloudflare.php::getSettingsToManage() (linhas 88-721), a definição autoritativa usada pelo próprio plugin para desenhar a página de Settings e para updateSettings(). Achado desta expansão: o número "44" da auditoria original referia-se à contagem informal mostrada na UI (que agrupa alguns campos); a contagem real de entradas no array getSettingsToManage() é 52. Lidas em bloco via GET /zones/{zone}/settings (a maioria) + endpoints individuais para os campos com override_endpoint (security_header, certificate_transparency, leaked_credential_checks, bot_management, speed_brain, fonts, content_converter, origin_max_http_version, h2_prioritization, settings/nel, flags para crawlhints, argo/tiered_caching). Escritas via PATCH /zones/{zone}/settings/{setting_id} com {"value": ...}, excepto onde a definição tem overwrite_write_method diferente (PUT para tudo em bot_management, POST para leaked-credential-checks/ct/alerting/crawlhints).

Categoria Definições
Topo (fora de secção) development_mode (bool, bom = off), security_level (select: off/essentially_off/low/medium/high/under_attack, bom = essentially_off) — mostradas no topo da página Settings, não numa tab
SSL/TLS ssl (modo de encriptação: off/flexible/full/strict/origin_pull), always_use_https, security_header (HSTS — ver §3), min_tls_version (1.0-1.3), opportunistic_encryption, tls_1_3 (radio: off/on/zrt — zero round trip), automatic_https_rewrites, ech (Encrypted Client Hello), certificate_transparency (ver §4, beta), tls_client_auth
Segurança waf (toggle legado, deprecated — ver §2), bot_fight_mode (Bot Fight Mode básico — ver §5a), ai_bots_protection, crawler_protection (ambos geridos via bot_management PUT — ver §5a), bot_likely_automated, bot_definitely_automated, bot_verified_bots, bot_static_resource_protection, bot_optimize_wordpress, bot_enable_js (Super Bot Fight Mode — ver §5a), leaked_credential_checks (ver §4), challenge_ttl (select 5min-12meses, bom = 30min), browser_check, replace_insecure_js
Speed polish (select: off/lossless/lossy), webp, speed_brain (beta, substitui Rocket Loader/Mirage antigos), fonts (beta, Cloudflare Fonts — serve Google Fonts a partir do edge), early_hints, rocket_loader (deprecated para segurança — ver §2), content_converter (beta), http2, origin_max_http_version (int, bom = 2), http3, h2_prioritization, 0rtt (TLS 1.3 zero round trip)
Caching cache_level (radio: basic/simplified/aggressive, bom = aggressive), browser_cache_ttl (select 0-12meses, 0 = respeitar headers de origem), crawlhints (beta, indica a crawlers quando o conteúdo mudou), always_online, tiered_caching (Argo Tiered Cache)
Network ipv6, websockets, pseudo_ipv4 (select: off/add_header/overwrite_header), ip_geolocation, max_upload (select 100-500MB), nel (Network Error Logging, value.enabled aninhado), opportunistic_onion
Scrape Shield email_obfuscation, hotlink_protection
# Ler uma definição individual
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/settings/rocket_loader" | jq .

# Escrever uma definição individual (formato standard "value")
curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/settings/rocket_loader" \
  -d '{"value":"off"}' | jq .

webp, content_converter, speed_brain, fonts, crawlhints, tiered_caching e outros podem estar plan-locked (não editáveis no plano Free Website) — a API devolve o valor actual mas ignora tentativas de escrita; não é bug, é limite de plano. A UI do plugin marca isto lendo editable do resultado da API (ver override_result_editable_if_has no código) e desactiva o <select>/checkbox correspondente.

EasyConfig — o que o botão "Easy config" aplica em bloco

Repository\Cloudflare::setEasyMode() (linha 723) aplica de uma vez, via POST /zones/{zone}/settings (bulk) + 5 chamadas individuais:

0rtt=on, browser_cache_ttl=0, cache_level=aggressive, early_hints=on,
http3=on, ip_geolocation=on, ipv6=on, min_tls_version=1.2,
opportunistic_encryption=on, opportunistic_onion=on, pseudo_ipv4=off,
rocket_loader=off, tls_1_3=zrt, websockets=on,
security_level=essentially_off (fora do WordPress; 'medium' fora do XF)

+ settings/nel → {"value":{"enabled":false}}
+ settings/origin_max_http_version → {"value":"2"}
+ settings/speed_brain → {"value":"on"}
+ settings/fonts → {"value":"on"}
+ argo/tiered_caching → {"value":"on"}

Não toca em SSL/TLS (excepto min_tls_version/opportunistic_encryption), Bot Management, WAF, HSTS nem Scrape Shield — o EasyConfig é só Speed + Caching + Network básico. Preferir aplicar definição a definição (§7 desta skill) para manter controlo e rasto de decisão, como já recomendado no achado original.


2. WAF real — o toggle waf está morto, usar Managed Ruleset

O toggle legado waf está deprecated para zonas novas: PATCH /zones/{id}/settings/waf devolve 1027 WAF is deprecated for this zone independentemente do valor enviado. O mecanismo real de protecção é a fase de ruleset http_request_firewall_managed, que pode estar completamente vazia (zero regras, zero protecção) mesmo num site "protegido pelo Cloudflare" — foi o estado encontrado em emanuelalmeida.pt antes da correcção.

# 1. Confirmar se a fase managed já tem entrypoint com regras
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/rulesets/phases/http_request_firewall_managed/entrypoint" | jq .
# Vazio/erro "could not find entrypoint" = zero protecção real, apesar do que o toggle `waf` sugere

# 2. Implantar o Cloudflare Managed Free Ruleset na fase managed
curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/rulesets/phases/http_request_firewall_managed/entrypoint" \
  -d '{
    "rules": [
      {
        "action": "execute",
        "action_parameters": {
          "id": "77454fe2d30c4220b5701f6fdfb893ba"
        },
        "expression": "true",
        "description": "Cloudflare Managed Free Ruleset"
      }
    ]
  }' | jq .

77454fe2d30c4220b5701f6fdfb893ba é o ID do Cloudflare Managed Free Ruleset — disponível em qualquer plano, incluindo Free Website. Testar sempre com um pedido real (POST/AJAX contra o site) depois de implantar, não confiar só no 200 OK do PUT.

A fase http_request_firewall_custom (regras próprias, não geridas) fica tipicamente vazia (10003 could not find entrypoint) num site sem regras custom — não é erro, é "zero regras próprias, só o Managed Ruleset". O plugin (versão Pro) gere regras custom através da página Firewall (§10) — "User agents" e "IP addresses" são interfaces amigáveis para criar regras Custom Rules na fase http_request_firewall_custom sem precisar de escrever expressões Cloudflare manualmente.

Rocket Loader não substitui isto. rocket_loader é uma definição de Speed (adia/reordena <script> no browser), não de segurança — o próprio plugin marca-o not_good_alert (recomenda desligado). Testado nesta sessão como hipótese de causa de CLS elevado num site: desligar não resolveu o CLS, mas manter desligado continua a ser a recomendação do plugin por outras razões (risco de quebra em sites com jQuery/Elementor). speed_brain é o sucessor recomendado pela Cloudflare (substitui Rocket Loader e Mirage), mas está marcado beta no código do plugin.


3. HSTS (security_header)

curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/settings/security_header" \
  -d '{
    "value": {
      "strict_transport_security": {
        "enabled": true,
        "max_age": 15552000,
        "include_subdomains": true,
        "preload": false,
        "nosniff": true
      }
    }
  }' | jq .

Gotcha real desta sessão: se a origem (nginx) já envia o seu próprio header Strict-Transport-Security, o HSTS do Cloudflare não o sobrepõe — é comportamento documentado, não bug. Confirmar com curl -sI que o cf-cache-status do pedido é MISS/DYNAMIC (não é cache stale) e que o max-age devolvido é o da origem, não o configurado no Cloudflare. Ajustar o max_age a partir da origem (nginx) quando o objectivo for realmente mudar o valor visto pelo browser.

preload: false é deliberado por omissão — entrar na lista de preload dos browsers é quase irreversível (remoção demora meses/anos a propagar). Só activar depois de HSTS estável em produção há semanas sem incidentes.

O "bom" (good) definido no código do plugin para este campo é max_age: 31536000 (12 meses), include_subdomains: 1, preload: 1, nosniff: '' (vazio, i.e. não activa X-Content-Type-Options via este mecanismo) — mais agressivo do que o valor sugerido na correcção original desta skill (6 meses). Escolher em função do risco de subdomínios não cobertos por HTTPS antes de ir para 12 meses + preload.


4. Certificate Transparency e Leaked Credential Checks

Endpoints próprios, fora do bulk settings:

# Certificate Transparency (alertas quando um certificado é emitido para o domínio)
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/ct/alerting" \
  -d '{"enabled": true}' | jq .

# Leaked Credential Checks (avisa se credenciais usadas no site apareceram em leaks conhecidos)
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/leaked-credential-checks" \
  -d '{"enabled": true}' | jq .

Ambos vinham off por omissão num site com anos de conta Cloudflare — não assumir que "conta antiga = tudo activo por defeito"; a Cloudflare tem adicionado features de segurança ao longo do tempo sem as retroactivar em zonas já existentes.


5. Bot Management (endpoint /bot_management) — 7 campos + is_robots_txt_managed

Endpoint próprio: GET/PUT /zones/{zone}/bot_management (não faz parte do bulk settings, e não aceita PATCH). Estes campos foram verificados via chamada directa à API (não são geridos pela UI do plugin através de getSettingsToManage(), ao contrário dos campos do §5a) — o plugin não expõe content_bots_protection, ai_training, ai_search, ai_user nem is_robots_txt_managed na sua própria página de Settings, mas estes são campos reais e válidos do endpoint bot_management da Cloudflare, geríveis por chamada directa à API.

Campo Padrão óptimo Descomplicar® O que faz mal se ligado
ai_bots_protection disabled Bloqueia crawlers de IA (GPTBot, ClaudeBot, etc.) — visto block activo em 2 sites do bundle
content_bots_protection disabled Idem, granularidade diferente
crawler_protection disabled Bloqueia crawlers genéricos
ai_training disabled Bloqueia bots de treino de modelos
ai_search disabled Bloqueia bots de motores de busca IA
ai_user disabled Bloqueia bots accionados por utilizador de IA (ex. ChatGPT a navegar em nome do utilizador)
fight_mode false Desafia (challenge JS) tráfego automatizado por heurística — apanha crawlers legítimos de IA que não resolvem JS challenges, visto a devolver "Just a moment..." em vez de conteúdo real

Achado real desta sessão (GEO): com is_robots_txt_managed: true, o Cloudflare injecta directivas Disallow: / no robots.txt servido para estes user-agents, por cima do ficheiro físico do site — mesmo que o ficheiro físico já permita esses bots explicitamente. Parsers que fundem grupos do mesmo user-agent aplicam o Disallow na mesma, tornando a permissão do ficheiro físico letra morta.

# Ler estado actual
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/bot_management" | jq .

# Corrigir — PUT, NUNCA PATCH (PATCH devolve sempre 10405 Method not allowed,
# independentemente das permissões do token)
curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/bot_management" \
  -d '{
    "ai_bots_protection": "disabled",
    "content_bots_protection": "disabled",
    "crawler_protection": "disabled",
    "ai_training": "disabled",
    "ai_search": "disabled",
    "ai_user": "disabled",
    "fight_mode": false,
    "is_robots_txt_managed": false
  }' | jq .

Depois de corrigir, purgar cache (Cloudflare + cache local WP, ex. WP Fastest Cache) — robots.txt costuma ter cache-control: max-age=315360000, a correcção não aparece sem purge. Confirmar ao vivo com curl -sI que cf-cache-status não é HIT e o corpo já não tem Disallow: / para os bots visados.

5a. Super Bot Fight Mode — campos geridos pela própria UI do plugin

Ao contrário do §5 (chamada directa, campos fora da UI), estes 8 campos são geridos por getSettingsToManage() e aparecem na tab "Security" da página Settings do plugin. Todos escrevem no mesmo endpoint PUT /bot_management, mas com nomes de campo JSON diferentes do que o id da definição no plugin:

ID no plugin Campo real na API bot_management Bom (good)
bot_fight_mode fight_mode false
ai_bots_protection ai_bots_protection block (nota: o good do código é block, oposto do padrão Descomplicar do §5 — ver decisão abaixo)
crawler_protection crawler_protection enabled (idem — oposto do padrão Descomplicar)
bot_likely_automated sbfm_likely_automated (select allow/block/managed_challenge) allow
bot_definitely_automated sbfm_definitely_automated (select allow/block/managed_challenge) allow
bot_verified_bots sbfm_verified_bots (select allow/block) allow
bot_static_resource_protection sbfm_static_resource_protection (bool) false
bot_optimize_wordpress optimize_wordpress (bool) false
bot_enable_js enable_js (bool) — comentário no código: "a API diz que é editável, mas só é possível de facto em plano Pro ou superior" false

Contradição intencional a documentar: o good que o código do plugin define para ai_bots_protection/crawler_protection é o valor "block"/ "enabled" (i.e. o próprio autor do plugin recomenda bloquear bots de IA por omissão). A decisão Descomplicar® (documentada no §5 e no gotcha da tabela §7) é o oposto — nunca bloquear bots de IA em nenhum site do bundle, por ser GEO-crítico (visibilidade em AI Overviews/ChatGPT/ Perplexity). Ao corrigir zonas, ignorar deliberadamente o "good" do plugin para estes 2 campos específicos e aplicar sempre disabled.

Todas as escritas neste grupo enviam unset_on_write: ['using_latest_model'] — o código remove explicitamente esse campo do payload antes de fazer PUT, porque using_latest_model é campo só de leitura devolvido pela API (indica se a Cloudflare está a usar o modelo de detecção de bots mais recente) e a API rejeita o PUT se ele for reenviado.


6. Scopes de token — lista completa (21 permissões, do código de setup)

Admin/Template/MultisiteSettings.php e Admin/Template/Settings.php embutem a lista completa e oficial de scopes recomendados pelo autor do plugin (usada para construir o link de criação de token pré-preenchido no dashboard Cloudflare) — muito mais granular do que os 6 grupos informais documentados na versão anterior desta skill:

Scope Cloudflare Nível de acesso Necessário para
Account.Access: Apps and Policies Edit Zero Trust Access (página Access, Pro)
Account.Access: Organizations, Identity Providers, and Groups Read Zero Trust Access — resolver grupos/IdPs nas políticas
Account.Account Analytics Read Dados agregados de conta (não usado nesta skill)
Account.Account Settings Read + Edit Detalhes/definições de conta Cloudflare
Account.Allow Request Tracer Read Página "HTTP request trace" (§10)
Account.Billing Read Info de plano/facturação Cloudflare mostrada no plugin
Account.Intel Read Páginas "IP address details"/"Domain details"/"WHOIS" (Cloudflare Intel API, §10)
Account.Turnstile Edit Criar/gerir sitekeys Turnstile CAPTCHA (§8, cfTurnstile)
Account.Workers R2 Storage Edit R2 media storage (página R2, Pro)
Account.Workers Scripts Edit Worker proxy (cfWorkersSubdomain/cfProxy, §8)
User.API Tokens Read O plugin lê as próprias permissões do token para colorir a checklist de setup a verde
Zone.Analytics Read Web Analytics / dashboards de tráfego
Zone.Bot Management Edit Bot Management + Super Bot Fight Mode (§5, §5a) — separado do Zone Settings, falta frequentemente no token gerado por omissão
Zone.Cache Purge Purge Purge de cache (página "Purge cache", botão admin bar)
Zone.Cache Rules Edit Cache Rules (página Rules, Pro)
Zone.Firewall Services Edit Firewall Rules, regras de User-Agent/IP (página Firewall, Pro)
Zone.Page Rules Edit Page Rules legadas (página Rules, Pro)
Zone.SSL and Certificates Edit Certificate Transparency, TLS client auth
Zone.Zone Edit Operações de gestão da própria zona
Zone.Zone Settings Edit As ~52 definições de zona do §1 (SSL/TLS, Speed, Caching, Network, Scrape Shield)
Zone.Zone WAF Edit WAF/Managed Ruleset (§2)

Zone.DNS não está na lista oficial do plugin — o plugin não gere DNS (DMARC/SPF/DKIM são lidos como registos públicos via dig, ver §7/§10, não via API Cloudflare autenticada).

Sintoma de scope em falta: a API devolve 403 ou o próprio plugin mostra um erro de permissão específico à operação, não um erro genérico de autenticação — o token pode estar válido e ainda assim faltar-lhe exactamente o scope daquela chamada. Testado nesta sessão: token com Zone SSL/Settings Edit funcionava para as definições de zona mas falhava em Bot Management até se alargar explicitamente o scope no dashboard Cloudflare (My Profile → API Tokens → editar token).


7. Gotchas e erros comuns

Sintoma Causa Solução
PATCH /zones/{id}/settings/waf → 1027 WAF is deprecated for this zone Toggle legado morto para zonas novas Implantar Managed Ruleset na fase http_request_firewall_managed (§2)
PATCH /zones/{id}/bot_management → 10405 Method not allowed Endpoint só aceita PUT Usar PUT sempre, mesmo para alterar um único campo (envia o objecto completo)
Correcção de robots.txt/HSTS não aparece no browser Header/ficheiro servido a partir de cache (Cloudflare e/ou plugin de cache WP) Purgar Cloudflare + cache WP local; confirmar cf-cache-status não é HIT antes de validar
HSTS configurado no Cloudflare mas browser continua a ver max-age diferente Origem (nginx) já envia o seu próprio header Strict-Transport-Security — Cloudflare não sobrepõe HSTS que a origem já envia Ajustar na origem, ou aceitar que o Cloudflare é só fallback para origens sem HSTS próprio
Regra de firewall/Transform Rule com operador matches (regex) falha ou não fica disponível na UI matches exige plano Business (WAF Advanced) Reescrever a expressão com in, eq ou contains — disponíveis em qualquer plano, incluindo Free
wp option update app_for_cf '...' apaga sub-chaves que não estavam no valor enviado wp option update substitui a opção inteira; app_for_cf é um array serializado Usar wp option patch update app_for_cf <chave> <valor> para editar uma sub-chave sem tocar nas restantes
R2 (media storage) devolve 10042 Please enable R2 through the Cloudflare Dashboard R2 não está activado a nível de conta (não é definição de zona nem do plugin) Activar manualmente no dashboard Cloudflare da conta antes de qualquer chamada à API de R2
ai_bots_protection/crawler_protection a block/enabled num site do bundle Divergência do padrão óptimo — visto em carstuff.pt e solarfvengenharia.com nesta sessão; o próprio good do código do plugin recomenda block (§5a), por isso corrigir manualmente sempre que se aplicar EasyConfig ou se seguir uma recomendação genérica do plugin Corrigir para disabled via PUT /bot_management (§5) — decisão explícita: nunca bloquear bots de IA em nenhum site Descomplicar
Página "Public page caching"/"Purge cache"/analytics parecem não fazer nada de imediato handleHeaders() só define Cache-Control: max-age=0,s-maxage=X — depende do Cloudflare respeitar esse header (cache_level/browser_cache_ttl compatíveis) e de não haver cookies de sessão a bloquear o cache (ver §9) Confirmar browser_cache_ttl = "Respect existing headers" (0) e cache_level ≠ basic; testar em aba anónima sem cookies wp-*/wordpress_*/comment_*/woocommerce_*
Botões "Firewall", "Access", "Rules" (criar regra), "R2", "Copy from...", "License key" aparecem desactivados/a cinzento ou com classe pro Funcionalidade gated para a versão Premium/Pro do plugin (Helper\Api::check() — ver §11) Confirmar licença activa (cfLicenseKey) ou aceitar a limitação no plano Free
wp option get app_for_cf mostra cfTurnstile:["0"] em vez do array completo com siteKey/onLogin/etc. Turnstile nunca foi activado neste site — o checkbox "Use Turnstile CAPTCHA" desmarcado grava só [0] (o <input type="hidden" value="0"> do formulário), não o array completo de Setup::defaults() Normal em sites sem Turnstile configurado; a estrutura completa (13 sub-chaves, ver §8) só aparece depois de gerar uma sitekey e gravar o formulário

8. Config do plugin (wp_options.app_for_cf) — as 18 chaves em detalhe

Fonte: Setup::defaults() (schema oficial de instalação) cruzado com a leitura ao vivo (wp option get app_for_cf) em emanuelalmeida.pt (16-08-2026) — as 18 chaves batem certo entre defaults e produção.

{"cfWorkersSubdomain":"","cfProxy":{"image":"0","url":"0"},"cfR2Bucket":{"media":""},"cfAccountId":"a8b00ad3227d729a146143a6a85a7522","cfZoneId":"574b243ebabd8ea2b8d89e68921f52cc","cfTokenId":"6b6d24fe8d324112ea4cfc328bf16f6c","cfZone":"emanuelalmeida.pt","cfPageCachingSeconds":"21600","cloudflareAuth":{"token":"cfut_..."},"cloudflareFirewallExpireDays":"7","cloudflareBlockIpsSpamClean":"0","cfExternalDataUrl":"","cloudflarePreload":"1","cfImagesTransform":"0","cfLicenseKey":"","cfPurgeCacheOnAdminBar":"1","cfTurnstile":["0"],"LockSettingsUserId":"1"}
Chave Tipo/Default O que faz
cfWorkersSubdomain string, '' Subdomínio *.workers.dev da conta Cloudflare, usado quando o Worker proxy (R2/imagens) está activo
cfProxy.image / cfProxy.url 0/1 cada Toggle interno para saber se URLs de imagem/media já estão a ser reescritas para servir via Worker/R2 (usado por filterWpGetAttachmentUrl()/filterWpCalculateImageSrcset() em Base/Pub.php)
cfR2Bucket.media string, '' Nome do bucket R2 escolhido/criado para armazenar media da biblioteca WordPress (feature Pro, página R2)
cfAccountId string ID da conta Cloudflare — resolvido automaticamente por getZoneId() a partir do hostname na primeira chamada, cacheado aqui
cfZoneId string ID da zona Cloudflare — idem, cacheado para evitar listZones() a cada pedido
cfTokenId string ID do token API (não o valor secreto) — mostrado na UI como referência ("Token ID: ...") para o admin confirmar qual token está activo
cfZone string Nome do domínio da zona (ex. emanuelalmeida.pt), cacheado junto com cfAccountId/cfZoneId
cfPageCachingSeconds int (segundos), '' (vazio = desligado) Tempo de s-maxage aplicado ao HTML para guests (§9) — 0/vazio desliga o guest page caching
cloudflareAuth.type 'token' ou 'email' Modo de autenticação — token é o único usado/recomendado; email+api_key é o modo legado Global API Key
cloudflareAuth.token string secreta Bearer token da Cloudflare API, scoped (ver permissões no §6)
cloudflareAuth.email / cloudflareAuth.api_key string, '' Só usados se type = 'email' (não usar)
cloudflareFirewallExpireDays int, default 7 (1-90) Dias até uma regra de firewall criada automaticamente pelo plugin (ex. bloqueio de IP de spammer, feature Pro) expirar sozinha
cloudflareBlockIpsSpamClean 0/1, default 1 Se 1, bloqueia automaticamente (feature Pro) o IP de um utilizador cujo comentário foi marcado como spam pela fila de moderação do WordPress
cfExternalDataUrl string URL, '' URL pública onde o media fica acessível quando R2 está activo — normalmente auto-preenchido ao activar R2, não editar manualmente salvo troubleshooting
cloudflarePreload 0/1, default 1 Se 1, o plugin usa hooks script_loader_tag/style_loader_tag/wp_footer para emitir headers HTTP Link: <url>;rel=preload (até 10 recursos) — funciona em conjunto com a definição de zona early_hints
cfImagesTransform 0/1, default 0 Se 1, activa Cloudflare Images Transform para blocos de imagem/media (serve AVIF/WebP a browsers modernos, formatos mais antigos a browsers antigos, qualidade reduzida em ligações muito lentas) — requer activação também a nível de zona no dashboard Cloudflare
cfLicenseKey string, '' Chave de licença da versão Premium/Pro do plugin — sem isto, Helper\Api::check() devolve sempre false (ver §11)
cfPurgeCacheOnAdminBar 0/1, default 0 Se 1, mostra um botão de purge de cache com um clique na admin bar do WordPress
cfTurnstile array com 13 sub-chaves quando activo: siteKey, secretKey, onRegister, onLogin, onPassword, onComment, onContactForm7, onHtmlForms, onMetForm, onWPForms, onWooCommerceRegister, onWooCommerceLogin, onWooCommercePassword CAPTCHA Turnstile aplicado por contexto — cada onX é um toggle independente que decide se o widget Turnstile aparece nesse formulário específico (registo/login/reset password/comentários WP nativos, WooCommerce, e os plugins de formulário Contact Form 7/HTML Forms/MetForm/WPForms se estiverem instalados)
LockSettingsUserId 0 ou ID de utilizador WP Se preenchido com um ID de utilizador, só essa conta WordPress pode alterar as definições Cloudflare a partir do wp-admin — risco: apagar essa conta ou trocar de utilizador tranca o acesso às definições

network_exclude (visto em código de multisite, Settings.php linha 290) é uma chave adicional só relevante em instalações multisite — indica que um site individual optou explicitamente por não herdar o token API de rede.


9. Guest page caching (HTML a nível de edge) — como funciona de facto

Página "Public page caching" (§10) e a chave cfPageCachingSeconds (§8) não usam Page Rules nem Cache Rules Cloudflare — usam um mecanismo mais simples e mais portável: o hook wp_headers do WordPress (Base\Pub::handleHeaders(), Base/Pub.php linha 276) define

Cache-Control: max-age=0,s-maxage=<cfPageCachingSeconds>

em toda resposta HTML elegível. max-age=0 diz ao browser para não cachear localmente; s-maxage=<N> diz a caches partilhadas (o edge da Cloudflare) para cachear durante N segundos — desde que a definição de zona browser_cache_ttl esteja em 0 ("Respect Existing Headers", o padrão recomendado no §1) e cache_level não seja basic.

Critérios de elegibilidade (pageCachingCriteria(), filtráveis via app_for_cf_guest_page_cacheable):

  • Utilizador não tem sessão WordPress activa (is_user_logged_in() falso)
  • Nenhum cookie do browser começa por wp-, wordpress_, comment_ ou woocommerce_ (senão a página é tratada como dinâmica/pessoal e o header de cache não é aplicado)
  • Pedido não é para /wp-login.php
  • Content-Type da resposta começa por text/html
  • Página não é 404
  • WP_DEBUG não está activo

Por omissão o valor sugerido na UI é 6 horas (21600 segundos, a mesma opção default usada em CachingGuestPage.php); opções disponíveis vão de 5 minutos a 24 horas, filtráveis via app_for_cf_cache_times, e o limite máximo é derivado da vida do nonce do WordPress (DAY_IN_SECONDS - 3600, tipicamente 23h).

# Ler/alterar o tempo de guest page caching
wp option pluck app_for_cf cfPageCachingSeconds --allow-root --path=$PATH
wp option patch update app_for_cf cfPageCachingSeconds 21600 --allow-root --path=$PATH   # 6h
wp option patch update app_for_cf cfPageCachingSeconds '' --allow-root --path=$PATH      # desligar

Como não é uma Page/Cache Rule, não aparece no dashboard Cloudflare como regra — só é visível inspeccionando o header Cache-Control da resposta HTTP (curl -sI https://SITE/) e o cf-cache-status.


10. As 14 páginas de admin — o que cada uma faz

Estrutura de menu confirmada em Admin/Base/Admin.php. O item de topo (Cloudflare, ícone dashicons-cloud) mostra um badge ! quando não há token configurado.

Slug (page=) Título no menu O que faz
app-for-cf_caching Public page caching Toggle + selector de tempo para o guest page caching do §9 (cfPageCachingSeconds)
app-for-cf_firewall Firewall 3 tabelas: Firewall Rules (link "Create rule" abre o dashboard Cloudflare — Pro), User agents (regras de bloqueio por user-agent geridas inline — Pro) e IP addresses (bloqueio manual por IP — Pro); todas suportam bulk enable/disable/delete via bulkActionNotice()
app-for-cf_access Access Lista de Zero Trust Access Apps + Groups da conta, com delete em massa (feature Pro — cria políticas de acesso, ex. proteger /wp-admin com SSO)
app-for-cf_rules Rules 2 tabelas: Page Rules legadas (link "Create page rule" abre dashboard — Pro) e Cache Rules modernas (link "Create cache rule" — Pro)
app-for-cf_r2 R2 (media storage) Explica e liga a biblioteca de media do WordPress a um bucket Cloudflare R2 (10GB grátis, depois $0.015/GB); requer token válido e licença Pro
app-for-cf_cache Purge cache Botão único "Purge Cloudflare cache..." — chama purgeCache() sem filtro (Purge Everything)
options-general.php?page=app-for-cf Settings (link para wp-admin/options-general.php?page=app-for-cf) A página principal com as 6 tabs de definições de zona (Setup/SSL-TLS/Security/Speed/Caching/Network+Scrape Shield) — ver §1; badge "Missing API token" se cloudflareAuth.token vazio
app-for-cf_analytics Web analytics Toggle único para injectar o beacon JavaScript do Cloudflare Web Analytics (privacy-first, sem cookies) em todas as páginas; link directo para o dashboard de analytics quando activo
app-for-cf_dmarc DMARC management Gráfico (Chart.js) de volume de email pass/fail por semana/mês + tabela de fontes de envio detectadas via os relatórios DMARC que a Cloudflare recebe em nome do domínio; link "View in Cloudflare" para o dashboard
app-for-cf_request-trace HTTP request trace Formulário para simular um pedido (URL, método, protocolo, bot score, país, threat score, skip challenge) e ver que produtos/regras Cloudflare seriam aplicados — requer scope Account.Allow Request Tracer
app-for-cf_ip-details IP address details Consulta a Cloudflare Intel API para um IP: PTR, ASN/organização, tipo, risk types — requer Account.Intel:Read
app-for-cf_domain-details Domain details Idem para domínios: tipo, IPs de resolução, categorias de conteúdo
app-for-cf_whois WHOIS Consulta WHOIS via Cloudflare Intel: datas de criação/actualização, registrant, registrar, nameservers
(sem entrada de menu própria) Copy Settings acedida a partir de um botão na página Settings Copia todas as definições de zona de uma zona diferente da mesma conta Cloudflare para a zona actual — irreversível, exige confirmação explícita ("I understand...") antes de activar o botão de submissão

Páginas exclusivas de multisite network admin (settings.php?page=):

Slug Título O que faz
app-for-cf_multisite-settings Cloudflare (em Network Admin → Settings) Define um token API partilhado por toda a rede (app_for_cf_network), com a mesma checklist de 21 permissões do §6; cada site individual pode optar por usar o token de rede ou o seu próprio
app-for-cf_multisite-r2 R2 (media storage) (Network Admin) Um único bucket R2 partilhado por todos os sites da rede, com o caminho de cada ficheiro prefixado por {siteId}/ (ex. cdn.site.com/50/2023/10/image.png) — feature Pro, licenciada ao hostname do site principal da rede

11. Free vs Pro — gate Helper\Api::check()

Helper\Api::$version / Helper\Api::check($force = false) (Helper/Api.php) determina se a licença Premium está activa através de um transient (acf_int) com 6 horas de validade (21600 segundos) — não é lido de cfLicenseKey directamente a cada pedido, é cacheado. Setup::install() chama Api::check(true) (forçado) na activação do plugin.

Funcionalidades bloqueadas/degradadas no plano Free (confirmado nos templates admin, condicional Helper\Api::$version ou classe CSS pro):

  • Botão "Create rule"/"Create page rule"/"Create cache rule"/"Create user agent rule"/"Create IP address rule" (Firewall, Rules) — sem eles, as tabelas continuam a listar regras já criadas manualmente no dashboard Cloudflare, só não permitem criar a partir do wp-admin
  • Página R2 (media storage) — bloqueada mesmo com token válido
  • Página Access (Zero Trust) — lista mas não gere
  • "Copy from..." (Copy Settings) — botão só aparece se Helper\WordPress::hasOwnApiToken() e implicitamente exige Pro para ser útil em produção
  • Campo "License key" só aparece se Helper\Api::$version já tiver alguma licença associada (mostra o campo para trocar/renovar)
  • "Block spammer IPs"/"Days that firewall rules last"/"External data URL" (cloudflareBlockIpsSpamClean, cloudflareFirewallExpireDays, cfExternalDataUrl) aparecem com classe CSS pro e disabled quando (int)$cloudflareAppInternal é falso — os campos ficam visíveis mas não editáveis

Todas as definições de zona do §1 (SSL/TLS, Speed, Caching, Network, Scrape Shield, Bot Management/SBFM), o WAF via Managed Ruleset (§2), HSTS (§3), Certificate Transparency/Leaked Credential Checks (§4), guest page caching (§9), purge de cache e Turnstile CAPTCHA (§8) funcionam no plano Free do plugin — não são gated, só dependem do plano Cloudflare subjacente (Free Website vs Pro/Business), que é uma dimensão diferente.


12. Automação — WP-CLI e Cron

# Purgar cache Cloudflare (Purge Everything) via linha de comandos
wp app-for-cf purge-cache --allow-root --path=$PATH

Implementado em Cli/PurgeCache.php (comando app-for-cf purge-cache, @when after_wp_load) — chama Repository\Cloudflare::purgeCache() sem argumentos (equivalente ao botão da página "Purge cache", §10).

Cron\Jobs::purgeCache(array $urls) é o mecanismo interno usado por Base\Pub::purgeCacheByPostIds() quando um post muda de estado (publicado/editado) ou um comentário muda de estado — faz chunking em lotes de 30 URLs por chamada à API de purge (limite da Cloudflare por pedido), parando o loop no primeiro chunk que falhar em vez de continuar a tentar os restantes.


13. Áreas do plugin sem config a auditar/activar (confirmado nesta sessão, emanuelalmeida.pt)

Área Estado
Cache Rules / Page Rules 0 regras — GET /zones/{id}/pagerules → count:0
Access (Zero Trust) Não configurado, fora de âmbito nesta sessão — funcionalidade real do plugin, ver §10
Turnstile CAPTCHA Site key existe mas desligada no plugin (cfTurnstile: ["0"]) — infra pronta se decidido substituir reCAPTCHA; ver estrutura completa das 13 sub-chaves no §8
Workers proxy (cfProxy) {image: off, url: off}
R2 (media storage) cfR2Bucket.media vazio, cfExternalDataUrl vazio — nunca activado neste site; requer licença Pro (§11)
DMARC/SPF/DKIM (Dmarc.php) Auditar via DNS público directo (dig TXT), nunca precisa de token Cloudflare — são registos públicos por desenho; a página DMARC do plugin (§10) é sobre os relatórios agregados que a Cloudflare recebe, não sobre editar os próprios registos DNS
Web Analytics (beacon JS) Confirmar por ausência/presença de cloudflareinsights/beacon.min.js no HTML servido, não por option — a página Web Analytics (§10) tem toggle próprio fora de wp_options (via API, não confirmado nesta sessão se lê de zona ou de conta)
EasyConfig (setEasyMode()) Aplica tudo em bloco (lista exacta no §1) — preferir aplicar definição a definição para manter controlo e rasto de decisão
Licença Pro (cfLicenseKey) Vazia — plugin a correr em modo Free; ver §11 para o que fica indisponível

Fonte

CONFIG-Plugins-Referencia.md §1 (App for Cloudflare®) — achados originais de HSTS/WAF/DMARC, credenciais de sessão. BUNDLE-Excelencia-WP.md §2.2 (Performance) e §2.4 (GEO — bots de IA, achado completo do is_robots_txt_managed e dos 7 campos de Bot Management via API directa). Ambos em Hub/04-Stack/02.04-Sistemas/71.Seguranca/, piloto emanuelalmeida.pt, sessão 16-08-2026.

Expansão desta sessão (16-08-2026): leitura completa do código-fonte do plugin app-for-cf v1.10.0 via SSH (server, 61 ficheiros PHP, 11.309 linhas) — não apenas os ficheiros tocados na auditoria pontual original. Ficheiros lidos na íntegra: app-for-cf.php (bootstrap), Setup.php (schema de wp_options), Repository/Cloudflare.php (2178 linhas — lista completa de 52 definições de zona + setEasyMode() + lógica de leitura/escrita), Admin/Template/Settings.php (766 linhas — UI completa, scopes de token, Turnstile), Admin/Base/Admin.php (menu, 14 páginas), Helper/Api.php (gate Free/Pro), Cron/Jobs.php, Cli/PurgeCache.php, e os 14 templates individuais em Admin/Template/ (Firewall.php, R2.php, Access.php, Rules.php, Analytics.php, Dmarc.php, RequestTrace.php, IpDetails.php, DomainDetails.php, Whois.php, CachingGuestPage.php, Caching.php, Cache.php, CopySettings.php, MultisiteR2.php, MultisiteSettings.php) e trechos relevantes de Base/Pub.php (657 linhas — guest page caching, preload, purge). Confirmado ao vivo com wp option get app_for_cf --format=json em emanuelalmeida.pt que as 18 chaves de wp_options batem certo com Setup::defaults(). Nenhuma escrita feita em produção durante esta expansão — só leitura de código-fonte e wp option get.