Skip to main content

Composite OR detections in Chronicle when upstream rules share no common identity fields

  • January 6, 2026
  • 0 replies
  • 100 views

PanosMtln
Forum|alt.badge.img+4

Hello community,

I’m sharing a composite detection pattern that took me quite some time to figure it out, because it addresses a limitation in Chronicle composite rules that isn’t clearly documented.
This pattern may help whoever needs to build OR-based composite detections across heterogeneous telemetry (for example, host-based vs user-based signals).
 

I needed a composite rule that triggers when either of the following detections fires for a watchlist entity:

  • USB File Write activity  (host-centric)

  • Outbound personal email attachments (user-centric)

Constraints:

  • The USB rule only has hostname

  • The Email rule only has email address

  • There is no common identity field across the two detections

  • The watchlist contains both email and hostname

  • Chronicle composite rules require all event groups in events: to be joined by equalities

  • Using OR directly between watchlist fields and detection fields results in compiler errors such as:

validating intermediate representation:
event variables are not all joined by equalities

The rules below are not working examples as they need to be adjusted to each environment.


Producer Rule #1

Log Type Characteristics

  • Identity: hostname

  • User identity may be missing or inconsistent

rule USB_Device_Mounted_and_FileWrite {

meta:
description = "Detect USB device usage"
severity = "Low"

events:
$e.metadata.product_event_type = /RemovableMediaVolumeMounted/
$Host = strings.to_lower($e.principal.hostname)
$User = $e.principal.user.userid

match:
$Host over 30m

outcome:
$risk_score = 10
$hostname = $Host
$User = $User
$USBFilesWritten = array_distinct($e.target.file.full_path)
$join = "1"

condition:
$e
}

Producer Rule #2

Log Type Characteristics

  • Identity: email address

  • No hostname information

  • Cannot be directly joined to the USB rule

rule Outbound_Personal_Email_Attachment {

meta:
description = "Detect outbound personal email attachments"
severity = "Low"

events:
$e.metadata.event_type = "EMAIL_TRANSACTION"
$Sender = strings.to_lower($e.network.email.from)

match:
$Sender over 30m

outcome:
$risk_score = 10
$OutboundEmailSender = $Sender
$OutboundEmailSubject = $e.network.email.subject
$OutboundEmailAttachmentMBytes =
cast.as_float($e.additional.fields["totalSizeAttachments"]) / 1000000
$join = "1"

condition:
$e
}

Composite rule: OR logic across USB and Email events for watchlist entities

 

The solution that worked was:

  1. Use a single detection placeholder ($d) to pool both rules

  2. Introduce a synthetic join key from the watchlist to satisfy the compiler’s equality-join requirement

  3. Apply the real correlation logic (hostname OR email) once the join graph is satisfied

⚠️ This is intentionally a workaround, not a preferred long-term design.

 

rule Composite_Departing_Employee_Exfiltration {

meta:
description = "Composite OR rule for USB or Email exfiltration scoped to watchlist"
severity = "Medium"

events:
// Watchlist row
$userid = %Watchlist1.userid
$upn = %Watchlist1.upn
$email = %Watchlist1.email_address
$hostname = %Watchlist1.hostname
$key = %Watchlist1.key

// Detection pool (single placeholder)
(
$d.detection.detection.rule_id = "ru_whatever_id_1" // USB
or
$d.detection.detection.rule_id = "ru_whatever_id_2" // Email
)

$Rule_Name = $d.detection.detection.rule_name

// Synthetic join to satisfy Chronicle compiler requirements
// Required because USB and Email detections share no common identity fields.
// Do not remove unless upstream rules emit a shared identity key.
$d.detection.detection.variables["join"] = $key

// Real correlation logic (email OR hostname)
// Since this is scoped to the aforementioned detections there is always
// a "hostname" or an "OutboundEmailSender"
(
strings.to_lower($d.detection.detection.variables["hostname"]) = $hostname
or
strings.to_lower($d.detection.detection.variables["OutboundEmailSender"]) = $email
)

// Context variables (may be empty depending on rule)
$USBHost = $d.detection.detection.variables["hostname"]
$USB_User = $d.detection.detection.variables["User"]
$USB_FilesWritten = $d.detection.detection.variables["USBFilesWritten"]
$USB_DeviceType = $d.detection.detection.variables["DeviceType"]

$EmailSender = $d.detection.detection.variables["OutboundEmailSender"]
$Email_Subject = $d.detection.detection.variables["OutboundEmailSubject"]
$Email_Attachment_MBytes = $d.detection.detection.variables["OutboundEmailAttachmentMBytes"]

match:
$key, $Rule_Name over 59m

condition:
$d

options:
// Allows inspection of all variables during testing
allow_zero_values = true
}
  • This pattern intentionally bypasses the composite join limitation using a synthetic equality.

  • The preferred long-term solution remains refactoring upstream rules to emit a shared identity key (user email, device ID, asset ID, etc.).

  • Until that is possible, this approach allows practical OR-based correlation across heterogeneous telemetry.

Happy to hear if others have found cleaner approaches, or if there are roadmap plans to relax the equality-join requirement in composite detections. (For example allowing event joins through a data table)