Cloudflare Hygiene
Periodic posture sweep over live zones: preflight -> sweep (read-only) -> report -> confirm -> apply -> verify. Optimization = real wins only: TLS strict, HTTPS forced, modern protocols on, DNS proxied + mail auth complete. No theater (no settings churned that already match optimal, no proxying records that must stay direct). Building on Cloudflare (Workers/Pages/KV/D1) -> cloudflare, hand off.
Preflight
Permission scope — this skill mutates zone settings + DNS, needs an authorized session that can write them. Probe once:
GET /zones/{id}/settings/ssl. 9109/10000 (Unauthorized) -> scope too narrow, STOP and surface the upgrade path (see Access setup below) before sweeping. Don't limp through a settings-blind run.Enumerate zones —
GET /zones(paginate). List name · id · plan. Warn: full sweep across N zones takes a moment.All work goes through
mcp__cloudflare__execute, which runs a JS async arrow function — not a plain REST caller. Every call in this skill is shorthand for that shape:async () => cloudflare.request({ method: "GET", path: `/zones/${zoneId}/settings/ssl` })accountIdis pre-injected; zone ids come from step 2. Returns{success, status, result, errors, result_info}— readerrors[0].codefor the 9109 probe,result_info.total_pagesto paginate. Batch a whole category's GETs into oneexecutecall (loop inside the function, return an object) — one round-trip per category beats one per setting.mcp__cloudflare__search(OpenAPI spec, refs resolved) for endpoint/body shapes;mcp__cloudflare__docsfor product behavior.
Sweep (Read-Only)
Per zone, every category below — each shows in the report, clean ones as "ok", never silently dropped. GETs only here; no mutation. Full endpoint + optimal-value matrix lives in references/best-practices.md; scripts/audit.mjs is the reference read-only sweep logic (GETs + verdict classifier).
- SSL/TLS —
settings/ssl(wantstrict);settings/always_use_https(on);settings/min_tls_version(≥1.2);settings/tls_1_3(on);settings/automatic_https_rewrites(on);settings/security_headerHSTS (enabled, max-age ≥6mo, includeSubDomains; preload = opt-in only). - Security / WAF / bot —
rulesetsphases (http_request_firewall_manageddeployed?ddos_l7present?);settings/security_level(medium);bot_management(Bot Fight Mode on — Free tier);settings/email_obfuscation(on);settings/browser_check(on); custom firewall ruleset (http_request_firewall_custom— any bespoke rules?); rate limiting ruleset (http_ratelimitphase — login/API endpoints protected?); IP access rules / Zone Lockdown (firewall/access_rules/rules— stale allowlists?); Page Shield (page_shield— client-side/Magecart, plan-gated); Authenticated Origin Pulls (settings/tls_client_auth— mTLS edge->origin so origin only trusts CF); Leaked Credential Check (leaked-credential-checks— alerts on breached creds, plan-gated). Under Attack mode = situational (incident only), never default-on. - Performance / caching —
settings/brotli(on);settings/early_hints(on);settings/cache_level(aggressive/standard);settings/browser_cache_ttl;settings/rocket_loader(case-by-case — can break JS, report don't force). - Network protocols —
settings/http3(on);settings/0rtt(on, idempotent-GET caveat);settings/ipv6(on);settings/websockets(on); HTTP/2 (on). - DNS hygiene —
dns_records: proxiable A/AAAA/CNAME should be proxied (orange) unless intentionally direct (flag grey-cloud = origin exposed);dnssec(active); parse TXT for SPF (rootv=spf1), DKIM (*._domainkey), DMARC (_dmarc.<zone>— flag missing as action); dangling CNAMEs -> takeover risk; low TTLs on stable records. - Analytics / observability — Web Analytics (
/accounts/{acct}/rum/site_info/list— privacy-first, free; enabled for this zone?); Security Events summary (GraphQLfirewallEventsAdaptive— last 24h threats blocked, top rules/countries -> posture context, not a setting); Logpush (logpush/jobs— paid/Enterprise, report gap only). Pull the security-events summary read-only so the report shows what the WAF is actually catching, not just whether it's on.
Report
Table per zone × category: check · current · optimal · verdict (ok / attention = watch / action = fix available, named). Lead per zone: "Zone X: N ok, N attention, N actions". Then AskUserQuestion multiSelect over the actions only. Anything that can break traffic gets its own option, side effects stated first.
Actions (Each Confirmed, Side Effects Stated First)
- SSL mode -> Full (strict):
PATCH settings/ssl {value:"strict"}— breaks if origin lacks a valid cert. Confirm origin has a cert (Cloudflare Origin CA or real CA) before flipping. - Always Use HTTPS / Auto HTTPS Rewrites / min TLS 1.2 / TLS 1.3 on:
PATCHeach — low risk; min-TLS bump can lock out ancient clients, name it. - HSTS:
PATCH settings/security_header {value:{strict_transport_security:{enabled,max_age,include_subdomains}}}(single-setting body wraps invalue;nosniffalso available). Hard to undo — browsers cache it; preload is near-permanent. Default no preload unless user explicitly opts in. - Brotli / Early Hints / HTTP/3 / 0-RTT / IPv6 / WebSockets on:
PATCH settings/{id} {value:"on"}each — safe, idempotent. - email obfuscation / browser check on:
PATCH settings/{id} {value:"on"}— safe. - Bot Fight Mode on:
PUT bot_management {fight_mode:true}(whole-object PUT, not a setting PATCH) — safe; can challenge some automation. - Rate limiting rule:
PUT rulesets/phases/http_ratelimit/entrypoint— protect login/API/expensive endpoints. Set thresholds from real traffic (use the analytics summary) — too tight = legit 429s. Confirm the matched path + threshold. - Authenticated Origin Pulls:
PATCH settings/tls_client_auth {value:"on"}— origin must be configured to require the CF client cert first, else all traffic 502s. Two-step, origin-side change owned by user. - Page Shield:
PUT page_shield {enabled:true}— plan-gated (Pro+); if Free errors, report the gap, don't churn. - Leaked Credential Check:
POST leaked-credential-checks {enabled:true}— plan-gated; same fallback. - Prune stale IP access rules:
DELETE firewall/access_rules/rules/{id}— confirm each; an old allow-rule can be a bypass hole. - Enable Web Analytics:
POST /accounts/{acct}/rum/site_infofor the zone — free, privacy-first, no cookie banner; gives RUM without GA. Safe. - Enable DNSSEC:
PATCH dnssec {status:"active"}-> returns DS record; incomplete until user adds the DS record at the registrar — give them the DS values, it's a two-step. - Proxy a grey-cloud record:
PATCH dns_records/{id} {proxied:true}— never proxy mail (MX/its A), verification, or services needing direct origin. Confirm per record. - Add DMARC: propose
_dmarc.<zone>TXT (startv=DMARC1; p=none; rua=mailto:...for monitoring), confirm value,POST dns_records.
Access Setup (When Preflight Fails)
The cloudflare MCP is a remote OAuth server (https://mcp.cloudflare.com/mcp) — there is no API token in its config to widen. A 9109/10000 means the authorized Cloudflare account lacks the permission, not that a token needs editing. Two ways out:
- Fix the account — the OAuth'd user needs Zone.Settings:Edit + DNS:Edit + Zone:Read on the target zones (plus Bot Management / SSL where the plan allows). Change the member's role in the Cloudflare dashboard -> Manage Account -> Members, then reconnect the MCP (
/mcp) so the session picks up the new scope. Re-run preflight after. - Fall back to the script —
scripts/audit.mjstakesCLOUDFLARE_API_TOKENdirectly, so a scoped token (My Profile -> API Tokens) gets a read-only sweep without touching the MCP session. Read-only: applies still need the MCP path above.
Rules
- Sweep = read-only. Change state only via confirmed actions, one at a time -> verify (re-GET, confirm new value) -> next.
- Side effects before confirm, always: SSL-mode origin break, min-TLS client lockout, HSTS/preload permanence, DNSSEC two-step, proxying a direct-only record.
- No churn: a setting already at optimal is
ok, not an action — don't PATCH it to look busy. Clean zone -> "posture optimal", stop. - Report-only is valid — fixing is optional, never assumed.
- Free-plan limits: some managed-WAF rules, Bot Management depth, and Page Rules count are plan-gated — report the gap, don't pretend it's fixable on Free.
- Boundaries: Workers/Pages/KV/D1/R2/Terraform ->
cloudflare; registrar-side DNS, origin-server config, email DNS content policy -> out of scope, name it.