Cross-stream window joins.
The strongest detections correlate across streams: an identity sign-in followed by an endpoint exec on the same entity inside a short window. Here is how to write one without losing event ordering or blowing up the join.
Join on entity_id
Every stream shares a stable entity_id for both actor and target, so a cross-stream match is a join, not an integration. Name the streams, bind them on the shared entity, and the pipeline correlates them in flight.
YAMLdetections/signin-then-exec.yml when: join: on: entity.actor # same actor across both streams streams: - stream: identity.signin as: signin match: { result: success, net.src_geo.new: true } - stream: endpoint.process as: exec match: { signed: false } window: 10m
Windows & ordering
A bare window asks “did both happen within 10 minutes?” Often you want more: that one happened before the other. Add a sequence and the pipeline only fires when the order holds.
YAMLrequire order window: 10m sequence: [signin, exec] # sign-in must precede the exec
| Clause | Fires when |
|---|---|
window only | Both legs match within the window, any order. |
sequence | Legs match within the window, in the listed order. |
within | Tighter inter-leg gap, e.g. exec within 90s of sign-in. |
Keep joins cheap
A join holds state for every open window. Two rules keep that state small: bound the window tightly, and join on a low-cardinality key. A 24-hour window on a high-volume stream is how a clever rule becomes an expensive one.
Where to go next
- Detection pipeline — the correlate stage in depth.
- Detection-as-code — ship the joined rule through CI.