Skip to main content

Signals

A signal is a CEL detection rule that Santa evaluates on the host against telemetry. A match does not block anything. Santa reports it back, and the report appears under Signals in Events. Signals need telemetry enabled on the hosts they target.

You manage signals under Rules > Signals. Each signal applies to one operating system and to the hosts in its tags.

The event Variable​

A signal's expression reads the current telemetry record through event, and must return a boolean. Guard each branch with has() for the event type it reads:

has(event.execution) && event.execution.Target.Executable.Path.startsWith("/tmp/")

Workshop checks every field against the telemetry schema of the signal's operating system when you save it. A misspelt field, or a field from the other platform, is rejected rather than silently never matching on the host. See the macOS schema for the fields each event carries.

Functions​

Signals can use the standard CEL macros, the CEL string extensions, and these functions:

FunctionReturnsDescription
correlate(window, threshold)boolTrue once the preceding condition has matched threshold times within the trailing window
correlate(window, threshold, groupBy)boolThe same, with a separate window per groupBy value (a string or a list of strings)
days(n)durationA duration of n days, for windows longer than duration() expresses cleanly
sequence(stage, of, maxspan, key)boolTrue on the final stage of an ordered sequence of events. See Sequences

Place correlate() and sequence() after the condition they apply to (cond && correlate(...)), so only matching events count. Their windows and progress persist across scans.

Sequences​

sequence() matches an ordered series of events that share a join key, all within a time span. Write one branch per stage, each guarded by its event type and predicate, with sequence() last:

(has(event.execution) && event.execution.Target.Executable.Path == "/usr/bin/curl" &&
sequence(1, 2, duration("30s"), event.execution.Target.ID)) ||
(has(event.network_activity) &&
sequence(2, 2, duration("30s"), event.network_activity.Process.ID))
  • stage is the branch's position in the sequence, starting at 1.
  • of is the number of stages in the sequence.
  • maxspan is how long the whole sequence has to complete, measured from its first stage.
  • key joins the stages: only events with the same key advance the same sequence.

The signal reports when the final stage matches.

Join Keys​

The key must be one of these types:

KeyExample
A process identityevent.execution.Target.ID
A stringevent.execution.Target.Executable.Path
A list of strings[event.execution.Hostname, event.execution.Target.Executable.Path]

Workshop rejects any other key when you save the signal, because Santa cannot join on it and the signal would never report. That includes a bare PID such as event.execution.Target.ID.PID: PIDs are reused, so a process identity pairs the PID with a value that tells two processes with the same PID apart. It also includes a whole process record such as event.execution.Target: pass its ID instead.

Stage Predicates​

When two stages match the same event type, make their predicates mutually exclusive. If one event can satisfy both, it restarts the sequence on one stage and is refused by the other as the same event, so the signal may never report.

Requirements

sequence() requires Santa 2026.8. Workshop sets a minimum Santa version of 2026.8 on every signal that uses it, so hosts on anything older are not sent the signal.

See Also​