Files
dotfiles/.agents/skills/cloudflare-deploy/references/waf/gotchas.md
2026-03-17 16:53:22 -07:00

6.1 KiB

Gotchas & Troubleshooting

Execution Order

Problem: Rules execute in unexpected order Cause: Misunderstanding phase execution Solution:

Phases execute sequentially (can't be changed):

  1. http_request_firewall_custom - Custom rules
  2. http_request_firewall_managed - Managed rulesets
  3. http_ratelimit - Rate limiting

Within phase: top-to-bottom, first match wins (unless skip)

// WRONG: Can't mix phase-specific actions
await client.rulesets.create({
  phase: 'http_request_firewall_custom',
  rules: [
    { action: 'block', expression: 'cf.waf.score gt 50' },
    { action: 'execute', action_parameters: { id: 'managed_id' } }, // WRONG
  ],
});

// CORRECT: Separate rulesets per phase
await client.rulesets.create({ phase: 'http_request_firewall_custom', rules: [...] });
await client.rulesets.create({ phase: 'http_request_firewall_managed', rules: [...] });

Expression Errors

Problem: Syntax errors prevent deployment Cause: Invalid field/operator/syntax Solution:

// Common mistakes
'http.request.path'  'http.request.uri.path' // Correct field
'ip.geoip.country eq US'  'ip.geoip.country eq "US"' // Quote strings
'http.user_agent eq "Mozilla"'  'lower(http.user_agent) contains "mozilla"' // Case sensitivity
'matches ".*[.jpg"'  'matches ".*\\.jpg$"' // Valid regex

Test expressions in Security Events before deploying.

Skip Rule Pitfalls

Problem: Skip rules don't work as expected Cause: Misunderstanding skip scope Solution:

Skip types:

  • ruleset: 'current' - Skip remaining rules in current ruleset only
  • phases: ['phase_name'] - Skip entire phases
// WRONG: Trying to skip managed rules from custom phase
// In http_request_firewall_custom:
{
  action: 'skip',
  action_parameters: { ruleset: 'current' },
  expression: 'ip.src in {192.0.2.0/24}',
}
// This only skips remaining custom rules, not managed rules

// CORRECT: Skip specific phases
{
  action: 'skip',
  action_parameters: {
    phases: ['http_request_firewall_managed', 'http_ratelimit'],
  },
  expression: 'ip.src in {192.0.2.0/24}',
}

Update Replaces All Rules

Problem: Updating ruleset deletes other rules Cause: update() replaces entire rule list Solution:

// WRONG: This deletes all existing rules!
await client.rulesets.update({
  zone_id: 'zone_id',
  ruleset_id: 'ruleset_id',
  rules: [{ action: 'block', expression: 'cf.waf.score gt 50' }],
});

// CORRECT: Get existing rules first
const ruleset = await client.rulesets.get({ zone_id: 'zone_id', ruleset_id: 'ruleset_id' });
await client.rulesets.update({
  zone_id: 'zone_id',
  ruleset_id: 'ruleset_id',
  rules: [...ruleset.rules, { action: 'block', expression: 'cf.waf.score gt 50' }],
});

Override Conflicts

Problem: Managed ruleset overrides don't apply Cause: Rule ID doesn't exist or category name incorrect Solution:

// List managed ruleset rules to find IDs
const ruleset = await client.rulesets.get({
  zone_id: 'zone_id',
  ruleset_id: 'efb7b8c949ac4650a09736fc376e9aee',
});
console.log(ruleset.rules.map(r => ({ id: r.id, description: r.description })));

// Use correct IDs in overrides
{ action: 'execute', action_parameters: { id: 'efb7b8c949ac4650a09736fc376e9aee', 
  overrides: { rules: [{ id: '5de7edfa648c4d6891dc3e7f84534ffa', action: 'log' }] } } }

False Positives

Problem: Legitimate traffic blocked Cause: Aggressive rules/thresholds Solution:

  1. Start with log mode: overrides: { action: 'log' }
  2. Review Security Events to identify false positives
  3. Override specific rules: overrides: { rules: [{ id: 'rule_id', action: 'log' }] }

Rate Limiting NAT Issues

Problem: Users behind NAT hit rate limits too quickly Cause: Multiple users sharing single IP Solution:

Add more characteristics: User-Agent, session cookie, or authorization header

{
  action: 'block',
  expression: 'http.request.uri.path starts_with "/api"',
  action_parameters: {
    ratelimit: {
      characteristics: ['cf.colo.id', 'ip.src', 'http.request.cookies["session"][0]'],
      period: 60,
      requests_per_period: 100,
    },
  },
}

Performance Issues

Problem: Increased latency Cause: Complex expressions, excessive rules Solution:

  1. Skip static assets early: action: 'skip' for \\.(jpg|css|js)$
  2. Path-based deployment: Only run managed on /api or /admin
  3. Disable unused categories: { category: 'wordpress', enabled: false }
  4. Prefer string operators over regex: starts_with vs matches

Limits & Quotas

Resource Free Pro Business Enterprise
Custom rules 5 20 100 1000
Rate limiting rules 1 10 25 100
Rule expression length 4096 chars 4096 chars 4096 chars 4096 chars
Rules per ruleset 75 75 400 1000
Managed rulesets Yes Yes Yes Yes
Rate limit characteristics 2 3 5 5

Important Notes:

  • Rules execute in order; first match wins (except skip rules)
  • Expression evaluation stops at first false in AND chains
  • matches regex operator is slower than string operators
  • Rate limit counting happens before mitigation

API Errors

Problem: API calls fail with cryptic errors Cause: Invalid parameters or permissions Solution:

// Error: "Invalid phase" → Use exact phase name
phase: 'http_request_firewall_custom'

// Error: "Ruleset already exists" → Use update() or list first
const rulesets = await client.rulesets.list({ zone_id, phase: 'http_request_firewall_custom' });
if (rulesets.result.length > 0) {
  await client.rulesets.update({ zone_id, ruleset_id: rulesets.result[0].id, rules: [...] });
}

// Error: "Action not supported" → Check phase/action compatibility
// 'execute' only in http_request_firewall_managed
// Rate limit config only in http_ratelimit phase

// Error: "Expression parse error" → Common fixes:
'ip.geoip.country eq "US"'   // Quote strings
'cf.waf.score gt 40'         // Use 'gt' not '>'
'http.request.uri.path'      // Not 'http.request.path'

Tip: Test expressions in dashboard Security Events before deploying.