This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Monday, 01 June 2026
StyloBots detektormotor är ASP, ., NET Core och .. Denna artikel förklarar hur den maskinen kopplas till Go-grindar, ,, noder och ., js-applikationer, ,, och alla andra stakar via en gRPC-bilar, mska6, en typad Go SDK, mske7, och ett Caddy-plugin, , utan att någon av dessa kunder behöver veta något om . och ..
Den
github.com/scottgal/stylobot-goSDK,github.com/scottgal/caddy-stylobotplugin, ochMostlylucid.BotDetection.Sidecarbehållare kommer att publiceras snart. Allt som följer beskriver ytan de ska utsätta
StyloBot-utsläppsserien
- Behavior, Inte Identitet: varför StyloBot modellerar klienter beteendemässigt
- Behavior-Är medveten ASP.NET UI: servern
- Att hitta och lösa gränslös tillväxt i långtids-, -- och / eller körande- -.-nätverk: den tillförlitlighetsdisciplin som gör motorn tråkig i produktionen
- Behavior-Akvis typscript-UI: Express , Fastify, och webbladerkomponenter
- Sidecar-arkitekturen: den här artikeln
- Att lära sig bli snabbare: adaptiv inlärningssystem, ,, fyra, M SK2, övningsminnet, ,, och bedömningsキャッシュen
- Att testa det som inte stannar kvar: kontrolldisciplinen : en BDF-fielddriver regression M SK2 belastning , och kalibrering
- StyloExtract - en lokal lärande HTML omvandlare till Markdown: HTML, →, Markdown-lagret som paras med detektorn, ,, walkersbug lucidVIEW som fångade, M SK3, och den hundföda-loopan som gjorde det ärligt
En omvänd proxy är den uppenbara platsen för att köra botdetektorn. göra annorlunda baserat på vem som gör begäran.
Sidekarets mönster skiljer de två problemna från varandra. Gatewayt stannar snabbt och ostämt . Sidekaret håller kvar sessie tillståndet, archetyp-ankernM SK3 drift tillståndet~, och slutsatsen-cache som gör detektorn exakta | ( archtypen-ankermodellenM SK7 fingeravtryckets slutsatss-caché~MSC9 och de fyra levels-lärningssystemen är täckt av Att lära sig bli snabbare). De två kommunicerar över det lokala nätverket | ( | samma bedienare eller samma Pod | ) | så den runda färden |- | är mikrosekunder till en enda |
Detta är inget nytt mönster. Envoy Proxy gör precis detta för tjänsten-meshbekymmer | ( | mTLS |, | återfördrag | Dapr gör det för staten och puben OpenTelemetry Kollektor gör det för telemetrin . Linkerd, Consul Connect, och AWS App Mesh alla följer samma modell . Patternen fortsätter att dyka upp eftersom den löser ett riktigt problem M SK4 du vill ha ett komplext tillståndsmässigt beteende som passerar språkgränserna utan att implementera det i varje språk
Alternativet är att kompilera detektorn direkt i portaalet.
Inget av dem är realistiskt för en motor med den här komplexiteten. vågor Later waves fire only when earlier signals warrant it , ;, a credential, -, theuffing attempt triggers different detectors than a Googlebot crawl Markovkedjans vektorer i ett dimensionellt utrymme... En Markov-skedja är bara en möjlighetsmodell över ". Med tanke på det sista som den här sammankomsten gjorde, ,. Vad kommer nästa gång? ?", och 129. Dimensionerna fångar tillräckligt med sidor. Leidens samhällsdetektion över dessa vektorer, :, en graf, -, en skrämmande algoritm som gruppar samman sestioner som beter sig på samma sätt.,, vilket är hur StyloBot upptäcker ett botnätverk även när enskilda sestioner ser bra ut.., och det fortsätter med allt detta till SQLite mellan begäran.M SK4, den här tillståndet behöver en oberoende livscykel. ., den kan inte börja om med noderprocessen eller bli sönderslagen när portaal laddar upp sin konfiguration igen.
Ett bilar låter varje komponent göra vad det är bra på
graph TD
classDef input fill:none,stroke:#3b82f6,stroke-width:2px
classDef async fill:none,stroke:#a855f7,stroke-width:2px
classDef good fill:none,stroke:#22c55e,stroke-width:2px
classDef store fill:none,stroke:#f59e0b,stroke-width:2px
GW["Gateway<br/>Caddy / YARP / nginx"]:::input
SD["StyloBot Sidecar · ASP.NET Core<br/>gRPC :5090 · REST :5091<br/>≤50ms per Detect RPC"]:::async
APP["Upstream Application<br/>Node / Go / ASP.NET<br/>reads req.stylobot.verdict"]:::good
DB[("SQLite<br/>sessions · signatures · reputation")]:::store
GW --> SD
SD <--> DB
GW --> APP
Gatewayn ropar på sidekaren, ,, injicerar resultatet som HTTP-header och blockerar det optionellt.. Uppströmsapplikationen läser ut headerna och sätter beslutet.
Mostlylucid.BotDetection.Sidecar är en minimal ASP-process.
/api/v1/* slutpunktergRPC är en högpresterande fjärranvedningssystem applikation som utvecklats på Google. Protokollbuffer (protobuf
Interfejsen definieras i .proto file. Från den filen , kogenerer producerar inskrivna klient- och server-stubs i vilket språk som helst .proto så att alla språk med en gRPC implementering kan kalla sidecar
Servicen har tre RPCs:
service DetectionService {
rpc Detect(DetectRequest) returns (DetectResponse);
rpc DetectBatch(DetectBatchRequest) returns (DetectBatchResponse);
rpc RenderWidget(RenderWidgetRequest) returns (RenderWidgetResponse);
}
Detect is the per-request hot path| .| Pass it method| ,| path|,| header| MST4| remote IP| M ST5| and optional TLS fingerprint data| M st6| It runs the wave pipeline | M St7| only the detectors that the request| M S S K 8| its signals warrant| M s K 9| updates the session vector _ M S K 10| scores against the reputation store| M L S K 11| and returns a verdict| M R S K 12
DetectBatch kör flera applikationer i ordning. Används för logrepetition och avgränsad analys, inte perM SK2 användandet av kopplingen till applikationen
RenderWidget accepterar en flytande templatesträng, en optionell bedömelse , och ett nyckel-värdekartan av ytterligare variablerM SK3 sedan renderar templateservernMska4 sida och återvänder HTMLM Ska5 Det här är hur icke-Mska6 NET-kolar producerar botMske7 medveten HTML utan att sätta upp en separat renderprocessMka8 detaljer i RenderWidget-sektionen härnästMkka9
En snabb Glossar före diagrammet, eftersom dessa namn kommer att dyka upp
request.ip.is_datacenter, detection.useragent.confidence). Detektorer skriver signaler till den ; Senare detektorer i samma våg läser dem . Den lever bara under en gång av ett begäran.HttpContext den gRPC-tjänsterna bygger från protoanfragens fälten. Detektormotorn var designad för ASP.NET mellanverk och förväntar sig att läsa från HttpContext; genom att syntetisera ett får samma motor att fungera oföränderligt inuti en gRPC sändningsequenceDiagram
participant GW as Gateway (Caddy)
participant SD as gRPC Service
participant ORC as BlackboardOrchestrator
participant DET as Detectors (up to 49, 4 waves)
participant DB as SQLite
GW->>SD: Detect RPC { method, path, headers, remoteIp }
SD->>ORC: DetectAsync(syntheticHttpContext)
ORC->>DET: Wave 0 - Identity + ContentSequence
ORC->>DET: Wave 1 - Fast path <1ms: UA, Header, IP, Heuristic ...
ORC->>DET: Wave 2 - Session vectors, Behavioural waveform
ORC->>DET: Wave 3 - Slow path: DNS, advanced fingerprinting
DET-->>ORC: DetectionContributions (signals, confidence deltas)
ORC->>DB: update session vector and reputation score
DB-->>ORC: ok
ORC-->>SD: AggregatedEvidence { botProbability, riskBand, ... }
SD-->>GW: DetectResponse { isBot, riskBand, recommendedAction, ... }
Hela pipelinen körs inuti en enda gRPC sändning.
Gateway-koden i Go kan inte importera ASP.NET sidecar. Det som den kan göra är att kalla den över gRPC . Go SDK M SK3github.com/scottgal/stylobot-go) ger en skriftippd gränssnitt som gömmer genererade protobuf-typer helt från budbärare
Protobuf-genererad kod är verbos och har en ovanlig API . Enamer är representerade som integerRISK_BAND_HIGH, inte "High"). Feldnamen är kameelKlas i vissa generatorer och slang _ fall i andra M SK2 Att utsätta prototyper i din offentliga API betyder att dina telefoner måste förstå allt detta
SDK översätter en gång vid gränsen (proto-enum till kanoniska strängar , protostrukturer till enkla Go-strukturer ) och telefonerna ser det aldrig
// the only interface you depend on: no proto imports required
type Client interface {
Detect(ctx context.Context, req DetectRequest) (*Verdict, error)
DetectBatch(ctx context.Context, reqs []DetectRequest) ([]*Verdict, error)
RenderWidget(ctx context.Context, req RenderRequest) (*RenderResponse, error)
Close() error
}
DetectRequest och Verdict är enkla Go-strukturer:
type DetectRequest struct {
Method string
Path string
Headers map[string]string
RemoteIP string
Protocol string // "http" or "https"; defaults to "https" if empty
TLS *TLSInfo
}
type Verdict struct {
IsBot bool
BotProbability float32
Confidence float32
BotType string // "AiBot", "Scraper", "GoodBot", ...
BotName string
RiskBand string // "VeryLow", "Low", "Elevated", "Medium", "High", "VeryHigh"
RecommendedAction string // "Allow", "Throttle", "Challenge", "Block"
ThreatScore float32
ThreatBand string
ProcessingTimeMs float32
DetectorsRun int32
Reasons []Reason
}
Att skapa en klient och köra detektorn:
import (
stylobot "github.com/scottgal/stylobot-go"
"context"
"time"
)
client, err := stylobot.NewClient(
"localhost:5090",
stylobot.WithTimeout(50 * time.Millisecond),
stylobot.WithAPIKey(os.Getenv("SB_API_KEY")),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
verdict, err := client.Detect(ctx, stylobot.DetectRequest{
Method: r.Method,
Path: r.URL.RequestURI(),
RemoteIP: r.RemoteAddr,
Headers: extractHeaders(r),
Protocol: "https",
})
if err != nil {
// fail open: log and continue
log.Printf("stylobot detect failed: %v", err)
return next(w, r)
}
if verdict.RecommendedAction == "Block" {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
grpc.NewClient skapar en klientkanal, men bygger inte direkt en TCP-uppkoppling.. Anslutningen sker vid den första RPC: s sändning.
Detta skiljer sig från HTTP-clienter, där du vanligtvis ansluter dig vid skapandet. gRPC gå med dokumentation täcker livscykeln i detalj.
WithTimeout på NewClient sätter en standardinställning per call deadline som tillämpas inuti varje Detect ringa. Om din ringkod | ( | eller mellanverk som Caddy-plugin |) | redan drar fram ett deadline | МSK3 | begränsat sammanhang från den inkomende utmaningen ♫ , | så applicerar SDK vilken deadline som helst först stängs ut ♫ WithTimeout från NewClient och låter pluginen kontrollera det. För att använda självständigt | ( | en handledare som ropar direkt på SDK |), | sätta igång NewClient som visas ovanför.
Caddy är en Go-based webbserver och omvänd proxy med automatisk HTTPS xcaddy att bygga ett anpassad Caddy-binär som innefattar dina plugins, som producerar en enda self-contained binary utan beroende av utloppstid på de delade biblioteken . Det här skiljer sig från nginxMSC3s dynamiska modulsystem M SK4.so fileloaded at runtime). StyloBots plugin (github.com/scottgal/caddy-stylobot) registrerar en middleware-handlerare som ropar på Go SDK varje gång
Konfigurationen av Caddyfilen:
{
order stylobot before respond
}
:80 {
stylobot {
endpoint localhost:5090 # gRPC host:port of the sidecar
timeout 50ms # per-request deadline; fails open on expiry
# on_block 503 # optional: change the block status code (default: 403)
}
reverse_proxy upstream:3000
}
Plug-in injicerar nio slutsatser på varje skickad pyyntö:
| Titel | källfält |
|---|---|
X-StyloBot-IsBot |
isBot (bool |
X-StyloBot-Probability |
botProbability (0.0-1.0) |
X-StyloBot-Confidence |
confidence (0.0-1.0) |
X-StyloBot-BotType |
e.gM SK2 AiBot, Scraper, GoodBot |
X-StyloBot-BotName |
e.gM SK2 GPTBot, Googlebot |
X-StyloBot-RiskBand |
VeryLow ... VeryHigh |
X-StyloBot-Action |
Allow / Throttle / Challenge / Block |
X-StyloBot-ThreatScore |
numerisk |
X-StyloBot-ThreatBand |
None ... Critical |
Tillåter var isBot=true och Action=Block stannar vid öppningsplatsen med en 403 och når aldrig uppströmmen | . | Allt annat |( | inklusive robotar med Throttle eller Challenge tillämpning ) skickas vidare med alla nio opskrifte intakt. Det är den avsedda splittningen : portaalet hanterar hårda blocker M SK3 uppströmmen hanterar nyanser
on_block ändrar statuskoden som används när portaal blockerar (defaultM SK1 403). Set on_block 503 för att hämma återtestningslogiken i skalare som behandlar 403 som återtestbart .
flowchart TD
classDef input fill:none,stroke:#3b82f6,stroke-width:2px
classDef async fill:none,stroke:#a855f7,stroke-width:2px
classDef good fill:none,stroke:#22c55e,stroke-width:2px
A["1. Strip inbound X-StyloBot-* headers"]:::input
B["2. context.WithTimeout(r.Context(), 50ms)"]:::input
C["3. sbClient.Detect(ctx, DetectRequest)"]:::async
D{error?}
E["log warn - fail open<br/>forward unchanged"]:::good
F["4. injectHeaders<br/>X-StyloBot-IsBot, Probability, Confidence,<br/>BotType, BotName, RiskBand, Action,<br/>ThreatScore, ThreatBand"]:::input
I["next.ServeHTTP - forward to upstream<br/>with all verdict headers injected"]:::good
A --> B --> C --> D
D -->|yes| E --> I
D -->|no| F --> I
steg 1, strålkastar inbound En klient som känner till X-StyloBot-* headernamen skulle kunna göra sig själva-injicera ett positivt dömande och få den att överleva misslyckandet -öppen väg . Att ta bort dem först betyder att dömet uppströms ser alltid kom från bilarna
steg 2, kontextuell termin Tyduppgången kommer från r.Context() med hjälp av context.WithTimeout ( som tar en relativt lång tid context.WithDeadline tar en absolut tid. r.Context() snarare än context.Background() är nyckelpunkten : om klienten avskärmar sig innan gRPC-uppdraget slutar, avbrytningen sprider sig genom och bibilen slutar processen tidigt
steg 3 och 4, att upptäcka och injicera Nio fällor blir nio X-StyloBot-* opskrifte. opskrifte är ställda innan blockchecken, så uppströmmen läser dem via styloBotMiddleware({ mode: 'headers' }) för alla icke-- blockerade begäran isBot=true och recommendedAction=Block kommer tillbaka som 403 vid öppningsplatsen ; allt annat går framåt med fulla bedömande naglarna anslutna
Implementeringen:
// from sdk/caddy/stylobot.go
func (s *StyloBot) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
for _, name := range stylobotHeaders {
r.Header.Del(name)
}
ctx, cancel := context.WithTimeout(r.Context(), s.timeout)
defer cancel()
verdict, err := s.sbClient.Detect(ctx, sb.DetectRequest{
Method: r.Method,
Path: r.URL.RequestURI(),
RemoteIP: ExtractIP(r),
Protocol: r.Proto,
Headers: ExtractHeaders(r),
})
if err != nil {
s.logger.Warn("stylobot detect failed, failing open", zap.Error(err))
return next.ServeHTTP(w, r)
}
injectHeaders(r, verdict)
if verdict.IsBot && s.OnBlock > 0 && verdict.RecommendedAction == "Block" {
http.Error(w, "Forbidden", s.OnBlock)
return nil
}
return next.ServeHTTP(w, r)
}
Caddy-plugins måste kompileras i binären med hjälp av xcaddy. Dockerfilen i integreringstesten visar mönstret
# from tests/integration/caddy-sidecar/Dockerfile
FROM caddy:2-builder AS builder
WORKDIR /build
COPY sdk/caddy/ caddy-plugin/
COPY sdk/go/ go/
WORKDIR /build/caddy-plugin
RUN xcaddy build \
--with github.com/scottgal/caddy-stylobot=/build/caddy-plugin \
--with github.com/scottgal/stylobot-go=/build/go
FROM caddy:2
COPY --from=builder /build/caddy-plugin/caddy /usr/bin/caddy
COPY tests/integration/caddy-sidecar/Caddyfile /etc/caddy/Caddyfile
Pluginet's go.mod innehåller:
replace github.com/scottgal/stylobot-go => ../go
Detta säger till Go toolchain " när du ser stylobot-go, använda det lokala kataloget istället för att hämta från proxymodulen go build och go test i plugin-katalogen.
xcaddy skapar ett nytt temporärt Go-modul för sin byggnad. Det modulet är inte härvt replace instruktioner från pluginn's go.mod. Utan den andra --with argument, xcaddy skulle försöka ladda ner stylobot-go från pkg.go.dev (där det inte är ännu publicerat
Den --with module=path argumentet är xcaddy's ursprungliga motsvarighet till replace direktiv: den kartlägger en modulväg till ett lokalt gids vid byggnadstid
RenderWidget är en gRPC RPC på sidecarn som accepterar en Flüssig templatesträng, renderar den med det detekterande sammanhanget , och återvänder HTML. Det låter vilken som helst som ska ringa M SK3 gå till proxy~,~ Nod SSR-layer , lotskedjan~)~ skapa botMSC7 som vet HTML utan att köra en separat renderprocessM SK8
Flytande är en templeringsspråk skapad av Shopify, som används av shopifys Themes, JekyllM SK2 GitHub Pages , och många andra systemMSC4 dess nyckeleigenheter : säkra att köra med användaren M SK6 tillhandahållna templater | ( inga godtyckliga kodöverdrag |), enkelt nog för icke- Fluid.Core, en hög -presterande
Implementering av bilar:
// from src/Mostlylucid.BotDetection.Sidecar/Services/DetectionGrpcService.cs
private static readonly FluidParser Parser = new(); // static, shared, compiled templates cached
public override async Task<Proto.RenderWidgetResponse> RenderWidget(
Proto.RenderWidgetRequest request, ServerCallContext context)
{
if (!Parser.TryParse(request.Template, out var template, out var error))
return new Proto.RenderWidgetResponse { Success = false, Error = error };
var ctx = new TemplateContext();
if (request.Verdict is { } v)
{
ctx.SetValue("isBot", v.IsBot);
ctx.SetValue("botProbability", (double)v.BotProbability);
ctx.SetValue("botType", v.BotType);
ctx.SetValue("botName", v.BotName);
ctx.SetValue("riskBand", v.RiskBand.ToString());
ctx.SetValue("recommendedAction", v.RecommendedAction.ToString());
ctx.SetValue("threatScore", (double)v.ThreatScore);
ctx.SetValue("threatBand", v.ThreatBand.ToString());
}
foreach (var kv in request.Vars)
ctx.SetValue(kv.Key, kv.Value);
var html = await template.RenderAsync(ctx);
return new Proto.RenderWidgetResponse { Html = html, Success = true };
}
Fluid.Core upprätthåller en inre kompilerad template-cache FluidParser är statisk och de delar ut sig över alla gRPC sändningar.
Noden StyloBotGrpcClient.renderWidget() exemplet och fulla templatevariablereferens finns i Artikel om TypeScript SDK.
Jag kallar det från Go:
rendered, err := client.RenderWidget(ctx, stylobot.RenderRequest{
Template: `{% if isBot %}<p class="warning">Bot: {{ botType }}</p>{% endif %}`,
Verdict: verdict,
Vars: map[string]string{"locale": "en-GB"},
})
if err == nil && rendered.Success {
fmt.Fprint(w, rendered.HTML)
}
Samma syntax om du ska ringa RenderWidget från Go, Node , eller använda <sb-widget> i webbläsaren: samma flytande motor , samma variabler som namnen på
graph LR
classDef input fill:none,stroke:#3b82f6,stroke-width:2px
classDef async fill:none,stroke:#a855f7,stroke-width:2px
classDef good fill:none,stroke:#22c55e,stroke-width:2px
classDef store fill:none,stroke:#f59e0b,stroke-width:2px
INT([Internet])
CF["Cloudflare<br/>Tunnel / CDN"]
CA["Caddy<br/>+ caddy-stylobot"]:::input
SD["StyloBot Sidecar<br/>:5090 gRPC · :5091 REST"]:::async
WEB["Upstream App<br/>Node / Go / ASP.NET"]:::good
DB[("SQLite<br/>sessions · reputation")]:::store
INT --> CF --> CA
CA -->|"gRPC Detect<br/>≤50ms"| SD
SD <-->|"persist"| DB
CA -->|"X-StyloBot-* headers"| WEB
WEB -->|"/_stylobot/partials/render<br/>(widget rendering)"| SD
Applikationen uppströms ropar på sidecaren direkt för att göra widget rendering, genomgå vägarna. Widget rendering behöver fulla bedömande sammanhang och händer efter att applikationen redan passerat vägarnadetektorn , så det finns inga detektorduplikeringarM SK3
Caddy plugin, Node middleware, och Go SDK alla misslyckas med att öppna : en bilstolstimmar eller fel blir en varningslog och ett tillåtande tomt slutsats M SK3 inte en MSC4xxM SK5 | | Caddy deadline är en kall | - | start safety margin | ; | den stabila |- | kostnaden på ett varmt anslutande är |
Världshandeln- -koder detta- -blockar den legitima trafiken, eftersom det inte är möjligt att upptäcka- -är värre än att sakna robottrafik under en bilolycka.
Veröffentlichungsserien fortsätter. Fler artiklar om detektionsinfrastrukturer , användningsmönster M SK2 observabilitet , och den kommersiella topologin är fortfarande kvar
källan till implementeringen: github.com/scottgalM SK2stylobot. Livmotorn , brädan, , och kommersiella kontrollen stylobot.net.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.