ARCHITECTURE · DETECTION PIPELINE
The detection pipeline.
Every event passes through the same five-stage pipeline. Each stage is a separately scaled microservice; back-pressure is monitored and exposed via /v1/health/streams.
The five stages
STAGE 1
Decode & parse
Native format → canonical event.
STAGE 2
Enrich
Asset · identity · geo · intel.
STAGE 3
Evaluate
Streaming detection plans.
STAGE 4
Correlate
Window joins on entity_id.
STAGE 5
Decide
Case · contain · suppress.
FIGURE 2 · PER-STAGE LATENCY (p50)
Detection-as-code
Detections are declarative plans, versioned in git and shipped through CI like any other code — not rules typed into a console. A plan says what to match, how to correlate, and what to do; the pipeline compiles it into a streaming query.
DETECTION PLAN · winrm-burst.yml
plan: lateral-movement/winrm-burst severity: high match: type: endpoint.process where: cmd ~ "wsmprovhost" and parent != "services.exe" correlate: by: entity.target window: 5m threshold: 3 emit: case: "WinRM lateral movement on $target" contain: [isolate-host, revoke-sessions]
Plans are evaluated continuously against the live stream and can be back-tested over history before promotion: td detect test winrm-burst.yml --since 30d.
Latency budget
| Stage | Work | p50 | p99 |
|---|---|---|---|
| Decode & parse | Native format → canonical event | 25 ms | 70 ms |
| Enrich | Asset, identity, geo, intel joins | 40 ms | 110 ms |
| Evaluate | Streaming detection plans | 90 ms | 240 ms |
| Correlate | Window joins on entity_id | windowed | — |
| Decide | Case · contain · suppress | <10 ms | 30 ms |
Backpressure & health
Each stage is scaled independently. Per-stream lag and backpressure are exposed so you can alert before a slow source masks a detection:
$ td health streams
{
"endpoint.process": { "lag_ms": 180, "backpressure": false },
"identity.signin": { "lag_ms": 240, "backpressure": false },
"network.flow": { "lag_ms": 910, "backpressure": true }
}