Skip to main content
Verbex uses a Web Application Firewall (WAF) to protect platform APIs and services from common web threats and malicious traffic.

Organization-Specific Protection

  • WAF rules are applied per organization to ensure isolated and customizable protection.
  • Each organization can define its own WAF policy based on its security requirements.

Policy Modes

Organizations can configure their WAF policy in one of the following modes:
  • Default Deny with Allow Rules
    • All traffic is denied by default.
    • Only explicitly allowed requests are permitted.
  • Default Allow with Deny Rules
    • All traffic is allowed by default.
    • Only explicitly denied requests are blocked.
 

Quick Start

1. Configure WAF for Your Organization


# Create WAF configuration (ALLOW mode - blocklist)curl -X POST /v1/waf/config \
  -H "X-User-Org-Id: your-org-id" \
  -H "Content-Type: application/json" \
  -d '{
    "default_policy": "ALLOW",
    "status": "ACTIVE"
  }'

2. Add IP Rules


# Block a malicious IPcurl -X POST /v1/waf/rules \
  -H "X-User-Org-Id: your-org-id" \
  -H "Content-Type: application/json" \
  -d '{
    "ip_address": "198.51.100.50",
    "action": "BLOCK",
    "reason": "Suspicious activity detected"
  }'

3. Check IP Access


# Check if an IP should be blockedcurl -X POST /v1/waf/check \
  -H "X-User-Org-Id: your-org-id" \
  -H "Content-Type: application/json" \
  -d '{"ip_address": "198.51.100.50"}'
Response:

{  "ip_address": "198.51.100.50",  "is_blocked": true,  "action": "BLOCK",  "matched_rule": "rule-id-here"}

Core Concepts

Default Policy

 
PolicyModeBehavior
ALLOWBlocklistAllow all IPs by default, block specific threats
DENYAllowlistBlock all IPs by default, allow specific trusted IPs
 

IP Types

 
TypeFormatExampleDescription
EXACTSingle IP192.168.1.100Match one specific IP
CIDRIP Range192.168.1.0/24Match IP range (256 IPs)
 

CIDR Notation


IP_ADDRESS / PREFIX_LENGTH
 
CIDRSubnet MaskIP RangeTotal IPs
/32255.255.255.255Single IP1
/24255.255.255.0x.x.x.0 - x.x.x.255256
/16255.255.0.0x.x.0.0 - x.x.255.25565,536
/8255.0.0.0x.0.0.0 - x.255.255.25516,777,216
 

Rule Priority (Specificity)

When multiple rules match, the most specific wins:

/32 (exact) > /24 > /16 > /8 (broadest)
Example:
  • Rule 1: BLOCK 10.0.0.0/8
  • Rule 2: ALLOW 10.1.2.3/32
Result: IP 10.1.2.3 is ALLOWED (/32 beats /8)

WAF Status

 
StatusBehavior
ACTIVEWAF rules are enforced
INACTIVEAll IPs allowed (bypass mode for troubleshooting)
 

API Reference

Endpoints

 
EndpointMethodDescription
/v1/waf/checkPOSTCheck if IP should be blocked
/v1/waf/configGETGet WAF configuration
/v1/waf/configPOSTCreate WAF configuration
/v1/waf/configPUTUpdate WAF configuration
/v1/waf/rulesPOSTCreate a rule
/v1/waf/rules/paginatedGETList rules with pagination
/v1/waf/rules/{ruleId}GETGet rule by ID
/v1/waf/rules/{ruleId}PUTUpdate rule
/v1/waf/rules/{ruleId}DELETEDelete rule
/v1/waf/rules/activate-allPOSTActivate all rules
/v1/waf/rules/deactivate-allPOSTDeactivate all rules
/v1/waf/cache/{orgId}DELETEClear cache
/v1/waf/cache/{orgId}/syncPOSTSync cache from database
 

Request Headers

 
HeaderRequiredDescription
X-User-Org-IdYesOrganization identifier
X-User-IDNoUser ID for audit trail
X-Client-IPNoClient IP for auto-rule creation
X-Trace-IdNoRequest correlation ID
 

Data Models

WAF Configuration:
{
  "org_id": "string",
  "default_policy": "ALLOW | DENY",
  "status": "ACTIVE | INACTIVE",
  "created_at": "2026-02-17T10:00:00Z",
  "updated_at": "2026-02-17T10:00:00Z"
}
IP Access Rule:
{
  "id": "string",
  "org_id": "string",
  "ip_address": "192.168.1.100",
  "ip_type": "EXACT | CIDR",
  "action": "ALLOW | BLOCK",
  "reason": "string",
  "is_active": true,
  "expires_at": "2026-03-17T00:00:00Z",
  "notes": "string",
  "created_at": "2026-02-17T10:00:00Z",
  "updated_at": "2026-02-17T10:00:00Z"
}
IP Check Response:
{
  "ip_address": "192.168.1.100",
  "is_blocked": false,
  "action": "ALLOW",
  "matched_rule": "rule-id or null",
  "default_policy": "ALLOW (if no rule matched)"
}

Configuration

Application Properties

waf:
  enabled: true                    # Enable/disable WAF
  default-policy: ALLOW            # System-wide default
  cache-ttl: PT1H                  # Redis cache TTL
  max-rules-per-org: 1000          # Max rules per org
  debug-logging: false             # Verbose logging

  negative-cache:
    enabled: true                  # Cache "no rule" results
    ttl: PT5M                      # Negative cache TTL

  cache-warmup:
    enabled: true                  # Preload on startup
    delay: PT5S                    # Warmup delay
    batch-size: 100                # Orgs per batch

Limits

 
ResourceLimit
Rules per organization1,000
Batch operations100 rules
Pagination max size100 items
 

Usage Examples

Example 1: Blocklist Mode (Default: ALLOW)

Block specific threats while allowing all other traffic.

# 1. Configure ALLOW modecurl -X POST /v1/waf/config \
  -H "X-User-Org-Id: acme-corp" \
  -d '{"default_policy": "ALLOW", "status": "ACTIVE"}'# 2. Block attacker IPcurl -X POST /v1/waf/rules \
  -H "X-User-Org-Id: acme-corp" \
  -d '{"ip_address": "198.51.100.50", "action": "BLOCK", "reason": "Brute force"}'
Result:  
IPRule MatchAccess
203.0.113.10No rule → Default ALLOWAllowed
198.51.100.50BLOCK ruleBlocked
 

Example 2: Allowlist Mode (Default: DENY)

Only allow specific trusted IPs.

# 1. Configure DENY mode (auto-creates rule for your IP)curl -X POST /v1/waf/config \
  -H "X-User-Org-Id: acme-corp" \
  -H "X-Client-IP: 203.0.113.10" \
  -d '{"default_policy": "DENY", "status": "ACTIVE"}'# 2. Allow office networkcurl -X POST /v1/waf/rules \
  -H "X-User-Org-Id: acme-corp" \
  -d '{"ip_address": "192.168.1.0/24", "action": "ALLOW", "reason": "Office network"}'
Result:  
IPRule MatchAccess
203.0.113.10Auto-created ALLOWAllowed
192.168.1.50CIDR ALLOWAllowed
10.0.0.99No rule → Default DENYBlocked
 

Example 3: Temporary Contractor Access


# Create rule with expirationcurl -X POST /v1/waf/rules \
  -H "X-User-Org-Id: acme-corp" \
  -d '{
    "ip_address": "45.67.89.100",
    "action": "ALLOW",
    "reason": "Contractor - Project Alpha",
    "expires_at": "2026-03-17T00:00:00Z",
    "notes": "Contract #CON-2026-042"
  }'
Rule auto-expires and is cleaned up by hourly job.

Example 4: CIDR with Exception

Block a range but allow specific IP within it.

# Block suspicious rangecurl -X POST /v1/waf/rules \
  -H "X-User-Org-Id: acme-corp" \
  -d '{"ip_address": "198.51.100.0/24", "action": "BLOCK", "reason": "Malicious range"}'# Allow trusted partner in that rangecurl -X POST /v1/waf/rules \
  -H "X-User-Org-Id: acme-corp" \
  -d '{"ip_address": "198.51.100.75", "action": "ALLOW", "reason": "Trusted partner"}'
Result: 198.51.100.75 is ALLOWED (/32 beats /24)

Architecture

Multi-Tenant Design

WAF operates at the organization level with complete isolation.

flowchart TB
    subgraph Platform["Platform"]
        subgraph Org1["Organization A"]
            WAF1["WAF Config + Rules"]
        end
        subgraph Org2["Organization B"]
            WAF2["WAF Config + Rules"]
        end
    end

IP Check Flow

Redis Key Patterns

 
KeyTypePurpose
waf:exact:{orgId}HashExact IP rules
waf:cidr:{orgId}HashCIDR rules
waf:config:{orgId}StringDefault policy
waf:nc:{orgId}:{ip}StringNegative cache
 

Storage

  • MongoDB: Source of truth for config and rules
  • Redis: High-performance cache layer

Performance

Response Times

 
ScenarioLatency
Redis hit (exact/CIDR)~1ms
Redis hit (negative cache)~1ms
MongoDB fallback~15ms
 

Negative Cache

Caches “no rule found” results to prevent repeated MongoDB queries. Without negative cache: Every unknown IP → MongoDB query With negative cache: First query → MongoDB, subsequent → Redis (~93% faster)

Cache Invalidation

 
OperationInvalidation Scope
Create exact ruleSingle IP negative cache
Create CIDR ruleAll negative cache for org
Update policyAll negative cache for org
Sync cacheAll cache for org
 

Troubleshooting

Enable Debug Logging


waf:  debug-logging: true
 

Common Issues

 
IssueSolution
Rule not taking effectClear cache: DELETE /v1/waf/cache/{orgId}
Locked out after DENY modeAuto-rule should be created; check with GET /v1/waf/rules
High latencyCheck Redis connectivity; ensure cache warmup completed
Rules not expiringVerify cleanup scheduler is running
 

Bypass WAF for Troubleshooting

Set status to INACTIVE to allow all traffic:

curl -X PUT /v1/waf/config \
  -H "X-User-Org-Id: your-org-id" \
  -d '{"status": "INACTIVE"}'
Remember to re-enable after troubleshooting!

Safety Features

Auto-Rule Creation

When switching to DENY mode, an ALLOW rule is automatically created for the requesting client IP to prevent admin lockout.

Policy Change Protection

Changing between ALLOW and DENY clears existing rules to ensure consistent behavior.