cloudflare-waf-author
Skill17 starsChanged 56 days agoMITView on GitHub
Workflow for crafting Cloudflare WAF rules across all three authoring interfaces - Terraform (cloudflare_ruleset), the Cloudflare Dashboard, and the Rulesets API. Covers both Custom Rules (actions like block, managed_challenge, log, skip-as-allow) and Managed-Rule Exceptions (action skip). Forces a docs-first read, peer-rule survey, explicit rule-type branch, and guards-by-type checklist before any expression is drafted. Use when adding or editing any Cloudflare WAF rule, when the user asks for a WAF block / challenge / allow / skip / OWASP child-rule bypass, or when reviewing such a change.
In this repository53 other files it gives its agents.
- agents-sdkSkill
- agent-workflowSkill
- api-designSkill
- aws-iamSkill
- bash-shell-scriptingSkill
- cicd-github-actionsSkill
- cloudflare-email-serviceSkill
- cloudflareSkill
- cloudflare-workers-authorSkill
- cloud-platformsSkill
- codebase-security-auditSkill
- containers-orchestrationSkill
- core-engineeringSkill
- database-postgresqlSkill
- databricksSkill
- data-engineeringSkill
- distributed-transactionsSkill
- documentation-standardsSkill
- domain-driven-designSkill
- durable-objectsSkill
- frontend-engineeringSkill
- git-workflowSkill
- go-rust-systemsSkill
- iam-security-advisorSkill
- independent-verificationSkill
- infrastructure-iacSkill
- kubernetes-containersSkill
- kubernetes-operator-developmentSkill
- mcp-developmentSkill
- memory-architectureSkill
- multi-perspective-reviewSkill
- networking-transportSkill
- observabilitySkill
- oktaSkill
- pdf-exportSkill
- python-developmentSkill
- reactflow-architecture-diagramsSkill
- sandbox-sdkSkill
- scripting-automationSkill
- security-testingSkill
- service-resilienceSkill
- single-file-dashboardSkill
- skills-compositionSkill
- skills-continuous-improvementSkill
- snowflakeSkill
- system-designSkill
- typescript-javascriptSkill
- web-perfSkill
- web-research-kb-refreshSkill
- workers-best-practicesSkill
- workload-identitySkill
- wranglerSkill
- zero-trustSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 5 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 24
Do NOT invoke for:
- Distinctive: line 34
Before Step 0, confirm which of the three interfaces the user is authoring through.
- Very specific: line 42
If unclear, ask. Per 405-cloudflare-waf-rules.mdc § "Source-of-truth discipline (NON-NEGOTIABLE)", a zone should have one documented …
- Distinctive: line 50
Before anything else, classify the request.
- Distinctive: line 60
If the rule type is unclear from the request, stop and ask the requester.
- Very specific: line 116
If no peer matches, stop and explain why before drafting.
- Distinctive: line 125
Ask for everything below before drafting.
- Very specific: line 163
Multi-value headers: use any(http.request.headers["X"][*] ...), never bare eq.
- Very specific: line 164
Content-type guards: use any(lower(http.request.headers["content-type"][*])[*] contains "multipart/form-data").
- Very specific: line 196
Run through the per-interface reviewer checklist from 405-cloudflare-waf-rules.mdc § "Reviewer checklist".
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Cloudflare WAF Author
Companion rule: 405-cloudflare-waf-rules.mdc (file-scoped to Cloudflare ruleset Terraform / JSON; topic-discoverable for Dashboard authoring). The rule is the gate; this skill is the workflow through the gate.
Voice: opinionated. Every WAF rule is a policy commitment that ships to production at the edge. Custom rules can DoS your own users; managed-rule skips can silently bypass OWASP signatures. The job is to make the predicate exactly the right shape and exactly traceable to a request.
When to invoke this skill
Invoke when:
- The user asks to add or edit a Cloudflare WAF rule of any kind - custom block, allow, challenge, log, or a managed-rule skip / exception / OWASP child-rule bypass.
- A diff touches a Cloudflare ruleset Terraform file (matches the file globs in
405-cloudflare-waf-rules.mdc) or a JSON payload destined for the Rulesets API. - The user is authoring a rule via the Cloudflare Dashboard (UI clicks under Security → WAF) and mentions it in the conversation.
- A user mentions a change ticket asking to "block country X", "allow IP range Y", "stop the WAF from blocking endpoint Z", or any equivalent.
- A code or change review finds a candidate rule that violates
405-cloudflare-waf-rules.mdc.
Do NOT invoke for:
- Cloudflare Single Redirect / Origin Rule / Transform Rule edits - different ruleset phases.
- Account-level WAF policy or Bulk Redirects - this skill is zone-level WAF only.
- Bot Management or DDoS configuration - separate Cloudflare products.
Pre-step: confirm the authoring interface
Before Step 0, confirm which of the three interfaces the user is authoring through. This determines provenance carriers, position semantics, and the templates you'll use in Step 4.
| Signal | Interface |
The user opened a .tf file, mentioned terraform apply, or pushed a branch to an IaC repo | Terraform |
| The user said "I'm in the Cloudflare dashboard", "WAF section", "Security → WAF" | Dashboard |
The user mentioned curl, client/v4/zones/<id>/rulesets, a JSON payload, or a script | API |
If unclear, ask. Per 405-cloudflare-waf-rules.mdc § "Source-of-truth discipline (NON-NEGOTIABLE)", a zone should have one documented authoring path; if the user is authoring via a path other than the documented one, push back before proceeding.
The six-step workflow
Step 0 - Identify the rule type (the branching decision)
Before anything else, classify the request. Everything downstream depends on this.
| Symptom | Rule type | Ruleset phase | Typical actions |
| "Block traffic from country X" / "Allow only trusted IPs to /admin" / "Challenge known-bad ASN" | Custom Rule | http_request_firewall_custom | block, managed_challenge, js_challenge, log, skip (as allow) |
| "WAF is blocking my legitimate POST" / "Skip OWASP rule X for endpoint Y" / "False positive on file upload" | Managed-Rule Exception (skip) | http_request_firewall_managed | skip only |
[!IMPORTANT] If the requester is asking for both ("allow these IPs AND skip the OWASP rules for them"), that's TWO separate rules in TWO separate phases. Don't conflate them. In Terraform, they live in separate files (or separate
cloudflare_rulesetresources); in the Dashboard, they're under different navigation paths; in the API, they're separate ruleset IDs.
If the rule type is unclear from the request, stop and ask the requester. Don't guess - the wrong phase is a wrong-behavior rule that may not fire at all.
Step 1 - Docs-first read (cannot skip)
Open the docs for the chosen rule type and skim before writing any expression. Do not rely on training-data memory; the API and field names evolve. If a user-cloudflare-docs MCP server is configured in the workspace, prefer it for fresh fetches.
Always read (both rule types, all interfaces)
| Doc | Why |
| Ruleset Engine fields reference | Verify exact field names. Multi-value fields (headers) need any(... [*] ...). |
| Ruleset Engine operators / functions | eq, in, contains, starts_with, lower(...), any(...). Operator gotchas live here. |
| Cloudflare country codes reference | Always paste country codes from this page; never type from memory. |
Custom rules - additional reading
| Doc | Why |
| WAF Custom Rules | Action options and their UX implications (block vs challenge vs managed_challenge vs js_challenge). |
cf.* request fields | cf.client.bot, cf.threat_score, cf.waf.score (Enterprise) - the high-leverage signals. |
400-cloudflare.mdc § "WAF Attack Score (Plan-Aware)" | When and how to use the score fields without self-DoS. |
Managed-rule exceptions - additional reading
| Doc | Why |
| Add an exception via API | Confirms the four exception types, the action_parameters shape, and the must-be-positioned-before-execute-rule rule. |
| Create an exception (Ruleset Engine) | When to use ruleset: current vs rulesets: [...] vs rules: { id: [...] } (you almost always want the third). |
| WAF changelog | Has the OWASP child-rule list you're targeting moved or been deprecated? |
| OWASP Managed Ruleset reference | What the rule IDs you're skipping actually do. |
Interface-specific additional reading
| Interface | Additional doc |
| Terraform | WAF Managed Rules with Terraform and terraform-provider-cloudflare changelog. |
| Dashboard | Custom Rules in the Dashboard and the Audit Logs documentation. |
| API | Rulesets API reference plus the expression test endpoint. |
[!IMPORTANT] Cite relevant documentation in the PR, change ticket, Dashboard runbook, or API sibling
.md. Do not paste generic documentation URLs into every Terraform rule comment.
Step 2 - Peer-rule survey (cannot skip)
Identify the closest existing rule to your case in the same zone and mirror its shape. The carrier for the survey depends on the interface:
| Interface | How to survey |
| Terraform | Open the target .tf file (matching Step 0's phase). Read every rule. Pick the closest one. |
| Dashboard | Navigate to Security → WAF → Custom rules (or Managed rules → Exceptions). Read every existing rule's expression and description. |
| API | GET /client/v4/zones/<zone_id>/rulesets/<ruleset_id> to fetch the current ruleset JSON. Grep the rules array for similar patterns. |
If no peer matches, stop and explain why before drafting. The handbook's canonical shapes for both rule types live in 405-cloudflare-waf-rules.mdc § "Pre-edit gate" - read those first.
[!TIP] If the closest peer is a year old and the requester's flow doesn't match it cleanly, that's a signal the request may need a different approach (a new managed-rule sensitivity tune, a different action, or a code-side fix). Push back and discuss before drafting.
Step 3 - Requester confirmation (cannot skip)
Ask for everything below before drafting. If any answer is missing, stop.
Always (both rule types, all interfaces)
- Change ticket / approval number, named owner, business justification
- Exact host(s), path(s), method(s) - confirmed against actual traffic, not assumed
- Source IP shape: trusted (named list) only / trusted + geo escape hatch / fully open / specific named source set
- Soak plan: how long in
logmode before promoting to enforce, and how the soak will be validated (Security Events filter, dashboard, etc.)
Custom rules - additional
- Intended action:
block,managed_challenge,log(soak),skip(allow) - Is this complementing an existing block / allow rule? If so, which one - it must be positioned correctly relative to that rule.
Managed-rule exceptions - additional
- Browser-app or server-to-server flow? (Origin header pinning only works for browser flows.)
- Which OWASP child-rule IDs are actually firing on the legitimate flow? (Cloudflare Security Events filter:
action=block,host=...,path=..., last 7 days. Skip only those, not the whole ruleset.)
[!TIP] If a custom-rule requester says "block all foreign traffic" - push back. Without a trusted-IPs escape hatch, traveling internal users and partners are blocked. Always include the escape hatch.
If a managed-rule-skip requester says "skip the whole OWASP managed ruleset for my host" - push back. Almost always 2-3 child rules are firing; skip those, not the whole ruleset.
Step 4 - Draft the expression
Pick the template for your interface (from the pre-step) AND your rule type (from Step 0). Templates live in the references/ directory to keep this SKILL.md focused on the workflow.
| Rule type | Terraform | Dashboard | API |
| Custom Rule | references/templates-terraform.md § Custom Rule | references/templates-dashboard.md § Custom Rule | references/templates-api.md § Custom Rule |
| Managed-Rule Exception | references/templates-terraform.md § Managed-Rule Exception | references/templates-dashboard.md § Managed-Rule Exception | references/templates-api.md § Managed-Rule Exception |
Common patterns across all six combinations (interface × rule type):
- Path predicate uses one of the four sanctioned shapes from
405-cloudflare-waf-rules.mdc§ "Path predicate decision matrix" -eq(default),starts_with(..., "...")with trailing slash, alternation, or - never - barestarts_withwithout trailing slash. - Multi-value headers use
any(http.request.headers["X"][*] ...), never bareeq. - Content-type guards use
any(lower(http.request.headers["content-type"][*])[*] contains "multipart/form-data"). - Multipart skips include the
filename=body marker. - Trusted-IP guards reference a named Cloudflare IP List (the convention in this skill is
$trusted_egress_ips; your zone's actual list name will differ - use it). - Allow-before-block ordering is non-negotiable in custom rules: the allow-skip rule must be positioned ABOVE the block rule it complements.
Step 5 - Record concise provenance + verify consistency
Provenance is mandatory, but it must not duplicate the rule expression or the change ticket:
| Interface | Carrier |
| Terraform | One direct HCL # line above the rule; add up to two lines only for non-obvious security or lifecycle context. Detailed evidence stays in the PR/ticket. |
| Dashboard | (1) Rule description (purpose-based, optionally ticket-suffixed) plus (2) an out-of-band runbook entry (Confluence / ServiceNow / repo doc). Both are required - Audit Logs cover who / what / when but never why. |
| API | Repo-resident sibling .md file alongside the JSON payload. The description field carries the purpose. |
For Terraform, retain date/ticket and purpose/scope. Add a Ray ID, child-rule lineage, unusual source scope, known gap, or expiry only when relevant. Never generate Why each guard, Approval:, or “same shape as” boilerplate. Dashboard/API records may be structured but should still omit fields that add no rule-specific value.
Then verify all four sources of truth agree:
- Title of the PR / change ticket / API call summary describes the predicate intent ("narrow", "broaden", "scope to trusted IPs", "block country X").
- Body uses the same vocabulary.
- Provenance record identifies the change and any non-obvious security decision without restating the expression.
- Saved rule expression matches all three.
A drift between any two of these is the most common cause of "the change shipped but did the wrong thing" incidents.
Self-check before submitting
Run through the per-interface reviewer checklist from 405-cloudflare-waf-rules.mdc § "Reviewer checklist". The high-leverage subset (always run):
Always (both rule types, all interfaces)
- Cited relevant Cloudflare documentation in the PR, ticket, runbook, or API sibling file
- Checked the closest peer rule; cite it only when lineage or divergence matters
descriptionfield is purpose-based (Terraform: ticket-free, ticket lives in concise#provenance; Dashboard / API: ticket-suffixed if required by zone convention)- Path predicate matches one of the four sanctioned shapes
- Multi-value headers use
any(... [*] ...)form (not bareeq) - No hardcoded IPs - a named IP List is referenced
- Title / body / provenance artifact / saved rule expression all agree
- Soak plan documented (log-mode duration and validation method)
Custom rules only
- Any non-obvious action or accepted risk is documented in the appropriate change record
- Allow-as-skip rules positioned ABOVE corresponding block rules
- Geo blocks include trusted-IPs escape hatch (unless OFAC compliance block)
- No
js_challengeon/api/*paths - New block-class rules went through a
log-mode soak before being promoted - Country codes copy-pasted from Cloudflare reference, not typed
Managed-rule exceptions only
- OWASP child-rule IDs sourced from actual Security Events firings (not guessed from symptom)
- Skip rule positioned BEFORE the
executerule it bypasses - Multipart skips include the
filename=body marker - Origin guard (when present) uses
any(... [*] eq "...")form
Per-interface additions
- Terraform:
terraform fmtclean,terraform planreviewed by author + at least one peer, file mode100644, heredoc indent matches neighboring rules. - Dashboard: clone existing rule before editing, save as a single logical change per Audit Log entry, staging-zone dry-run completed, runbook entry created.
- API:
GET+ ETag captured,PUTwithIf-Match,position.before/after(notindex), JSON validated via expression test endpoint, response logged to the run-log.
See references/common-failure-modes.md for failure-mode patterns observed across many WAF authoring teams (not specific to any one team).
Related
405-cloudflare-waf-rules.mdc- the rule (file-scoped guardrail; covers both rule types and all three interfaces)400-cloudflare.mdc- broader Cloudflare ruleset patterns including OWASP Managed Ruleset configuration (PL1 + anomaly score threshold 60)cloud-platformsskill - cross-cloud platform patternscodebase-security-auditskill - WAF rule reviews are security reviews
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

