WAF MCP Server
by KratosUAE
README.md
# WAF MCP Server
An MCP (Model Context Protocol) server for managing [OWASP ModSecurity CRS](https://coreruleset.org/) via Docker. Gives AI assistants like Claude direct access to WAF monitoring, analysis, and configuration through a structured drill-down pipeline.
Built for [Claude Code](https://claude.com/claude-code) but works with any MCP-compatible client.
## Why
LLM proxy services (LiteLLM, OpenRouter, etc.) sit behind WAFs that generate massive amounts of false positives — prompts contain code, SQL, HTML, shell commands that trigger every content-inspection rule in the book. Managing these WAFs requires constant monitoring, tuning exclusions, and investigating events.
This MCP server lets an AI assistant do that work directly:
1. **Overview** — see total events, unique IPs, active rules at a glance
2. **Drill down** — filter events by IP or rule, inspect matched data
3. **Act** — disable rules, whitelist IPs, change engine mode — all without leaving the conversation
## Tools
### Analysis (drill-down pipeline)
| Tool | Description |
|------|-------------|
| `waf_overview` | Dashboard: total events, unique IPs/rules, events last hour |
| `waf_top_ips` | Top IPs by event count with geo enrichment (ipinfo.io) |
| `waf_top_rules` | Most triggered rules with severity and description |
| `waf_fp_candidates` | Rules that fired on HTTP 2xx responses (false positive candidates) |
| `waf_events_by_ip` | Events filtered by source IP |
| `waf_events_by_rule` | Events filtered by rule ID |
| `waf_event_detail` | Full event: headers, request body, all rule matches with matched data |
### Actions
| Tool | Description |
|------|-------------|
| `waf_status` | Container health, engine mode, rules loaded, paranoia level |
| `waf_set_engine` | Switch between `On`, `Off`, `DetectionOnly` |
| `waf_set_paranoia` | Set CRS paranoia level (1–4) |
| `waf_disable_rule` | Disable a rule by ID (adds `SecRuleRemoveById` to exclusions) |
| `waf_enable_rule` | Re-enable a previously disabled rule |
| `waf_allow_ip` | Whitelist an IP (bypass WAF entirely) |
| `waf_deny_ip` | Remove an IP from whitelist |
| `waf_test` | Run test suite: scanner detection, SQLi, XSS, path traversal |
### Common parameters
**`since`** — All analysis tools accept a `since` parameter to control the time window. Default is `"24h"`. Supports Docker duration syntax: `"1h"`, `"24h"`, `"7d"`, `"30m"`. Days are automatically converted to hours (Docker's `--since` doesn't support the `d` suffix natively).
```
waf_overview(since: "7d") # last 7 days
waf_events_by_ip(ip: "1.2.3.4", since: "1h") # last hour
```
**`verbose`** — `waf_events_by_ip`, `waf_events_by_rule`, and `waf_event_detail` accept `verbose: true`. By default, `matchedData` and `requestBody` are truncated to keep responses within context limits:
| Field | Default | Verbose |
|-------|---------|---------|
| `matchedData` (per rule) | 150–200 chars | 4000 chars |
| `requestBody` | 500 chars | 8000 chars |
## Prerequisites
- **Docker** with a running [owasp/modsecurity-crs](https://hub.docker.com/r/owasp/modsecurity-crs) container
- **Docker Compose** managing the ModSecurity container
- **Node.js** 18+
- ModSecurity configured with **JSON Serial audit log** (`SecAuditLogFormat JSON`)
## Installation
```bash
git clone https://github.com/KratosUAE/waf_mcp.git
cd waf_mcp
npm install
npm run build
```
## Configuration
### Environment variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `WAF_COMPOSE_DIR` | **Yes** | — | Path to directory containing `docker-compose.yml` |
| `WAF_DOMAIN` | No | `https://localhost` | Domain for WAF test requests |
| `WAF_LOGS_SINCE` | No | `24h` | Default time window for log queries |
| `WAF_CONTAINER_PATTERN` | No | `modsecurity` | Grep pattern to find the ModSecurity container |
| `WAF_EXCLUSIONS_FILE` | No | `modsecurity/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf` | Path to CRS exclusions file (relative to compose dir) |
| `WAF_COMPOSE_FILE` | No | `docker-compose.yml` | Docker Compose filename |
| `IPINFO_TOKEN` | No | — | [ipinfo.io](https://ipinfo.io) token for IP geolocation |
| `WAF_DEBUG` | No | — | Set to any value to enable debug logging |
### Connect to Claude Code
```bash
claude mcp add --transport stdio --scope user \
-e WAF_COMPOSE_DIR=/path/to/your/compose/dir \
-e WAF_DOMAIN=https://your-domain.com \
waf -- node /path/to/waf_mcp/dist/index.js
```
Or manually add to `~/.claude.json`:
```json
{
"mcpServers": {
"waf": {
"type": "stdio",
"command": "node",
"args": ["/path/to/waf_mcp/dist/index.js"],
"env": {
"WAF_COMPOSE_DIR": "/path/to/your/compose/dir",
"WAF_DOMAIN": "https://your-domain.com"
}
}
}
}
```
### Docker Compose setup
The server expects a ModSecurity container managed by Docker Compose. Example service definition:
```yaml
modsecurity:
image: owasp/modsecurity-crs:nginx-alpine
environment:
- BACKEND=http://your-app:8080
- MODSEC_RULE_ENGINE=DetectionOnly
- MODSEC_AUDIT_LOG=/dev/stderr
- MODSEC_AUDIT_LOG_FORMAT=JSON
- MODSEC_AUDIT_LOG_TYPE=Serial
- MODSEC_AUDIT_ENGINE=RelevantOnly
- MODSEC_REQ_BODY_ACCESS=On
- MODSEC_REQ_BODY_LIMIT=52428800
- MODSEC_RESP_BODY_ACCESS=Off
- PARANOIA=1
- ANOMALY_INBOUND=5
volumes:
- ./modsecurity/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:/etc/modsecurity.d/owasp-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:ro
```
Key settings:
- **`MODSEC_AUDIT_LOG=/dev/stderr`** — sends audit log to Docker logs (required for the MCP server to read events)
- **`MODSEC_AUDIT_LOG_FORMAT=JSON`** — JSON format for structured parsing
- **Exclusions file mount** — allows hot-reload of rule exclusions via `nginx -s reload`
### CRS exclusions for LLM traffic
LLM API endpoints receive prompts containing code, SQL, HTML, and shell commands — all legitimate content that triggers WAF rules. Create an exclusions file to disable content-inspection rules on API paths:
```apache
# modsecurity/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
SecRule REQUEST_URI "@rx ^(/v1/)?(chat/completions|completions|embeddings|responses|messages)|^/anthropic/" \
"id:1000,phase:1,nolog,pass,\
ctl:ruleRemoveById=921000-944999"
```
This disables rules 921000–944999 (all content-inspection categories: SQLi, XSS, RCE, LFI, RFI, etc.) on LLM API endpoints while keeping protocol enforcement, scanner detection, DoS protection, and IP reputation checks active.
## Usage example
Typical workflow in Claude Code:
```
You: "Check the WAF — anything suspicious?"
Claude: [calls waf_overview]
→ 332 events, 4 unique IPs, 12 rules triggered
Claude: [calls waf_top_ips]
→ 135.237.83.23 (Washington, US, Microsoft) — 320 events
Claude: [calls waf_events_by_ip, ip: "135.237.83.23", count: 5]
→ All POST /chat/completions, HTTP 200, rules: 942360, 932100...
Claude: [calls waf_event_detail, index: 42]
→ User-Agent: OpenAI/JS 6.26.0, body contains tool descriptions
→ Rule 942360 matched "update" in cron action descriptions
Claude: "This is your OpenClaw bot — all false positives.
Want me to whitelist this IP?"
You: "Yes"
Claude: [calls waf_allow_ip, ip: "135.237.83.23"]
→ Done. IP whitelisted.
```
Investigating older events:
```
You: "Check IP 185.206.249.230 — it was flagged yesterday"
Claude: [calls waf_events_by_ip, ip: "185.206.249.230", since: "7d"]
→ 2 events from Apr 7, GET /v1/skills, HTTP 401, no rules triggered
→ Apple Private Relay IP (Singapore), just unauthorized API probes
```
## Development
```bash
npm run build # Compile TypeScript
npm test # Run tests (43 tests)
npm run test:watch # Watch mode
WAF_DEBUG=1 npm start # Run with debug logging
```
## Architecture
```
src/
├── index.ts # MCP server setup, tool registration
├── waf-manager.ts # Core service: Docker exec, log parsing, config management
├── types.ts # TypeScript interfaces
├── config.ts # Environment-based configuration
├── logger.ts # stderr-only logger (stdout reserved for MCP protocol)
└── tools/
├── overview.ts # L0: dashboard
├── top-ips.ts # L1: IP aggregation
├── top-rules.ts # L1: rule aggregation
├── fp-candidates.ts # L1: false positive detection
├── events-by-ip.ts # L2: drill-down by IP
├── events-by-rule.ts # L2: drill-down by rule
├── event-detail.ts # L3: full event inspection
├── status.ts # Container status
├── set-engine.ts # Engine mode control
├── set-paranoia.ts # Paranoia level control
├── disable-rule.ts # Rule management
├── enable-rule.ts # Rule management
├── allow-ip.ts # IP whitelist
├── deny-ip.ts # IP whitelist
├── test.ts # WAF test suite
└── utils.ts # Shared utilities
```
Events are parsed from Docker logs and cached for 30 seconds. Rapid drill-down calls (overview → top IPs → events by IP → event detail) hit the cache instead of re-parsing. The cache is invalidated when the `since` parameter changes.
## License
MIT
TDQS
A4/5.0
Scored across 15 tools
Disambiguation5/5
Each tool has a clearly distinct purpose: IP management, rule toggling, event analysis, engine settings, and testing. No two tools overlap in functionality.
Naming Consistency5/5
All tools follow a consistent 'waf_verb_noun' pattern, making it easy to predict what each tool does based on its name.
Tool Count5/5
15 tools is an appropriate number for a WAF management server, covering operations, monitoring, and tuning without being overwhelming.
Completeness5/5
The tool set covers the full lifecycle of WAF management: IP whitelisting/blacklisting, rule enable/disable, engine mode, paranoia levels, event drilling, false positive detection, and testing. No obvious gaps.
Maintenance
ActivityInactive
ResponsivenessNo issues