PLEASE REPORT ANY BUGS IN ISSUES
This is the series of articles on my blog about StyloBot
StyloBot Release Series: Behaviour, Not Identity
StyloBot Release Series: Behaviour-Aware ASP.NET UI
StyloBot Release Series: Finding and Fixing Unbounded Growth in Long-Running .NET Services
StyloBot Release Series: Behaviour-Aware TypeScript UI
StyloBot Release Series: The Sidecar Architecture
StyloBot Release Series: Learning to Get Faster
And NUMEROUS others in the coming weeks covering all of StyloBot's features at release. (These will also form the basis of a lucidSupport AI support system ;))
Bot detection that knows your site. Cloud scoring services evaluate your traffic against generic baselines trained on other people's users. StyloBot learns what normal looks like on your specific application: the document-asset-API request sequence, the timing distribution of your real users, the session shape your checkout flow produces. Bots that adapt to evade a cloud service still diverge from those patterns.
Runs in your own infrastructure: in-process ASP.NET Core middleware, standalone YARP gateway proxy, or sidecar detection API. 57 detectors, <150µs per request, no PII leaves your server.
This repo is the FOSS product. Full detection engine, dashboard, entity resolution, simulation packs. The commercial product uses the same engine with enterprise add-ons (see FOSS vs Commercial).
Cloud-based bot services work until your attacker adapts. When a sophisticated scraper learns to look like a user from your IP allow-list, a scoring API that doesn't know your application's normal behaviour has nothing to go on.
StyloBot runs in your own infrastructure. It knows what a real page-load sequence looks like on your site: document, then asset burst in 80-500ms, then API calls, then optionally SignalR. It knows the timing signatures of your real users' sessions compressed into 129-dimensional Markov chain vectors. It tracks identity across rotation attempts using cosine similarity walks across fingerprint neighbours. All of this runs in ~150µs per request on commodity hardware, with no network call to a third party and no PII leaving your server.
macOS (Homebrew - recommended)
brew install scottgal/stylobot/stylobot
stylobot 5080 http://localhost:3000 # starts in demo mode (observe only)
stylobot 5080 http://localhost:3000 --mode production # enable blockingHomebrew strips macOS's quarantine flag automatically. If you'd rather download the tarball, see the macOS first-run note in docs/RELEASE_SIGNING.md.
Linux (apt - Debian/Ubuntu - recommended)
curl -fsSL https://pages.stylo.bot/setup.deb.sh | sudo sh
sudo apt update && sudo apt install stylobot
stylobot 5080 http://localhost:3000
stylobot genkey # generate a secure HMAC key for productionThe apt repo is a GPG-signed static repository hosted on GitHub Pages; the setup script installs the signing key to /usr/share/keyrings/stylobot.gpg and apt update verifies the repo signature on every fetch.
Linux (manual tarball)
# Download from GitHub Releases
VER=X.Y.Z
curl -L -O https://github.com/scottgal/stylobot/releases/download/allbot-v${VER}/stylobot-linux-x64.tar.gz
# Verify provenance (proves this binary was built from this repo's workflow)
gh attestation verify stylobot-linux-x64.tar.gz --owner scottgal
tar xzf stylobot-linux-x64.tar.gz && chmod +x stylobot && sudo mv stylobot /usr/local/bin/
stylobot 5080 http://localhost:3000macOS (manual tarball)
VER=X.Y.Z
curl -L -O https://github.com/scottgal/stylobot/releases/download/allbot-v${VER}/stylobot-osx-arm64.tar.gz
tar xzf stylobot-osx-arm64.tar.gz && cd stylobot-osx-arm64
./clear-quarantine.sh # strip the download quarantine flag (one-time)
./stylobot 5080 http://localhost:3000Or brew install scottgal/stylobot/stylobot and skip the quarantine dance.
Docker (gateway - transparent proxy in front of your app)
docker run --rm -p 8080:8080 -e DEFAULT_UPSTREAM=http://host.docker.internal:3000 \
scottgal/stylobot-gateway:latestDocker (sidecar - detection API your app calls explicitly, gRPC + REST)
The sidecar is a deliberately low-surface-area build of the same detection engine, scoped to the sidecar topology: your app makes a per-request call (gRPC or REST), gets a verdict back, decides what to do with it. No dashboard, no Razor, no SignalR, no HTML - just the detector pipeline behind a thin auth + transport layer. Run it next to your app (same pod, same host, same container) so the network hop is loopback. The proto and the REST schema are stable; the binary is single-file and self-contained.
docker run --rm -p 5090:5090 \
-e BotDetection__ApiKeys__0__Key=changeme \
scottgal/stylobot-sidecar:latest
# gRPC: localhost:5090 | REST: POST localhost:5090/api/v1/detectDocker (all-in-one - gateway + dashboard for the simplest deployment)
stylobot-all bundles YARP detection proxy and the dashboard into one process. One container, hit /_stylobot for the dashboard, point YARP at your upstream via ReverseProxy:Routes in appsettings.
docker run --rm -p 8080:8080 scottgal/stylobot-all:latest
# Dashboard: http://localhost:8080/_stylobot | Proxy + detection on the same portDocker (dashboard viewer - stylobot-ui against a remote gateway)
stylobot-ui is the dashboard-host product. It runs next to a stylobot gateway (started with --enable-api) and proxies every dashboard read over HTTP. Hosted inside your network with local-only access; nothing leaves the LAN.
# 1. Run the gateway with the API enabled (one or more X-SB-Api-Key entries required)
stylobot 5080 http://your-app:3000 --enable-api
# 2. Run stylobot-ui pointed at the gateway
docker run --rm -p 5095:8080 \
-e StyloBot__Source__Pull__Url=http://host.docker.internal:5080 \
-e StyloBot__Source__Pull__ApiKey=SB-... \
-e StyloBot__Source__Live__Url=http://host.docker.internal:5080/api/v1/hub \
scottgal/stylobot-ui:latest
# Dashboard: http://localhost:5095/_stylobot (read-only viewer; gateway owns data + writes)NuGet (embed as ASP.NET Core middleware)
dotnet add package mostlylucid.botdetection
dotnet add package mostlylucid.botdetection.uibuilder.Services.AddStyloBot(dashboard => {
dashboard.AllowUnauthenticatedAccess = true; // dev only
});
app.UseRouting();
app.UseStyloBot(); // broadcast, detection, dashboard: correct ordering guaranteed
app.MapControllers();Dashboard at /stylobot. Detection at ~150µs per request from first request.
The foreground commands in Quick start are for kicking the tyres - they hold the terminal and die when you close the SSH session. In production, stylobot runs as a background daemon under a process manager. That's the path the Homebrew formula, the Debian package, and every Docker image take internally.
The CLI exposes daemon control directly:
stylobot 5080 http://localhost:3000 -d # fork to background, write PID file, return
stylobot status # check the daemon + hit /health
stylobot stop # SIGTERM the running daemon
# Long form (equivalent):
stylobot start 5080 http://localhost:3000 # same as -d
stylobot 5080 http://localhost:3000 --daemon # same as -dWhen run with -d / --daemon / start:
- The process double-forks and detaches from the controlling terminal.
- A PID file is written so
stop/statuscan find it later. - Logs go to stdout/stderr - under systemd/launchd these are journaled. Outside a process manager, redirect (
> stylobot.log 2>&1). stylobot statusreturns non-zero if the daemon isn't running, so it composes with shell health checks.
Daemon mode is opt-in, not the default - running stylobot 5080 http://localhost:3000 without -d stays foreground so demos and CI runs aren't surprised by a backgrounded process. Don't add -d to docker run invocations either; containers run in the foreground by design and the orchestrator (Docker / Kubernetes / systemd-resolved) handles supervision.
Under systemd, the recommended unit looks like:
[Service]
Type=forking
PIDFile=/var/run/stylobot/stylobot.pid
ExecStart=/usr/bin/stylobot 5080 http://localhost:3000 -d
ExecStop=/usr/bin/stylobot stop
Restart=on-failure(Type=forking because -d double-forks; the unit waits for the parent to exit and reads the PID file.)
- Content sequence detection: tracks the natural document/asset/API page-load order per fingerprint. Bots hitting APIs directly, or at machine speed (<20ms inter-request), diverge from the expected human chain and get flagged. Centroid freshness suppresses false positives during deploys by detecting ETag changes and divergence rate spikes
- 129-dim session vectors: Markov chain transition probabilities + timing entropy + protocol fingerprint dimensions, all in one vector. Partial chain archetypes detect bots at 3-5 requests before full session maturity. L2 velocity between consecutive sessions catches rotation and account takeover
- Anonymous entity resolution: builds progressive identity (L0 to L5) from IP+UA, TLS, HTTP/2, client-side JS, and behavioural patterns. Merge/split/rewind operations backed by immutable snapshots. Rotation creates a trail of near-miss cosine neighbours that get linked back to the same actor
- Leiden clustering: groups signatures into bot networks by behavioural similarity. HNSW graph for sub-millisecond approximate nearest-neighbour search. Emergent bot clusters surface when new attack patterns are still unlabelled
- Simulation packs: honeypots that look like real products (WordPress 5.9 + 8 CVE modules). Bots that hit them get engaged by the holodeck with HMAC-canary-embedded fake responses. Canary replay links rotated fingerprints back to the original actor
- Local GPU tunnel: route LLM inference from a cloud instance to a local GPU via
stylobot llmtunnel+ Cloudflare tunnel. HMAC-SHA256 per-request signing, 30s TTL nonces, loopback-only listener - Zero PII: HMAC-SHA256 hashed signatures. Raw UAs stored PII-stripped. No raw IPs persisted. Blackboard signals are privacy-safe keys, never raw data
- Headless framework naming: identifies Puppeteer, Playwright, Selenium, PhantomJS by name from timing and API surface, not UA string
Request -> Wave 0 (< 1ms) -> Wave 1 (behavioral) -> Wave 2 (AI) -> Verdict
Signature (identity) Session vectors, Heuristic model, Bot probability
UA, Header, IP, Periodicity, Cookies, Intent scoring, Risk band
TLS/TCP/H2/H3, Resource waterfall, Cluster detection, Action policy
Transport, Haxxor, CVE probes, Waveform LLM escalation Entity resolution
ContentSequence
| Layer | Detectors | What it catches |
|---|---|---|
| Identity | Signature, HeaderCorrelation, Periodicity | UA rotation, identity factors, temporal patterns |
| Protocol | TLS (JA3/JA4), TCP/IP (p0f), HTTP/2, HTTP/3, Transport, StreamAbuse | Spoofed browser fingerprints, protocol inconsistencies |
| Behavioral | Waveform, SessionVector, AdvancedBehavioral, CacheBehavior, CookieBehavior, ResourceWaterfall, ContentSequence | Timing patterns, Markov chains, missing assets, page-load sequence divergence |
| Content | UserAgent, Header, AiScraper, Haxxor, SecurityTool, VersionAge | Known bots, attack payloads, impossible browser versions |
| Network | IP, GeoChange, ResponseBehavior, MultiLayerCorrelation, CveProbe | Datacenter IPs, impossible travel, CVE scanning, cross-layer mismatches |
| Intelligence | FastPathReputation, ReputationBias, TimescaleReputation, Cluster, Similarity, Intent | Historical reputation, Leiden clustering, HNSW similarity, threat scoring |
| Ad Fraud | ClickFraud, PiiQueryString | IAB SIVT: datacenter/VPN/headless on paid traffic, referrer spoofing, immediate bounce |
| AI | Heuristic, HeuristicLate, LLM | 50-feature model (<1ms), optional LLM for ambiguous cases |
| Client | ClientSide, FingerprintApproval, ChallengeVerification | JS timing probes, headless detection, PoW challenges |
Each visitor builds a progressive identity across requests:
L0: IP + UA hash (immediate)
L1: TLS fingerprint correlation
L2: HTTP/2 frame signature
L3: Client-side JS probes (Canvas, WebGL, audio context)
L4: Behavioural pattern matching (session vector cosine similarity)
L5: Confirmed human (challenge solved or approved fingerprint)
Rotation is detected by walking cosine neighbours in the HNSW graph. If a "new" fingerprint lands within distance 0.15 of a known bad actor, it inherits reputation, even if IP, UA, and TLS all changed.
Sessions compress into 129-dimensional vectors:
[0..99] Markov transition probabilities (10 states x 10 states)
[100..109] Stationary distribution (time in each request state)
[110..117] Temporal features (timing entropy, burst ratio, error rate, ...)
[118..128] Fingerprint dimensions (TLS, HTTP protocol, TCP OS, headless, datacenter, ...)
States: PageView, ApiCall, StaticAsset, WebSocket, SignalR, ServerSentEvent, FormSubmit, AuthAttempt, NotFound, Search
Fingerprint mutation (new TLS JA3, new HTTP/2 settings) shows up as velocity in dimensions 118-128; the same L2 delta that catches behavioural rotation also catches protocol rotation.
Real browsers follow a predictable request sequence after a page load. StyloBot tracks this per fingerprint with four time-phase windows:
| Phase | Window | Expected states |
|---|---|---|
| Critical | 0-500ms | StaticAsset, PageView |
| Mid | 500ms-2s | StaticAsset, ApiCall, PageView |
| Late | 2s-30s | ApiCall, SignalR, WebSocket, SSE |
| Settled | 30s+ | ApiCall, SignalR, SSE |
Divergence score = machine-speed timing + unexpected state for phase + high request volume. Threshold: 0.4. When 40%+ of sessions on an endpoint diverge within a 1-hour window, the centroid is marked stale, suppressing false positives during deploys rather than flagging your own users.
Raw request -> HMAC-SHA256 -> PrimarySignature -> blackboard signals
| |
Never persisted Privacy-safe keys only
(IP, raw UA) (no IP, no raw UA, no body)
Blackboard is ephemeral per-request. Signals are hierarchical keys (request.ip.is_datacenter, sequence.diverged). Raw PII stays in DetectionContext, never written to signals.
| StyloBot | Cloud scoring APIs | |
|---|---|---|
| Latency | ~150µs in-process | 20-200ms network round-trip |
| Privacy | No data leaves your server | Request metadata sent to third party |
| Explainability | Full signal trace per request | Black-box score |
| Customisation | YAML manifests, per-endpoint policy overrides | Limited or none |
| Continuity | Works if internet is down | Fails open or closed |
| Cost model | Fixed (your hardware) | Per-request or per-seat |
| Context | Knows your site's normal patterns | Generic baselines |
- Web scraping: sequence divergence catches scrapers that skip the asset burst and jump straight to API endpoints; UA + TLS mismatch catches headless frameworks claiming to be Chrome
- Credential stuffing: velocity detection via inter-session L2 distance; session vector clustering groups attack waves by shared behavioural signature even when IPs rotate
- API abuse: no document request means no sequence context, so the full deferred detector stack always runs; machine-speed timing detected regardless of IP
- Click fraud: dedicated
ClickFraudContributorscores IAB SIVT patterns - datacenter/VPN/headless on paid-ad landings, referrer spoofing, immediate bounce fraud; UTM and click-ID signals extracted and hashed byPiiQueryStringContributorbefore any PII reaches the blackboard - Automated account creation: client-side fingerprinting detects missing JS APIs (canvas, WebGL, audio) and Puppeteer/Playwright named by timing characteristics
- CVE probing: simulation packs serve fake vulnerable endpoints; canary-embedded responses link probe attempts to the same actor across IP rotation
Detection works fully without any LLM. LLM enriches bot names and handles ambiguous cases at the edge of the heuristic model's confidence range.
stylobot 5080 http://localhost:3000 --mode production --llm ollama # local (default: gemma4)
stylobot 5080 http://localhost:3000 --mode production --llm openai --llm-key sk-...
stylobot 5080 http://localhost:3000 --mode production --llm anthropic --llm-key sk-ant-...
# Route cloud LLM inference to a local GPU
stylobot llmtunnel # on GPU machine, prints connection key
stylobot 5080 http://localhost:3000 --llm localtunnel --llm-key "sb_llmtunnel_v1_..."| Provider | Default model | Cost |
|---|---|---|
ollama |
gemma4 | Free (local) |
openai |
gpt-4o-mini | ~$0.15/1M tokens |
anthropic |
claude-haiku-4-5 | ~$0.25/1M tokens |
gemini |
gemini-2.0-flash | Free tier |
groq |
llama-3.3-70b | Free tier |
localtunnel |
your local model | Free (Mostlylucid.BotDetection.Llm.Tunnel) |
Real-time monitoring at /stylobot. All data persists to SQLite.
- Overview: top threats, traffic chart, world threat map
- Visitors: signature-level cards with probability badges (Bot/Suspicious/Uncertain/Human)
- Sessions: Markov chain timeline with behavioral radar and session playback
- Threats: CVE probe feed, honeypot engagements, severity badges
- Clusters: Leiden community detection visualization
- User Agents: family breakdown, version distribution, full-text search
- Configuration: Monaco YAML editor (read-only in FOSS)
Two products, same detection engine. FOSS is complete for detection, entity resolution, and the dashboard. The commercial product adds enterprise operational features via DI; gateways run unmodified FOSS detection.
- All 57 detectors, same pipeline as commercial
- Anonymous entity resolution (merge/split/rewind, L0-L5 confidence)
- Real-time dashboard (Overview, Visitors, Sessions, Threats, Clusters, User Agents, Configuration)
- Session vectors, Markov chains, behavioral radar charts
- Simulation packs (WordPress 5.9 with 8 CVE modules)
- SQLite persistence (zero external dependencies)
- Local GPU tunnel for LLM inference routing
- BDF replay testing
- CLI binary (6 platforms)
- Docker gateway (YARP reverse proxy)
- Optional LLM enrichment (any provider)
- Public REST API + Node.js SDK
Persistence & scale: PostgreSQL + pgvector, Redis cross-gateway cache and pub/sub config reload, TimescaleDB retention
Fleet management: multi-gateway coordination, fleet dashboard, leader election, Kubernetes Helm chart
Live configuration: forms-based detector config editor with hot-reload, per-endpoint policy overrides, config audit trail
Identity & access: Keycloak + Ed25519 JWT license validation, OIDC/SAML SSO, protected identity policies
Reporting: scheduled threat intelligence digests, webhook alerting, data retention controls
Additional packs: Django, Rails, Laravel, Spring Boot, Strapi, Shopify simulation packs; identity graph explorer
License model: capability-based JWT tiers. If a license expires, the system reverts to FOSS mode: detection continues, PostgreSQL falls back to SQLite, config editor goes read-only. No downtime.
Mostlylucid.BotDetection/ Core detection library (NuGet)
Mostlylucid.BotDetection.UI/ Dashboard + SignalR hub (NuGet)
Mostlylucid.BotDetection.Api/ Public REST API
Mostlylucid.BotDetection.Llm.Tunnel/ GPU tunnel relay
Mostlylucid.BotDetection.Console/ Standalone CLI / gateway (6 platforms, AOT, --enable-api)
Mostlylucid.BotDetection.Sidecar/ Headless sidecar (gRPC + REST, AOT)
Stylobot.Gateway/ Docker YARP reverse proxy
Stylobot.Ui/ Dashboard host (rest/local mode, not AOT)
Stylobot.All/ Gateway + dashboard in one process (not AOT)
sdk/node/ Node.js SDK (core, node, elements packages)
bot-signatures/ BDF replay test signatures
test-bdf-scenarios/ BDF replay test scenarios
docs/ Architecture, specs, security review
scripts/ Load tests, Docker compose, build tooling
| Binary | Role | Detection | Dashboard | AOT | When |
|---|---|---|---|---|---|
stylobot (Console) |
Gateway / reverse-proxy | yes | no (--enable-api exposes REST + SignalR hub) |
yes (35MB) | Edge gateway. Minimal surface; pair with stylobot-ui for the dashboard. |
stylobot-sidecar |
gRPC + REST detection sidecar | yes | no | yes (37MB) | App calls a per-request detect; loopback. |
stylobot-ui |
Dashboard host | no | yes | no | Hosted inside a network as the dashboard for a remote stylobot gateway. Loopback bind by default. |
stylobot-all |
Gateway + dashboard | yes | yes | no | One container, simplest deployment. Trades binary size for "it just works". |
- Quick start
- Integration levels
- Tutorial
- Configuration reference
- Configuration reference (full)
- Deployment guide
- Sidecar deployment - gRPC/REST detection API for non-.NET backends
- YARP integration
- YARP gateway
- Proxy topologies
- Action policies
- Blocking and filters
- Signals and custom filters
- Detection strategies
- Policies
- Extensibility
- User agent detection
- Header detection
- IP detection
- AI detection
- AI scraper detection
- Behavioral analysis
- Advanced behavioral detection
- Behavioral waveform
- Content sequence detection
- Centroid freshness
- Cache behavior detection
- Client-side fingerprinting
- TLS/TCP/HTTP/2/HTTP/3 fingerprinting
- HTTP/3 fingerprinting
- TCP/IP fingerprint
- Transport protocol detection
- Stream/transport detection
- Inconsistency detection
- Cluster detection
- Response behavior
- Fast-path reputation
- Reputation bias
- Learning and reputation
- Timescale reputation
- Geo change detection
- Haxxor detection
- Security tools detection
- Version age detection
- Verified bot detection
- Adblocker detection
- Click fraud detection
- Account takeover detection
- Project Honeypot
- Simulation packs
- Holodeck (honeypot response system)
- Custom pack authoring
- Endpoint pinning
- Fingerprint approval
- Proof-of-work challenge
- Session analytics
- Response PII masking
- SignalR beacon architecture
- Authenticated users
- WAF comparison
- API reference
- API keys
- Dashboard threat scoring
- In-dashboard config editor
- Training data API
- UI components
- BDF replay system
- .NET 10.0 (building from source)
- No external dependencies for FOSS (SQLite is embedded)
- Commercial: PostgreSQL, optional Redis
GNU AGPLv3 — FOSS core. Commercial features licensed separately.
See ATTRIBUTIONS.md for the full list of OSS projects StyloBot is built on, with links to their source repos and the project files where they're consumed.