# ConfigurationEvery knob the daemon exposes — environment variables, file paths, and integration points.

The daemon is configured entirely through environment variables and
two on-disk files. No code change is required to tune behaviour.

## Environment variables

| Variable | Type | Default | Purpose |
|---|---|---|---|
| `ABUSIVE_FAMILY` | `v4` / `v6` / `any` | `any` | Restrict matching to one IP family. |
| `ABUSIVE_MIN_HITS` | integer ≥ 1 | `3` | Hits from one source before the IP is written to the blocklist. |
| `ABUSIVE_SCOPE` | comma-list | `request,ua` | Which log fields to evaluate. Valid: `request`, `ua`, `referrer`. |
| `ABUSIVE_REFERRER_DOMAINS` | whitespace/comma list | _(empty)_ | Allowed referrer hosts. Only used if `referrer` is in `ABUSIVE_SCOPE`. |
| `ABUSIVE_SYSLOG_TAG` | string | `abusive_http_watch` | Syslog program tag. |
| `ABUSIVE_SYSLOG_FACILITY` | string | `daemon` | Syslog facility. Use `local0`–`local7` for custom routing. |
| `ABUSIVE_SYSLOG_PRIO` | `info` / `notice` / `warn` / `err` | `notice` | Syslog priority for `added` events. |
| `ABUSIVE_DEBUG` | `0` / `1` | `0` | Verbose stderr trace of every line. Useful only during scoping. |

### Notes

- **`ABUSIVE_MIN_HITS`** — Setting this to `1` blocks on the first
  hit. That is more aggressive but increases the false-positive
  risk from one-shot probes that happen to traverse a benign cloud
  IP later. The default of `3` is a good compromise.
- **`ABUSIVE_SCOPE`** — `referrer` is off by default because a
  malicious referrer can put your visitor's IP into the blocklist
  via a drive-by. Only enable `referrer` if you also set
  `ABUSIVE_REFERRER_DOMAINS` to a strict allowlist.
- **`ABUSIVE_FAMILY`** — Useful when you want to mirror the daemon
  across two processes: one for IPv4 and one for IPv6, each writing
  to its own blocklist file.

## Token set

The detection regex set is hardcoded in the script (`@TOKENS`) and
covers eight attack classes — PHP exploitation, WordPress probes,
path traversal, sensitive paths, RCE/CGI attempts, SQL injection,
login-loop probes, and null-byte injection.

To tune the set:

1. Open the script.
2. Adjust the `@TOKENS` array — add patterns, remove ones that fire
   false positives in your environment.
3. Restart the daemon.

The token set is deliberately conservative. Adding tokens like
`/admin`, `/login`, or `phpmyadmin` without scoping will cause false
positives on legitimate traffic.

## Whitelist file

Path: `/etc/abusive_http_whitelist`

One entry per line. Three formats are supported:

```
# Comments allowed
203.0.113.42                    # single IPv4
2001:db8::abcd                  # single IPv6 (full or shortened)
203.0.113.0/24                  # IPv4 CIDR
2001:db8::/32                   # IPv6 CIDR
```

Whitelisted IPs are skipped completely — they never increment hit
counters and never end up in the blocklist, even if their requests
match every token in the set.

Use the whitelist for:

- Office and VPN public IPs (operators triggering scanners by
  accident).
- Search-engine crawler IP ranges that are documented to be
  legitimate (Google, Bing). Test before adding — the whitelist is
  rarely needed for crawlers because their requests do not match
  attack tokens.
- Monitoring services that probe `/wp-login.php` deliberately.

## Blocklist file

Path: `/var/www/run/abusive_http_hosts`

Format: one IP per line, no comments, no headers, no duplicates.
The daemon enforces de-duplication via an in-memory hash and
`flock(LOCK_EX)` on writes.

The file is **append-only at runtime** — old entries are never
removed by the daemon. To prune (e.g. expire entries after 30
days), use external rotation:

```sh
# In a daily cron
mv /var/www/run/abusive_http_hosts /var/www/run/abusive_http_hosts.old
touch /var/www/run/abusive_http_hosts
chown _www:_www /var/www/run/abusive_http_hosts
doas rcctl restart abusive_http_watch
```

This forces the daemon to rebuild its in-memory state from an
empty file. The firewall reloads the new (empty) blocklist on its
next cron tick.

## Firewall consumption

The blocklist file format is intentionally flat so that any layer
can consume it. Common integrations:

### OpenBSD `pf`

```pf
table <abusive> persist file "/var/www/run/abusive_http_hosts"
block in quick on egress from <abusive>
```

Reload table:

```sh
pfctl -t abusive -T replace -f /var/www/run/abusive_http_hosts
```

### Linux `nftables`

```nft
table inet filter {
  set abusive_v4 {
    type ipv4_addr
    elements = { ... }
  }
  chain input {
    ip saddr @abusive_v4 drop
  }
}
```

Reload set from file via a small helper:

```sh
nft 'flush set inet filter abusive_v4'
awk '/^[0-9]/ {print}' /var/www/run/abusive_http_hosts | \
  xargs -I{} nft 'add element inet filter abusive_v4 { {} }'
```

### Apache

```apache
<RequireAll>
  Require all granted
  Require not ip 203.0.113.0/24
  # … one Require not ip per blocklist entry
</RequireAll>
```

Generated by a templater (`m4`, `sed`) from the blocklist file on a
cron. Slower to react than firewall integration, but works when you
do not own the firewall layer.

### nginx

```nginx
geo $abusive {
  default 0;
  include /var/www/run/abusive_http_nginx.conf;
}
server {
  if ($abusive) { return 444; }
}
```

Where `abusive_http_nginx.conf` is generated by a template from the
blocklist file.
