# fail2ban The [fail2ban utility](https://en.wikipedia.org/wiki/Fail2ban) provided the inspiration for this plugin. However, this approach isn't limited by simple string matching and supports various actions. It monitors events for failures using `failed()`. When a key exceeds its threshold within `window` seconds, `ban()` is called. Events always pass through downstream regardless of failure status. Once a key is banned (i.e. `ban()` returns a non-null event), it is recorded permanently for the lifetime of the pipeline. Subsequent events matching that key pass through unchanged without calling `ban()` again. The sandbox provides `this.execute(key)` which runs `logbus-fail2ban ` as a subprocess and returns the exit code (0 = success). ## Config **Required:** - `failed`: JS function `(event) => { key, ts, limit } | null`. Return an object if the event represents a failure; return `null` (or any falsy value) to ignore it. All three fields are required — if any field is missing or the wrong type the object is treated as a non-failure and a warning is printed to stderr. - `key`: string that groups related failures (e.g. `"ssh~1.2.3.4"`) - `ts`: event timestamp in milliseconds — used for sliding-window eviction so out-of-order or replayed events are handled correctly - `limit`: number of failures within `window` before `ban()` is called for this key. Set per event so different failure types can have different sensitivity (e.g. `limit: 1` for SSH probes, `limit: 10` for iptables hits) - `ban`: JS function `(event, key) => event | null`. Called when a key exceeds its limit. Return the (possibly mutated) event to permanently ban the key and pass it downstream. Return `null` if the ban could not be applied (e.g. `this.execute()` failed) — the original event passes through unchanged, the key stays unbanned, and `ban()` will be called again on future failures. **Optional:** - `window`: Sliding time window in seconds (default: `60`). Eviction uses the `ts` values returned by `failed()`, not wall clock, so out-of-order or replayed events are handled correctly. ## Example Per-key limits allow for different ban sensitivities (e.g. by process): ```yaml pipeline: fail2ban: config: window: 300 failed: | (event) => { if (event.process === 'sshd') { const m = event.message.match(/invalid user \S+ (?\S+)/u); if (m) return { key: `ssh~${m.groups.ip}`, ts: event.ts, limit: 1 }; } if (event.source && event.event && event.event.dataset === 'iptables') return { key: `iptables~${event.source.ip}`, ts: event.ts, limit: 10 }; return null; } ban: | function(event, key) { if (this.execute(key) !== 0) return null; event._banned = true; return event; } ```