WAF Rules skill for Codex

Count first, block later, except narrowly. Codex rolls out WAF protection the cautious way: logging first, managed rules in count or log mode through a business peak, then per-rule, per-path exceptions for confirmed false positives before switching to block. It writes Cloudflare and AWS WAF rules as Terraform, reads WAF logs to classify matches, adds expiring virtual patches for CVEs, and asks before any block-mode change goes live.

  • Security
  • $waf-rules
  • 165 lines
  • 2 files
  • ~1,850 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- waf-rules

Installs to ~/.agents/skills/waf-rules/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $waf-rules, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Writes, deploys and tunes web application firewall rules on Cloudflare WAF and AWS WAF (plus ModSecurity or Coraza with the OWASP Core Rule Set) - managed rulesets, custom expressions, rate limiting, bot and geo rules, count-mode rollouts and narrowly scoped exceptions for false positives - managed as Terraform. Use when enabling a WAF, when legitimate requests get 403s from a managed rule, when writing a rule to block an abusive path or virtual-patch a CVE, or when reviewing WAF logs to decide what to tune.

Use it when

  • You are enabling a WAF for the first time
  • Legitimate users or webhooks are being blocked by a managed rule
  • You need a temporary virtual patch for a known CVE
  • WAF rules live only in a dashboard and need to move to code

Use something else when

How to check the result

Replayed false-positive requests pass, a harmless known-bad probe to an owned URL is blocked with the expected rule ID in the logs, and 403 rates stay flat for 48 hours after the change.

The mistake it prevents

Fixing a false positive by skipping the entire managed ruleset for a path, which silently removes protection from everything on it.

~/.agents/skills/waf-rules/SKILL.md

--- name: waf-rules description: Writes, deploys and tunes web application firewall rules on Cloudflare WAF and AWS WAF (plus ModSecurity or Coraza with the OWASP Core Rule Set) -… license: MIT ---

Done well means: every rule went live in log or count mode first, every block is explained by a rule ID in the logs, false positives are fixed with the narrowest possible exception (one rule, one path, one parameter), and the whole configuration lives in version control. A WAF is a filter in front of the application, never a substitute for fixing the bug.

Before touching rules

  1. Read AGENTS.md and find how the WAF is managed: Terraform (cloudflare_ruleset, aws_wafv2_web_acl), CloudFormation, CDK, or dashboard only. If dashboard only, export the current config before changing anything and propose moving it to code.
  2. Get logs flowing first. Cloudflare: Security Events (Security Analytics) or Logpush of firewall events. AWS WAF: logging to CloudWatch Logs (log group name must start with aws-waf-logs-), S3 or Firehose.
  3. Identify the traffic: APIs with JSON bodies, file uploads, rich-text editors, GraphQL, webhooks from third parties. These are the usual false-positive sources.

Production WAF changes affect all traffic. Show the user the plan (terraform plan) and get explicit confirmation before applying any rule in block mode.

Rollout order

- [ ] 1. Managed rules on in log/count mode; collect at least 7 days including a business peak
- [ ] 2. Review top-matching rules per path; classify true vs false positives
- [ ] 3. Add narrow exceptions for confirmed false positives
- [ ] 4. Switch managed rules to block, one rule group at a time
- [ ] 5. Add custom rules and rate limits (count first, then block)
- [ ] 6. Alert on block-rate spikes and on sudden drops (rule disabled or bypassed)

Cloudflare

Custom rules use the Rules language. Header names in http.request.headers are lowercase.

# provider v4 block syntax; v5 takes rules = [ { ... } ] as a list attribute
resource "cloudflare_ruleset" "custom" {
  zone_id = var.zone_id
  name    = "custom firewall rules"
  kind    = "zone"
  phase   = "http_request_firewall_custom"

  rules {
    description = "Block admin panel outside office and VPN ranges"
    expression  = "(starts_with(http.request.uri.path, \"/admin\") and not ip.src in $office_ranges)"
    action      = "block"
  }
  rules {
    description = "Challenge login posts from non-browser clients"
    expression  = "(http.request.uri.path eq \"/login\" and http.request.method eq \"POST\" and not cf.client.bot)"
    action      = "managed_challenge"
  }
}
  • Exceptions to managed rules go in the managed phase (http_request_firewall_managed) as a rule with action = "skip" placed before the managed ruleset execution, skipping only the specific rule IDs for the specific path. Skipping the whole ruleset for a path is the common over-broad fix.
  • The Cloudflare OWASP Core Ruleset scores requests; tune the anomaly score threshold and paranoia level rather than disabling it. Start at paranoia level 1.
  • Rate limiting rules (http_ratelimit phase) key on IP by default; for APIs behind NAT or mobile carriers, key on a header such as an API key or session cookie where the plan allows.
  • log as an action is limited to some plans; on others, test new expressions with managed_challenge on a low-traffic path or use Security Analytics to see what would match.
  • Use the Terraform provider version the repo already pins; resource schemas changed between major versions (v4 to v5), so read the pinned provider docs before writing blocks.

AWS WAF (v2)

rule {
  name     = "aws-common"
  priority = 10
  override_action { none {} }          # use count {} for the observation period
  statement {
    managed_rule_group_statement {
      vendor_name = "AWS"
      name        = "AWSManagedRulesCommonRuleSet"
      rule_action_override {           # one noisy rule to count, the rest still block
        name = "SizeRestrictions_BODY"
        action_to_use { count {} }
      }
    }
  }
  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = "aws-common"
    sampled_requests_enabled   = true
  }
}
  • Narrow exceptions: count the noisy rule, then add a later custom rule that blocks when the rule’s label is present and the request is not on the excepted path (label_match_statement combined with a not + byte_match_statement on the URI). That keeps protection everywhere else.
  • Body inspection is limited: 8 KB for ALB and AppSync, 16 KB default (configurable up to 64 KB) for CloudFront, API Gateway and some others. Set oversize_handling deliberately; CONTINUE lets large payloads skip body inspection.
  • Useful baseline groups: AWSManagedRulesCommonRuleSet, AWSManagedRulesKnownBadInputsRuleSet, AWSManagedRulesAmazonIpReputationList, plus SQLiRuleSet and language-specific sets that match the stack. Bot Control and ATP/ACFP groups cost extra per request.
  • Rate-based rules aggregate over a 1, 2, 5 or 10 minute window and can key on IP, forwarded IP, header, cookie or query argument.

CloudWatch Logs Insights query for tuning:

fields @timestamp, httpRequest.uri, terminatingRuleId, httpRequest.clientIp
| filter action = "BLOCK"
| stats count(*) as hits by terminatingRuleId, httpRequest.uri
| sort hits desc
| limit 50

ModSecurity / Coraza with CRS

Exclusions follow the CRS pattern: ctl:ruleRemoveTargetById for one argument on one path (in a REQUEST-900 exclusion file), not SecRuleRemoveById globally. Raise the paranoia level only after PL1 runs clean.

Deciding true vs false positive

SignalLikely
Match on a known field that holds rich text, code or HTML (editor, comment, template)False positive: exception for that field on that path
Many IPs, normal user agents, authenticated sessions, business hoursFalse positive
Scanner user agents, sequential payloads, one IP across many pathsTrue positive
Matches only from third-party webhook IPs on the webhook pathFalse positive: verify the sender by signature in the app, exclude the path from body rules

Virtual patching a CVE

Write a custom rule matching the exploit precondition (path plus the specific parameter shape), deploy in count, confirm it matches the public proof of concept in a test environment and not normal traffic, then block. Record the CVE and an expiry: remove the rule after the real patch ships, or it outlives everyone’s memory of why it exists.

Gotchas

  • Rules that trust X-Forwarded-For can be spoofed unless the WAF knows the trusted proxy chain. Use the platform’s client IP field.
  • Geo-blocking is easy to evade with VPNs; treat it as noise reduction only.
  • Blocking returns 403 with a WAF page; API clients see HTML instead of JSON. Configure a custom JSON response for API hostnames.
  • A rule that blocks your own health checks or uptime monitors causes a false outage. Exempt them by path and source, not by user agent.
  • WAF only sees traffic routed through it. Confirm the origin rejects direct connections (allowlist CDN ranges, authenticated origin pulls, or private origin).

Verification

  • Replay sampled false-positive requests against the tuned rule in a test zone or staging ACL: they pass.
  • Send a harmless known-bad probe (for example ?q=<script>alert(1)</script> or a CRS test string) to a URL you own: blocked, with the expected rule ID in the logs.
  • Compare block counts and 403 rates for 48 hours after each change; investigate any spike.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "WAF Rules"
  short_description: "Count first, block later, except narrowly"
  default_prompt: "$waf-rules Our checkout API gets 403s from AWS WAF since Friday - find the rule and fix it narrowly"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem