> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dnzwithazee.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Web application firewall

> Stop SQL injection, XSS, path traversal, Log4Shell and attack tools at the proxy with ready-made rules, before your servers see them.

A route's **Web application firewall** checks every request against ready-made rules for
well-known attacks: SQL injection, cross-site scripting (XSS), path traversal, command injection,
Log4Shell, attack tools and the like. A [DNZ Proxy](https://docs.dnzwithazee.com/proxy/dnz-proxy.md) blocks a match with a `403`
page, or only logs it, before your servers see the request.

It is off until you turn it on for a route, and it needs a DNZ Proxy. The
[Firewall](https://docs.dnzwithazee.com/proxy/dnz-proxy.md#firewall-dnzfirewall) holds your own rules; these are DNZ's, the same
for every route. For one known vulnerability in an app you can't upgrade, see
[Shields](https://docs.dnzwithazee.com/proxy/shields.md).

## Turn it on

**1. Open the route**

On the proxy's page, open the route (**Routes** → the route).

**2. Switch on Web application firewall**

Pick **Block attacks** or **Log only**, and a **Sensitivity**. **Medium** is the usual choice.

**3. Save**

Every node has it within seconds.

Or from the terminal:

```bash theme={null}
dnz proxy routes edit edge app.example.com --waf block
```

> [!TIP]
>
> Not sure it suits your site? Start with **Log only** (`--waf log`): nothing is blocked, and every
> match shows in Analytics and the request log. Switch to **Block attacks** once only attacks show up.

## What it checks

* the path,
* every query parameter, its name and its value,
* the `User-Agent` and `Referer` headers and the cookies' values.

Each is decoded first, the way attacks hide: percent-encoding (twice), `+` as a space, HTML
entities such as `&lt;`, and SQL comments (`UNION/**/SELECT`). Case doesn't matter.

> [!WARNING]
>
> The request **body isn't checked yet**: a form POST or a JSON body goes through unseen. For
> uploads, [Upload limits](https://docs.dnzwithazee.com/proxy/upload-limits.md) check what multipart bodies may hold, and your own
> [Firewall](https://docs.dnzwithazee.com/proxy/dnz-proxy.md#firewall-dnzfirewall) rules can refuse methods or paths that should take
> no body at all.

## Sensitivity

| Sensitivity | Rules | What it means |
| - | - | - |
| **Low** | 23 | Clear attacks only. Almost never blocks a real visitor |
| **Medium** (default) | 37 | The usual choice: more attacks caught, rarely a real visitor |
| **High** | 43 | Catches the most, but may block unusual normal requests (any HTML tag in a parameter, `SELECT … FROM`, line breaks). Try it with **Log only** first |

Each rule starts at one sensitivity and runs at every higher one:

| Category | Low and up | Medium and up | High |
| - | - | - | - |
| SQL injection | `sqli-union`, `sqli-tautology`, `sqli-stacked`, `sqli-time`, `sqli-schema` | `sqli-tautology-number`, `sqli-functions`, `sqli-comment` | `sqli-select-from` |
| Cross-site scripting (XSS) | `xss-script`, `xss-event-handler`, `xss-js-call` | `xss-js-uri`, `xss-handler-attribute`, `xss-tags`, `xss-dom`, `xss-data-uri` | `xss-alert`, `xss-any-tag` |
| Path traversal | `traversal-deep`, `traversal-system-files` | `traversal` | |
| Sensitive files | `files-secrets` | `files-backups` | |
| Remote file inclusion | `rfi-wrapper` | `rfi-file-uri` | `rfi-script-url` |
| Command injection | `rce-log4shell`, `rce-substitution`, `rce-commands` | `rce-chained`, `rce-windows` | |
| PHP code injection | `php-tag`, `php-functions` | `php-decoders` | |
| Java code injection | `java-injection` | | |
| Server-side request forgery | `ssrf-metadata`, `ssrf-schemes` | | `ssrf-local` |
| Attack tools | `scanner` | | |
| Protocol attacks | `protocol-null-byte`, `protocol-header-injection` | | `protocol-newline` |

`dnz proxy waf rules` lists every rule with what it catches.

## Block or log only

* **Block attacks** (default): the visitor gets an **Access blocked** page with the `403` status,
  saying the site's firewall stopped the request, and a request ID to send you (HTML, JSON or text,
  like the [status pages](https://docs.dnzwithazee.com/proxy/status-pages.md)). The request never reaches your servers.
* **Log only**: the request goes on as usual; the request log notes `log waf:<rule>`.

## See what it matched

* **Proxy → WAF**: everything in one place, with a world map of where attacks came from, each event,
  an address's place and network, blocking and one-click exceptions. See [The WAF page](https://docs.dnzwithazee.com/proxy/waf-page.md).
* **Proxy → Analytics → Web application firewall**: blocked and logged requests, over time, the rules
  that matched (`SQL injection · sqli-union`, Blocked or Logged), and the addresses and paths it matched
  most. Click an address or a path to show its requests in the request log. It follows the page's
  proxy, hostname and time range.
* The **Routes** list: the WAF chip says what it did on the route's hostnames in the last 24 hours,
  such as `WAF · 12 blocked` or `WAF · log only · 3`.
* The **request log** (Proxy → Analytics) shows the rule on each line, such as
  `WAF blocked · SQL injection · sqli-union`. Open the line to see what the rule catches.
* Analytics' **Firewall rules** list counts the rules that matched; blocked requests count as blocked.
* `dnz proxy analytics` prints the blocked and logged counts with the top rules and addresses.
* `dnz proxy routes waf-hits edge` lists matches since each node started, per rule and action.
* Prometheus: `dnz_proxy_waf_hits_total{proxy, node, middleware, rule, action}`, and for coverage and
  the page's blocks `dnz_proxy_waf_routes{proxy, state}` and `dnz_proxy_waf_page_blocks{kind}`.
* AI assistants: the MCP tools `proxy_waf_rules`, `proxy_waf_hits` and, with write access, `set_waf`.

Thinking of a higher sensitivity? [Preview it](https://docs.dnzwithazee.com/proxy/waf-page.md#preview-a-higher-sensitivity) first: the
extra rules only count, and the page says what they would also have stopped.

## Exceptions

Under **Exceptions** in the route dialog:

| Exception | For | CLI |
| - | - | - |
| **Skip these paths** (and below them) | Pages where people send code or markup on purpose, such as a CMS editor | `--waf-skip-path /admin/editor` |
| **Skip these rules** | A rule that trips on a normal request of your site; the rest keep checking | `--waf-skip-rule xss-tags` |
| **Never check these addresses** | Your office, or a security scanner you run yourself | `--waf-allow 10.0.0.0/8` |

Paths are plain prefixes: `/admin` also covers `/administrator`. Skipping one rule is safer than
turning the whole firewall off.

## When a real visitor gets blocked

**1. Find the request**

In **Proxy → WAF**, find the event (filter by the address or hostname) and use **Not an
attack…**: it adds the exception for you ([The WAF page](https://docs.dnzwithazee.com/proxy/waf-page.md#when-it-wasnt-an-attack)).
Or in **Proxy → Analytics → Web application firewall**, click the visitor's address (or the path)
to show its requests in the request log, or filter the log by the hostname and the address. The
line names the rule; **Copy rule id** copies it.

```bash theme={null}
dnz proxy logs --host app.example.com --client 198.51.100.7 --status 403
```

**2. Pick the narrowest exception**

Skip the rule when a normal page trips it, the path when one page takes code or markup on
purpose, or the address when it is your own tool.

**3. Add it**

In the route dialog (**Web application firewall → Exceptions**), or:

```bash theme={null}
dnz proxy routes edit edge app.example.com --waf-skip-rule xss-tags
```

**4. Watch**

Not sure what else it stops? Switch the route to **Log only** for a while, check the hits, then
go back to **Block attacks**.

## Where it runs

It runs right after the route's address list and your own Firewall, so your rules decide first. A
Firewall **Allow** rule doesn't skip these rules: use **Never check these addresses** instead.

The rules are compiled once on each node (per sensitivity) and shared by every route. A request
that matches nothing, nearly all of them, costs one pass of the rules over what it sent.

## What it costs

Next to nothing for ordinary requests (−1 to −3% requests a second on one CPU); a request carrying many query parameters and cookies costs about 13 µs more CPU (−13%). +2.3 MiB of memory per node at Medium, +4.2 MiB at High. Details in [Proxy benchmarks](https://docs.dnzwithazee.com/proxy/benchmarks.md).

## Traefik and Caddy

Traefik and Caddy proxies have no web application firewall: a route with it is refused there. A
route saved on several proxies gets it on the DNZ Proxies only.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.