Configuration
The keys under BotDetection:* you'll actually edit. Everything is settable three ways, in precedence order:
- CLI flag (where one exists):
--mode production. - Env var with
__separator:BotDetection__BotThreshold=0.75. appsettings.json(or--config /path/to/your.json).
stylobot --output-config /etc/stylobot/appsettings.json dumps the full effective config with every default visible. Edit that.
Detection thresholds
| Key | Default | Effect |
|---|---|---|
BotDetection:BotThreshold |
0.7 |
Probability above this counts as bot. |
BotDetection:NonAiMinProbability |
0.01 |
Floor when no LLM ran. Stops the heuristic pipeline saturating to literal 0.0 for confident humans. |
BotDetection:NonAiMaxProbability |
0.90 |
Ceiling when no LLM ran. Prevents heuristic-only "100% bot" claims. |
BotDetection:EarlyExitThreshold |
0.95 |
Probability above which the orchestrator short-circuits remaining detectors. |
BotDetection:ImmediateBlockThreshold |
0.98 |
Probability above which action policies skip the soft-throttle path. |
Action policies
--policy <name> picks the default; per-route overrides live under BotDetection:Policies and BotDetection:BotTypeActionPolicies.
{
"BotDetection": {
"DefaultActionPolicyName": "throttle-stealth",
"BotTypeActionPolicies": {
"Scraper": "block",
"Tool": "throttle-tools",
"MaliciousBot": "mask-pii"
},
"Policies": {
"block": { "ActionPolicyName": "block", "BlockThreshold": 0.7 },
"throttle-stealth": { "ActionPolicyName": "throttle-stealth", "BlockThreshold": 0.7 },
"block-ai-scrapers": { "ActionPolicyName": "block", "AllowVerifiedBots": true }
}
}
}
| Built-in policy | Behaviour |
|---|---|
allow |
Pass through, log only. |
throttle-stealth |
Task.Delay then real response (bots think it's slow; humans don't notice). |
throttle-tools |
429 + Retry-After. |
challenge |
JS / cookie challenge. |
block |
403. |
block-hard |
TCP RST. |
redirect-honeypot |
302 to a fake. |
mask-pii |
Real response, PII stripped. Requires ResponsePiiMasking:Enabled=true. |
PII masking (when using mask-pii)
{
"BotDetection": {
"ResponsePiiMasking": {
"Enabled": true,
"AutoApplyForHighConfidenceMalicious": true,
"AutoApplyBotProbabilityThreshold": 0.9,
"AutoApplyConfidenceThreshold": 0.75
}
}
}
Without Enabled=true, mask-pii is a no-op.
Identity matcher
Required for the dashboard's fingerprint radar to render and for cross-request behavioural learning.
| Key | Default | Effect |
|---|---|---|
BotDetection:Identity:Enabled |
false |
Master switch. Set true to write fingerprint_keys rows + render the radar. |
BotDetection:Identity:VectorDimension |
512 |
Centroid vector size. Don't change post-deploy. |
BotDetection:Identity:MatchThreshold |
0.85 |
Cosine similarity above which a session matches an existing fingerprint. |
LLM
Per-provider knobs under BotDetection:AiDetection.
{
"BotDetection": {
"AiDetection": {
"Provider": "ollama",
"MaxConcurrentRequests": 2,
"TimeoutMs": 5000,
"Ollama": {
"Endpoint": "http://localhost:11434",
"Model": "gemma4:e2b"
}
},
"EnableLlmDescriptions": true
}
}
Provider accepts ollama, llamasharp (in-process CPU), openai, anthropic, azure. Each provider has its own subsection: BotDetection:AiDetection:OpenAi, :Anthropic, :Azure, etc.
EnableLlmDescriptions=true is the master switch for the background description coordinator (per-fingerprint names + cluster summaries). Off by default.
Threat-intel feeds
The IP/CIDR matchers used by ThreatIntelContributor.
{
"BotDetection": {
"ThreatIntel": {
"Enabled": true,
"Providers": {
"SpamhausDrop": { "Enabled": true },
"TorExit": { "Enabled": true },
"CisaKev": { "Enabled": true },
"CloudRanges": { "Enabled": true }
}
}
}
}
Without ThreatIntel:Enabled=true, the contributor short-circuits and known-bad sources never get flagged.
API keys
Required for --enable-api (the /api/v1/* surface). Trusted callers send the key as X-SB-Api-Key: <value> and bypass detection.
{
"BotDetection": {
"ApiKeys": {
"internal-cron": {
"Key": "<output of: stylobot genkey>",
"Name": "Internal cron job",
"Enabled": true
}
}
}
}
Without at least one enabled entry, --enable-api fails on startup.
Response headers
What Stylobot writes on every proxied response.
| Key | Default | Effect |
|---|---|---|
BotDetection:ResponseHeaders:Enabled |
true |
Master switch. |
BotDetection:ResponseHeaders:IncludeReasonHeader |
true |
Adds X-Bot-Reasons listing top detector reasons. |
BotDetection:ResponseHeaders:IncludeProcessingTime |
true |
Adds X-Bot-ProcessingTime. |
Signature labels
Pin a human-readable name to a specific signature (overrides the matcher's auto-name).
{
"BotDetection": {
"SignatureLabels": {
"abc123def456": "Internal monitoring",
"789xyz456abc": "Pingdom check"
}
}
}
Kestrel profile
Picks transport-layer tuning. Also settable via --profile.
| Profile | Thread pool | Conns | Keep-alive | Body cap | Use it for |
|---|---|---|---|---|---|
balanced (default) |
50 / 50 | 10k | 30s | none | Mixed traffic. |
api |
100 / 100 | 20k | 15s | 64 KB | JSON APIs, no WebSockets. |
site |
200 / 200 | 10k WebSockets | 120s | 1 MB | Public site with browsers + SignalR. |
highrisk |
50 / 50 | 2k | 5s | small | Under attack right now. Reject fast (3s header timeout). |
STYLOBOT_PROFILE=api stylobot 5080 http://localhost:3000 --mode production
Signature hash key
HMAC key used to hash IPs into signatures. Set this in production so signatures persist across restarts.
# Generate
stylobot genkey
# Set
BotDetection__SignatureHashKey=<base64-output>
Default behaviour: session-scoped random key (signatures rotate every restart; warning logged at startup).
Logging
Standard ASP.NET Core: Logging:LogLevel:Default=Information. Stylobot uses Serilog under the hood; everything also writes to logs/stylobot-*.log next to the binary by default.
More
- Running Locally: first-run smoke test with these knobs applied inline.
- CLI Reference: which flags map to which config keys.
- Troubleshooting: symptom-to-fix table for the common gotchas.
- Full FOSS docs: https://github.com/scottgal/stylobot/tree/main/docs
- Stylobot articles on the Mostlylucid blog: https://www.mostlylucid.net/blog/category/StyloBot