Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
"url": "https://github.com/crowdsecurity"
},
"metadata": {
"description": "CrowdSec skills and tooling — operational automation for installing, configuring, and debugging CrowdSec, plus a Service API skill for the premium Console cloud API."
"description": "CrowdSec skills and tooling \u2014 operational automation for installing, configuring, and debugging CrowdSec, plus a Service API skill for the premium Console cloud API."
},
"plugins": [
{
"name": "crowdsec",
"source": "./",
"description": "CrowdSec skills: an operational skill (engine, cscli, hub, bouncers, WAF/AppSec across bare-metal, Docker, Kubernetes) and a Service API skill (premium Console cloud API — blocklists, allowlists, firewall integrations, metrics, decisions).",
"version": "0.2.3",
"description": "CrowdSec skills: an operational skill (engine, cscli, hub, bouncers, WAF/AppSec and bot detection across bare-metal, Docker, Kubernetes) and a Service API skill (premium Console cloud API \u2014 blocklists, allowlists, firewall integrations, metrics, decisions).",
"version": "0.3.0",
"author": {
"name": "CrowdSec",
"url": "https://github.com/crowdsecurity"
Expand Down
9 changes: 6 additions & 3 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "crowdsec",
"version": "0.2.3",
"version": "0.3.0",
"skills": [
"./skills/crowdsec",
"./skills/crowdsec-service-api"
],
"description": "CrowdSec skills: an operational skill (install/configure/operate/debug the engine, cscli, bouncers, WAF across bare-metal/Docker/Kubernetes) and a Service API skill (drive the premium Console cloud API — blocklists, allowlists, firewall integrations, metrics, decisions).",
"description": "CrowdSec skills: an operational skill (install/configure/operate/debug the engine, cscli, bouncers, WAF and bot detection across bare-metal/Docker/Kubernetes) and a Service API skill (drive the premium Console cloud API \u2014 blocklists, allowlists, firewall integrations, metrics, decisions).",
"author": {
"name": "CrowdSec",
"url": "https://github.com/crowdsecurity"
Expand All @@ -25,6 +25,9 @@
"sapi",
"blocklist",
"integration",
"cloud"
"cloud",
"bot-detection",
"bot-management",
"challenge"
]
}
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- `references/appsec/bot-detection/` — bot detection / AppSec challenge mode (CrowdSec 1.8):
`overview.md` (PoW + fingerprint model, challenge envelope, event vocabulary, bouncer
support), `deploy.md` (bundle choice, glob acquisition, bouncer wiring, four-step smoke
test), `configure.md` (the `challenge:` block, multi-instance `master_secret`, difficulty
levels), `customize.md` (hook/helper matrix, exemptions, `MatchKnownBot` datafiles,
fingerprint scoring, custom scenarios), `troubleshoot.md` (10 symptoms + metrics reading).

### Changed
- `SKILL.md` — bot-detection routing rows, boundary row, trigger phrases in `description`,
and a new **Step 1.6 — Feature compatibility** table holding minimum engine versions and
feature prerequisites in one place.
- `references/appsec/{overview,configure,deploy,troubleshoot}.md` — `challenge` action,
`on_challenge` / `on_challenge_submit` hooks, and pointers into `bot-detection/`.
- `references/configure/bouncers/web-servers.md` — per-bouncer bot-detection capability column.

## [0.2.3] - 2026-05-29

## [0.2.2] - 2026-05-26
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ If you are uncertain about any fact, statistic, date, or piece of technical info
Depth and recipes belong in the reference docs, not in `SKILL.md`.
- **Cover every environment.** Each command or recipe carries its systemd / docker / k8s variant,
matching the existing convention. If some command or recipe is irrelevant to some variant it should be noted.
- **Version-gate in one place.** Minimum engine versions and feature prerequisites live in
`SKILL.md` § Step 1.6 — Feature compatibility, one row per feature. Reference docs describe
behavior and point there; they do not sprinkle "1.8+" through the prose. Adding a
version-gated feature means adding a row in the same change.
- **Anchor to canonical docs.** Each reference doc cites the upstream CrowdSec docs URL it derives
from. Claims trace to canonical documentation, not to memory.

Expand All @@ -49,6 +53,7 @@ All depth lives in `references/<area>/`, organized by the axis that fits the are
| `configure/` | **config domain** | `acquisition`, `hub`, `profiles`, `notifications`, `allowlists`; platforms merged inline. `configure/bouncers/` nests one level by **service type** (`firewall`, `web-servers`). |
| `operate/` | **task** | `health-check`, `upgrades`, `multi-server`. |
| `appsec/` | **lifecycle** | `overview` → `deploy` → `configure` → `troubleshoot` (the WAF/AppSec feature silo). |
| `appsec/bot-detection/` | **lifecycle** | `overview` → `deploy` → `configure` → `customize` → `troubleshoot`. Bot detection (challenge mode) ships inside the AppSec component but is its own feature with its own install, config and failure modes, so it gets its own silo and its own routing rows — not a section of the WAF docs. `customize` is the extra step: authoring exemptions and scoring rules is the bulk of the work. |
| `debug/` | **kind** | `common/` (`triage`, `errors`, `platform-gotchas`) + `symptoms/` (`parsing`, `no-alerts`, `not-blocked`). Feature troubleshooting is *routed to* the feature's own dir (e.g. AppSec → `appsec/troubleshoot.md`), not duplicated under debug/. |
| `migrate/` | **source product** | `from-fail2ban`. |
| `scripts/` | — | helper scripts (`diagnose.sh`, `check-verification.py`); stdlib/bash only, runnable in static checks. |
Expand Down
19 changes: 18 additions & 1 deletion skills/crowdsec/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: crowdsec
description: Use when the user is installing, configuring, operating, or debugging CrowdSec — including cscli, LAPI/CAPI, hub collections, parsers/scenarios/whitelists deployment, bouncers (firewall, nginx, traefik, caddy), WAF (AppSec component) deployment, profiles, notifications, upgrades, and fail2ban migration. Covers bare-metal/systemd, Docker, Kubernetes/Helm, and CrowdSec Console enrollment. This is an operational skill — it does not author WAF rules, scenarios, or parsers.
description: Use when the user is installing, configuring, operating, or debugging CrowdSec — including cscli, LAPI/CAPI, hub collections, parsers/scenarios/whitelists deployment, bouncers (firewall, nginx, traefik, caddy), WAF (AppSec component) deployment, bot detection / AppSec challenge mode (blocking headless browsers and scrapers, allowing verified crawlers), profiles, notifications, upgrades, and fail2ban migration. Covers bare-metal/systemd, Docker, Kubernetes/Helm, and CrowdSec Console enrollment. This is an operational skill — it does not author WAF rules, scenarios, or parsers.
---

# CrowdSec — operations, deployment, configuration, and debugging
Expand All @@ -17,6 +17,7 @@ thing. This skill uses both interchangeably.
| Configure acquisition, hub, profiles, notifications | this skill |
| Install and wire a bouncer (firewall, nginx, traefik, caddy) | this skill |
| Deploy the WAF (AppSec component) | this skill |
| Deploy and tune bot detection (AppSec challenge mode) | this skill |
| Debug "logs not parsing" / "no alerts" / "bouncer not blocking" | this skill |
| Migrate from fail2ban | this skill |
| **Write** a parser, scenario, or WAF (AppSec) rule | the `crowdsec-local-mcp` mcp |
Expand Down Expand Up @@ -96,6 +97,20 @@ onto the official repo first: [references/operate/upgrades.md](./references/oper
Hub. If it's far behind `version.crowdsec.net/latest`, pull a newer tag
([references/operate/upgrades.md](./references/operate/upgrades.md) happy path).

## Step 1.6 — Feature compatibility

Some features only exist past a given engine version, and a few also need a capable bouncer.
Check here before debugging "the config is right but nothing happens" — on an older engine the
answer is an upgrade, not a config change.

| Feature | Min engine | Also needs | Docs |
|---|---|---|---|
| Bot detection (AppSec challenge mode) — *alpha* | **1.8.0** | A bot-detection-capable bouncer (nginx, OpenResty, HAProxy SPOA, Traefik, Envoy) and a host that can run WASM in compiler mode | [references/appsec/bot-detection/](./references/appsec/bot-detection/) |
| Glob patterns in `appsec_configs` | **1.8.0** | — | [references/appsec/bot-detection/deploy.md](./references/appsec/bot-detection/deploy.md) § 2 |

Version-gated behaviour is recorded here rather than repeated in each reference doc — when a
feature is added, give it a row instead of sprinkling "x.y+" through the prose.

## Step 2 — Detect the intent

| Cue from user | Go to |
Expand All @@ -117,6 +132,7 @@ Hub. If it's far behind `version.crowdsec.net/latest`, pull a newer tag
| "caddy bouncer", "caddy module / xcaddy" | [references/configure/bouncers/web-servers.md](./references/configure/bouncers/web-servers.md) § Caddy |
| "wrong source IP", "real client IP", "behind Cloudflare / reverse proxy / NPM", "X-Forwarded-For", "everyone shows as the proxy IP" | [references/configure/bouncers/web-servers.md](./references/configure/bouncers/web-servers.md) — per-bouncer real-IP/trusted-proxy sections |
| "AppSec", "WAF", "virtual patching", "block by request shape" | [references/appsec/](./references/appsec/) — overview, deploy, configure, troubleshoot |
| "bot detection", "challenge mode", "JS challenge", "proof of work", "block headless browsers / puppeteer / selenium / scrapers", "let Googlebot through" | [references/appsec/bot-detection/](./references/appsec/bot-detection/) — overview, deploy, configure, customize, troubleshoot |
| "Console", "enroll", "share signals" | [references/install/console.md](./references/install/console.md) |
| "upgrade", "back up", "roll back", "new version", "tainted items after upgrade" | [references/operate/upgrades.md](./references/operate/upgrades.md) |
| "old/outdated version", "`cscli` command or flag missing", "hub item won't install", "behavior doesn't match the docs", "installed from the distro package" | [references/operate/upgrades.md](./references/operate/upgrades.md) § Detect & fix an outdated / distro-packaged install (see **Step 1.5** above) |
Expand All @@ -131,6 +147,7 @@ Hub. If it's far behind `version.crowdsec.net/latest`, pull a newer tag
| **Debug — by symptom** · "decision exists but not blocked" | [references/debug/symptoms/not-blocked.md](./references/debug/symptoms/not-blocked.md) |
| **Debug — by symptom** · "bouncer blocks everything", "locked out of all services", "every request 403 after adding the bouncer" | [references/debug/symptoms/not-blocked.md](./references/debug/symptoms/not-blocked.md) § 7 — Inverse symptom |
| **Debug — by feature** · AppSec/WAF not blocking, false positives, captcha | [references/appsec/troubleshoot.md](./references/appsec/troubleshoot.md) |
| **Debug — by feature** · challenge never served, endless challenge loop, real crawlers being challenged, everything blocked after enabling bot detection | [references/appsec/bot-detection/troubleshoot.md](./references/appsec/bot-detection/troubleshoot.md) |
| "switch from fail2ban" | [references/migrate/from-fail2ban.md](./references/migrate/from-fail2ban.md) *(TODO — stub)* |

For anything debug-shaped, the first move is almost always:
Expand Down
88 changes: 88 additions & 0 deletions skills/crowdsec/references/appsec/bot-detection/configure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
verified:
- date: 2026-08-31
version: "1.8.0"
env: systemd
notes: "challenge: block via overlay — master_secret silences the warning, cookie_ttl 2h applied; difficulty levels + invalid-level error"
---

# Challenge runtime configuration

Canonical: <https://docs.crowdsec.net/docs/next/appsec/bot_detection/configuration>

`challenge:` is a **top-level key in any appsec-config** (sibling of `name:` and `inband:`).
Every loaded appsec-config contributes, merged field by field, last non-nil winning. Never edit
hub files — ship an overlay and list it by name in `appsec_configs:`
([./deploy.md](./deploy.md) § 2):

```yaml
# /etc/crowdsec/appsec-configs/mycorp-overlay.yaml
name: mycorp/appsec-bot-challenge-overlay

challenge:
master_secret: "<64+ hex chars; generate with: openssl rand -hex 32>"
cookie_ttl: 2h
Comment thread
Copilot marked this conversation as resolved.
```

| Key | Default | Purpose |
|---|---|---|
| `master_secret` | random, ephemeral | Root of every derived signing key and the cookie-sealing key. Hex ≥64 chars, or passphrase ≥32 bytes. |
| `key_rotation_interval` | `5m` | Per-epoch signing key advance. Minimum 30s. |
| `max_live_epochs` | `3` | Past epochs still accepted. A client has `(max_live_epochs + 1) × key_rotation_interval` to solve. |
| `cookie_ttl` | `12h` | Success-cookie lifetime. Sealed under a separate long-lived key, so key rotation does not invalidate issued cookies. |
| `crypto_obfuscation_pool_size` | `1` | Obfuscation variants of the per-epoch key module. ~5s CPU per variant per rotation. |
| `max_cookie_size` | `4096` | Byte cap on the sealed cookie, enforced on seal and open. Bounds an over-allocation DoS. |
| `spent_set_max_entries` | `1000000` | Replay-protection LRU — a solved challenge cannot be resubmitted. |
| `log_level` | inherits global | Challenge runtime only — [./troubleshoot.md](./troubleshoot.md) § 8. |

Resolved settings are logged at startup:

```
level=info msg="WAF challenge runtime initialized" cookie_ttl=2h0m0s crypto_pool_size=1 max_cookie_len=4096 module=challenge pow_difficulty=20 rotation_interval=5m0s
```

## Multi-instance

Without `master_secret`, each instance generates a random one at startup and rejects the
others' cookies, so clients re-challenge on every request landing elsewhere; restarts do the
same on a single node (the engine warns — see [./troubleshoot.md](./troubleshoot.md) § 4).
Behind a load balancer or on more than one AppSec instance, set it with `openssl rand -hex 32`,
and set `key_rotation_interval` and `max_live_epochs` identically everywhere, since keys derive
from both.

Rotation: roll the new value to every instance inside one `cookie_ttl` window, then restart
each. Clients holding old cookies are challenged once more, not blocked.

| Change | Invalidates |
|---|---|
| `master_secret` | Issued cookies **and** in-flight challenges |
| `key_rotation_interval`, `max_live_epochs` | In-flight challenges only |
| `cookie_ttl` | Nothing — applies to newly issued cookies |
| `crypto_obfuscation_pool_size` | Nothing — applies at the next rotation tick |

Treat the secret as a credential: a Kubernetes `Secret` projected into the log-processor's
appsec-config, or a Docker secret / bind-mounted file.

## Difficulty

PoW cost in leading zero bits, calibrated for a low-end 2-core phone; desktops are ~20× faster.

| Level | Bits | Low-end mobile | Desktop |
|---|---|---|---|
| `disabled` | 0 | instant | instant |
| `low` | 18 | ~0.5s | ~0.03s |
| `medium` **(default)** | 20 | ~2s | ~0.10s |
| `high` | 22 | ~8s | ~0.41s |
| `impossible` | 256 | never solvable — hard block, rejected server-side, no reason leaked | |

No `challenge:` key sets the default level; set it per request with
`SetChallengeDifficulty("high")` in `pre_eval` or `on_challenge`
([./customize.md](./customize.md)). An unknown level is a per-request runtime error, not a
startup failure, and the request is still challenged at the default:

```
level=error msg="unable to apply appsec pre_eval[inband] expr: unknown challenge difficulty \"extreme\" (expected disabled, low, medium, high, or impossible)"
```

Difficulty is a weak deterrent — a scraper has more CPU than a phone. Use it to slow abuse of a
specific expensive route; rely on fingerprint scoring to identify bots.
Loading
Loading