Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c02ebbf
add wip scenarios for challenge mode
blotus Jun 1, 2026
f07f5b7
update appsec parser for challenge events
blotus Jun 1, 2026
612b0e0
add basic appsec-config for challenge
blotus Jun 2, 2026
7a2e15f
bundle files in a collection, exclude well-known paths, split parsers…
buixor Jun 15, 2026
47e1b42
rename to make CI happy
buixor Jun 15, 2026
6e0a72a
Update index
actions-user Jun 15, 2026
dc8dac3
rename to make CI happy
buixor Jun 15, 2026
85db8a9
Update index
actions-user Jun 15, 2026
e1b6796
rely on existing methods instead
buixor Jun 16, 2026
7a3a818
Update index
actions-user Jun 16, 2026
dcd5f05
reflect helper change
buixor Jun 18, 2026
5fe9fdd
reflect helper change
buixor Jun 18, 2026
54eb693
add the appsec-rules tracking data files
buixor Jun 19, 2026
10943ab
Update index
actions-user Jun 19, 2026
36b3baf
add appsec-rule to fetch bot desc json files
buixor Jun 22, 2026
210838b
Update index
actions-user Jun 22, 2026
0d5442b
Merge branch 'master' into test-waf-challenge-mode-scenarios
buixor Jul 1, 2026
27eee02
fix the helpers names
buixor Jul 1, 2026
fd47f24
rename
buixor Jul 1, 2026
18fa69b
avoiid this
buixor Jul 1, 2026
00260c9
Update index
actions-user Jul 1, 2026
545299e
add more bots (search, ai, monitoring) + split into various appsec-rules
buixor Jul 1, 2026
503a268
Update index
actions-user Jul 1, 2026
3f15d33
be more lenient
buixor Jul 1, 2026
389aae4
Update index
actions-user Jul 1, 2026
862da2f
exclude more commons paths from bot challenge
buixor Jul 1, 2026
46bcfcd
cleanup comments
buixor Jul 1, 2026
a41deab
Update index
actions-user Jul 1, 2026
b9b4a91
datadog
buixor Jul 1, 2026
60b320c
up
buixor Jul 1, 2026
3bcc4d3
Update index
actions-user Jul 1, 2026
fef0309
up
buixor Jul 1, 2026
c5594ae
Update index
actions-user Jul 1, 2026
8879d07
Merge branch 'master' into test-waf-challenge-mode-scenarios
buixor Jul 1, 2026
27808d5
fix escape
buixor Jul 1, 2026
d0b7e3f
Update index
actions-user Jul 2, 2026
b70a0fb
update the branch to reflect the changes on IsKnownBot helper
buixor Jul 15, 2026
034df7e
Update index
actions-user Jul 15, 2026
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
11 changes: 11 additions & 0 deletions appsec-configs/crowdsecurity/challenge-exclude-crawler-files.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: crowdsecurity/challenge-exclude-crawler-files
# Let well-known crawler files fly through without a challenge. SetLegitimateBot() is
# strictly per-request (never persisted to a cookie or across requests), so this cannot
# be abused to whitelist a session for other paths.
inband:
pre_eval:
- filter: >-
req.URL.Path in ['/robots.txt', '/ads.txt', '/app-ads.txt', '/sitemap.xml'] ||
req.URL.Path startsWith '/.well-known/'
apply:
- SetLegitimateBot()
10 changes: 10 additions & 0 deletions appsec-configs/crowdsecurity/challenge-exclude-feeds.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: crowdsecurity/challenge-exclude-feeds
# Let syndication feed endpoints fly through without a challenge. SetLegitimateBot() is
# strictly per-request (never persisted to a cookie or across requests).
inband:
pre_eval:
- filter: >-
req.URL.Path in ['/feed', '/rss', '/atom.xml'] ||
req.URL.Path startsWith '/feed/' || req.URL.Path startsWith '/rss/'
apply:
- SetLegitimateBot()
8 changes: 8 additions & 0 deletions appsec-configs/crowdsecurity/challenge-exclude-webhooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: crowdsecurity/challenge-exclude-webhooks
# Let third-party webhook callbacks fly through without a challenge. SetLegitimateBot() is
# strictly per-request (never persisted to a cookie or across requests).
inband:
pre_eval:
- filter: "req.URL.Path in ['/webhooks/stripe', '/webhooks/github', '/webhooks/sendgrid']"
apply:
- SetLegitimateBot()
14 changes: 14 additions & 0 deletions appsec-configs/crowdsecurity/simple-bot-challenge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: crowdsecurity/simple-bot-challenge
inband:
post_eval:
# Skip the challenge for verified good bots (forward-confirmed rDNS) and for requests a
# well-known-path exclusion config flagged via SetLegitimateBot(). The flag is per-request only.
- filter: "!IsLegitimateBot(req.RemoteAddr, req.UserAgent(), req.URL.Path)"
apply:
- SendChallenge()
on_challenge_submit:
# Reject submissions whose fingerprint fast-detect flags a known bot. The resulting "rejected"
# event feeds crowdsecurity/appsec-bot-detected (alert only, no active ban).
- filter: "fingerprint.IsBot()"
apply:
- RejectSubmission("known bot (fast bot detection)")
35 changes: 35 additions & 0 deletions collections/crowdsecurity/appsec-bot-challenge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# AppSec Bot Challenge

This collection enables CrowdSec AppSec **challenge mode** for bot detection: visitors are served a
lightweight proof-of-work + browser-fingerprint challenge, and clients flagged as known bots by the
fast bot detection are rejected.

What it ships:

- `crowdsecurity/simple-bot-challenge`: challenges incoming requests, skips verified good bots
(forward-confirmed rDNS), and rejects challenge submissions whose fingerprint trips the fast bot
detection.
- Well-known-path exclusion configs (`challenge-exclude-crawler-files`, `-feeds`, `-webhooks`) that
let machine-facing endpoints (`/robots.txt`, `/.well-known/*`, feeds, webhooks) through without a
challenge. The exclusion is strictly per-request and is never persisted to a cookie or across
requests, so it cannot be abused to whitelist a session for other paths.
- A dedicated bot-detection parser tracking the fingerprint session id (`fsid`) and OS.
- Scenarios that alert on detected bots and on challenge abuse.
- A user-friendly alert context exposing `fsid`, OS, and the bot signals that fired.

## Enabling bot challenge

Add the `crowdsecurity/simple-bot-challenge` appsec-config to your WAF acquisition, plus any of the
well-known-path exclusion configs you need:

```yaml
appsec_configs:
- crowdsecurity/simple-bot-challenge
- crowdsecurity/challenge-exclude-crawler-files
- crowdsecurity/challenge-exclude-feeds
- crowdsecurity/challenge-exclude-webhooks
labels:
type: appsec
listen_addr: 127.0.0.1:7422
source: appsec
```
26 changes: 26 additions & 0 deletions collections/crowdsecurity/appsec-bot-challenge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: crowdsecurity/appsec-bot-challenge
parsers:
- crowdsecurity/appsec-logs
- crowdsecurity/appsec-bot-detection-logs
appsec-configs:
- crowdsecurity/simple-bot-challenge
- crowdsecurity/challenge-exclude-crawler-files
- crowdsecurity/challenge-exclude-feeds
- crowdsecurity/challenge-exclude-webhooks
scenarios:
- crowdsecurity/appsec-bot-detected
- crowdsecurity/appsec-challenge-too-many-requests
- crowdsecurity/appsec-challenge-too-many-submissions
- crowdsecurity/appsec-challenge-request-with-no-submission
Comment thread
buixor marked this conversation as resolved.
Outdated
contexts:
- crowdsecurity/appsec_base
- crowdsecurity/appsec_bot_detection
description: "AppSec bot detection: challenge mode with fast-detect bot rejection and well-known-path exclusions"
labels:
label: "WAF - Bot Challenge"
author: crowdsecurity
tags:
- waf
- appsec
- http
- bot
23 changes: 23 additions & 0 deletions contexts/crowdsecurity/appsec_bot_detection.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# User-friendly context for AppSec bot-detection / challenge events.
# Each detected_signals line emits a readable signal name only when its flag is set,
# so the console shows a compact list of what tripped the fast bot detection.
context:
fingerprint_id:
- evt.Meta.fsid
operating_system:
- evt.Meta.os
user_agent:
- evt.Meta.http_user_agent
bot_detected:
- evt.Meta.fingerprint_bot
challenge_event:
- evt.Meta.challenge_event
detected_signals:
- "evt.Unmarshaled.fingerprint.Bot.Webdriver ? 'webdriver' : ''"
- "evt.Unmarshaled.fingerprint.Bot.Selenium ? 'selenium' : ''"
- "evt.Unmarshaled.fingerprint.Bot.CDP ? 'chrome-devtools-protocol' : ''"
- "evt.Unmarshaled.fingerprint.Bot.Playwright ? 'playwright' : ''"
- "evt.Unmarshaled.fingerprint.Bot.BotUserAgent ? 'bot-user-agent' : ''"
Comment thread
buixor marked this conversation as resolved.
Outdated
- "evt.Unmarshaled.fingerprint.Bot.MissingChromeObject ? 'headless-chrome' : ''"
- "evt.Unmarshaled.fingerprint.Bot.PlatformMismatch ? 'platform-mismatch' : ''"
- "evt.Unmarshaled.fingerprint.Bot.GPUMismatch ? 'gpu-mismatch' : ''"
Comment thread
buixor marked this conversation as resolved.
Outdated
42 changes: 42 additions & 0 deletions parsers/s01-parse/crowdsecurity/appsec-bot-detection-logs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
onsuccess: next_stage
format: 3.0
#debug: true
filter: "evt.Parsed.program == 'appsec' && evt.Parsed.source == 'crowdsec-appsec-challenge'"
name: crowdsecurity/appsec-bot-detection-logs
description: "Parse AppSec bot-detection / challenge events"
statics:
- meta: service
value: appsec
- meta: log_type
value: appsec-challenge
- meta: source_ip
expression: "evt.Parsed.source_ip"
- meta: target_host
expression: "evt.Parsed.target_host"
- meta: request_uuid
expression: "evt.Parsed.req_uuid"
- meta: target_uri
expression: "evt.Parsed.target_uri"
- meta: challenge_event
expression: evt.Parsed.challenge_event
- meta: challenge_difficulty
expression: evt.Parsed.challenge_difficulty
- meta: challenge_fail_reason
expression: evt.Parsed.challenge_fail_reason
- meta: fsid
expression: evt.Parsed.fsid
- meta: fingerprint_bot
expression: evt.Parsed.fingerprint_bot
- meta: http_user_agent
expression: evt.Parsed.user_agent
# raw navigator.platform, kept for precise matching
- meta: platform
expression: evt.Parsed.platform
# user-friendly OS label derived from the raw platform string
- meta: os
expression: |
evt.Parsed.platform contains "Win" ? "Windows" :
(evt.Parsed.platform contains "Mac" ? "macOS" :
(evt.Parsed.platform contains "Linux" ? "Linux" :
(evt.Parsed.platform contains "iPhone" || evt.Parsed.platform contains "iPad" ? "iOS" :
(evt.Parsed.platform contains "Android" ? "Android" : evt.Parsed.platform))))
2 changes: 1 addition & 1 deletion parsers/s01-parse/crowdsecurity/appsec-logs.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
onsuccess: next_stage
format: 3.0
#debug: true
filter: "evt.Parsed.program == 'appsec'"
filter: "evt.Parsed.program == 'appsec' && evt.Parsed.source != 'crowdsec-appsec-challenge'"
Comment thread
buixor marked this conversation as resolved.
name: crowdsecurity/appsec-logs
description: "Parse Appsec events"
statics:
Expand Down
17 changes: 17 additions & 0 deletions scenarios/crowdsecurity/appsec-bot-detected.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type: trigger
format: 3.0
#debug: true
name: crowdsecurity/appsec-bot-detected
description: "A known bot was detected and rejected by the AppSec challenge (fast bot detection)"
filter: "evt.Meta.log_type == 'appsec-challenge' && evt.Meta.challenge_event == 'rejected'"
groupby: evt.Meta.source_ip
blackhole: 1m
labels:
service: http
confidence: 1
spoofable: 0
classification:
- attack.T1071
label: "Bot detected by CrowdSec AppSec challenge"
behavior: "http:bot"
remediation: false
20 changes: 20 additions & 0 deletions scenarios/crowdsecurity/challenge-request-with-no-submission.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type: counter
format: 3.0
#debug: true
name: crowdsecurity/appsec-challenge-request-with-no-submission
description: "Client made too many request to challenge page"
filter: "evt.Meta.log_type == 'appsec-challenge' && (evt.Meta.challenge_event == 'requested' || evt.Meta.challenge_event == 'submitted')"
cancel_on: "evt.Meta.log_type == 'appsec-challenge' && evt.Meta.challenge_event == 'submitted'"
duration: 60s
capacity: -1
cache_size: 1
groupby: evt.Meta.source_ip
blackhole: 10s
labels:
service: http
confidence: 0
spoofable: 0
classification:
- attack.T1110
label: "Blocked by CrowdSec AppSec"
behavior: "http:exploit"
19 changes: 19 additions & 0 deletions scenarios/crowdsecurity/challenge-too-many-request.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type: leaky
format: 3.0
#debug: true
name: crowdsecurity/appsec-challenge-too-many-requests
description: "Client made too many request to challenge page"
filter: "evt.Meta.log_type == 'appsec-challenge' && evt.Meta.challenge_event == 'requested'"
leakspeed: "60s"
capacity: 5
groupby: evt.Meta.source_ip
blackhole: 1m
labels:
service: http
confidence: 0
spoofable: 0
classification:
- attack.T1110
label: "Blocked by CrowdSec AppSec"
behavior: "http:exploit"
remediation: true
19 changes: 19 additions & 0 deletions scenarios/crowdsecurity/challenge-too-many-submit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type: leaky
format: 3.0
#debug: true
name: crowdsecurity/appsec-challenge-too-many-submissions
description: "Client made too many submissions for challenge"
filter: "evt.Meta.log_type == 'appsec-challenge' && evt.Meta.challenge_event == 'submitted'"
leakspeed: "60s"
capacity: 5
groupby: evt.Meta.source_ip
blackhole: 1m
labels:
service: http
confidence: 0
spoofable: 0
classification:
- attack.T1110
label: "Blocked by CrowdSec AppSec"
behavior: "http:exploit"
remediation: true
Loading