Detection rules › Sublime MQL
Sublime MQL rules: vip
VIP / Executive impersonation (strict match, untrusted)
#Sender display name matches the display name of a user in the $org_vips list, and the sender has never been seen before. The $org_vips list must first be manually connected to a VIP group of your upstream provider (Google Workspace and Microsoft 365 only) in order for this rule to work. Once connected, the list will be automatically synced and kept up-to-date. For more information, see the $org_vips documentation: https://docs.sublimesecurity.com/docs/configure-org_vips-list This rule is recommended to be used on a relatively small list of VIPs, and is meant to reduce attack surface by detecting any message that matches the protected list of display names from a first-time or unsolicited sender. Additional rule logic can be added to look for suspicious subjects, suspicious links, etc.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and (
// the display name matches a name on the orgs vip list
any($org_vips, .display_name =~ sender.display_name)
// or the display name starts with the name on the orgs vip list
or (
any($org_vips,
strings.istarts_with(sender.display_name, .display_name)
// and it is longer than just their name (eg. John Doe CEO)
and length(sender.display_name) > length(.display_name)
)
// and we have confidence it's BEC
and any(ml.nlu_classifier(body.current_thread.text).intents,
.name == "bec" and .confidence == "high"
)
)
)
and (
// ignore personal <> work emails
// where the sender and mailbox's display name are the same
length(recipients.to) > 0
or length(recipients.cc) > 0
or sender.display_name != mailbox.display_name
)
// bounce-back negations
and not strings.like(sender.email.local_part,
"*postmaster*",
"*mailer-daemon*",
"*administrator*"
)
and not any(attachments,
.content_type in (
"message/rfc822",
"message/delivery-status",
"text/calendar"
)
)
and (
(
profile.by_sender_email().prevalence in ("new", "outlier")
and not profile.by_sender_email().solicited
)
or (
profile.by_sender_email().any_messages_malicious_or_spam
and not profile.by_sender_email().any_messages_benign
)
// mismatched reply-to with unsoliticed reply-to address
or (
length(headers.reply_to) > 0
and (sender.email.email not in map(headers.reply_to, .email.email))
// negate reply-to addresses that are within the org domain
and not all(headers.reply_to,
.email.domain.domain in $org_domains
or .email.domain.root_domain in $org_domains
)
//
// This rule makes use of a beta feature and is subject to change without notice
// using the beta feature in custom rules is not suggested until it has been formally released
//
and not beta.profile.by_reply_to().solicited
and beta.profile.by_reply_to().prevalence != "common"
)
)
and (
(
sender.email.domain.root_domain in $org_domains
and not headers.auth_summary.dmarc.pass
)
or sender.email.domain.root_domain not in $org_domains
)
and not profile.by_sender_email().any_messages_benign
Detection logic
Scope: inbound message.
Sender display name matches the display name of a user in the $org_vips list, and the sender has never been seen before. The $org_vips list must first be manually connected to a VIP group of your upstream provider (Google Workspace and Microsoft 365 only) in order for this rule to work. Once connected, the list will be automatically synced and kept up-to-date. For more information, see the $org_vips documentation: https://docs.sublimesecurity.com/docs/configure-org_vips-list This rule is recommended to be used on a relatively small list of VIPs, and is meant to reduce attack surface by detecting any message that matches the protected list of display names from a first-time or unsolicited sender. Additional rule logic can be added to look for suspicious subjects, suspicious links, etc.
- inbound message
any of:
any of
$org_vipswhere:- .display_name is sender.display_name
all of:
any of
$org_vipswhere all hold:- strings.istarts_with(sender.display_name)
- length(sender.display_name) > length(.display_name)
any of
ml.nlu_classifier(body.current_thread.text).intentswhere all hold:- .name is 'bec'
- .confidence is 'high'
any of:
- length(recipients.to) > 0
- length(recipients.cc) > 0
- sender.display_name is not mailbox.display_name
not:
sender.email.local_part matches any of 3 patterns
*postmaster**mailer-daemon**administrator*
not:
any of
attachmentswhere:- .content_type in ('message/rfc822', 'message/delivery-status', 'text/calendar')
any of:
all of:
- profile.by_sender_email().prevalence in ('new', 'outlier')
not:
- profile.by_sender_email().solicited
all of:
- profile.by_sender_email().any_messages_malicious_or_spam
not:
- profile.by_sender_email().any_messages_benign
all of:
- length(headers.reply_to) > 0
- sender.email.email not in map(headers.reply_to, .email.email)
not:
all of
headers.reply_towhere any holds:- .email.domain.domain in $org_domains
- .email.domain.root_domain in $org_domains
not:
- beta.profile.by_reply_to().solicited
- beta.profile.by_reply_to().prevalence is not 'common'
any of:
all of:
- sender.email.domain.root_domain in $org_domains
not:
- headers.auth_summary.dmarc.pass
- sender.email.domain.root_domain not in $org_domains
not:
- profile.by_sender_email().any_messages_benign
Inspects: attachments[].content_type, body.current_thread.text, headers.auth_summary.dmarc.pass, headers.reply_to, headers.reply_to[].email.domain.domain, headers.reply_to[].email.domain.root_domain, headers.reply_to[].email.email, mailbox.display_name, recipients.cc, recipients.to, sender.display_name, sender.email.domain.root_domain, sender.email.email, sender.email.local_part, type.inbound. Sensors: beta.profile.by_reply_to, ml.nlu_classifier, profile.by_sender_email, strings.istarts_with, strings.like. Reference lists: $org_domains, $org_vips.
Indicators matched (2)
| Field | Match | Value |
|---|---|---|
ml.nlu_classifier(body.current_thread.text).intents[].name | equals | bec |
ml.nlu_classifier(body.current_thread.text).intents[].confidence | equals | high |
Stages and Predicates
Stage 1: mql_rule
and
or
and
any($org_vips)
and
length func_call "length(sender.display_name) > length($org_vips[].display_name)"
strings.istarts_with func_call "strings.istarts_with(sender.display_name)"
any(ml.nlu_classifier(body.current_thread.text).intents)
and
ml.nlu_classifier(body.current_thread.text).intents.confidence eq "high"
ml.nlu_classifier(body.current_thread.text).intents.name eq "bec"
any($org_vips)
$org_vips.display_name cross_field_compare "sender.display_name"
or
and
not
beta.profile.by_reply_to func_call "beta.profile.by_reply_to().solicited"
not
macro "all(headers.reply_to)"
beta.profile.by_reply_to func_call "beta.profile.by_reply_to().prevalence != common"
headers.reply_to length_compare "0"
macro "sender.email.email not in map(headers.reply_to, .email.email)"
and
not
profile.by_sender_email func_call "profile.by_sender_email().any_messages_benign"
profile.by_sender_email func_call "profile.by_sender_email().any_messages_malicious_or_spam"
and
not
profile.by_sender_email func_call "profile.by_sender_email().solicited"
profile.by_sender_email func_call "profile.by_sender_email().prevalence in (new, outlier)"
or
and
not
headers.auth_summary.dmarc.pass eq "true"
macro "sender.email.domain.root_domain in org_domains"
macro "sender.email.domain.root_domain not in org_domains"
not
any(attachments)
attachments.content_type in ["message/delivery-status", "message/rfc822", "text/calendar"]
not
or
sender.email.local_part match "administrator"
sender.email.local_part match "mailer-daemon"
sender.email.local_part match "postmaster"
not
profile.by_sender_email func_call "profile.by_sender_email().any_messages_benign"
or
recipients.cc length_compare "0"
recipients.to length_compare "0"
sender.display_name cross_field_compare "mailbox.display_name"
type.inbound eq "true"Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
attachments | array_any | excludes:attachments | |
sender.email.local_part | match | administrator | excludes:sender.email.local_part field:"sender.email.local_part" value:"administrator" |
sender.email.local_part | match | mailer-daemon | excludes:sender.email.local_part field:"sender.email.local_part" value:"mailer-daemon" |
sender.email.local_part | match | postmaster | excludes:sender.email.local_part field:"sender.email.local_part" value:"postmaster" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
sender.display_name | cross_field_compare |
| field:"sender.display_name" kind:cross_field_compare value:"mailbox.display_name" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP / Executive impersonation in subject (untrusted)
#Sender subject contains the display name of a user in the $org_vips list, and the sender has never been seen before. The $org_vips list must first be manually connected to a VIP group of your upstream provider (Google Workspace and Microsoft 365 only) in order for this rule to work. Once connected, the list will be automatically synced and kept up-to-date. For more information, see the $org_vips documentation: https://docs.sublimesecurity.com/docs/configure-org_vips-list This rule is recommended to be used on a relatively small list of VIPs, and is meant to reduce attack surface by detecting any message that matches the protected list of display names from a first-time or unsolicited sender. Additional rule logic can be added to look for suspicious subjects, suspicious links, etc.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and any($org_vips,
strings.contains(subject.subject, .display_name)
and strings.contains(.display_name, " ")
)
// not being sent to said VIP
and not (
(
length(recipients.to) == 1
and all(recipients.to,
any($org_vips,
.email == ..email.email
and strings.contains(subject.subject, .display_name)
and strings.contains(.display_name, " ")
)
)
)
)
and (
// ignore personal <> work emails
// where the sender and mailbox's display name are the same
length(recipients.to) > 0
or length(recipients.cc) > 0
or sender.display_name != mailbox.display_name
)
// bounce-back negations
and not strings.like(sender.email.local_part,
"*postmaster*",
"*mailer-daemon*",
"*administrator*"
)
and not any(attachments,
.content_type in (
"message/rfc822",
"message/delivery-status",
"text/calendar"
)
)
and (
(
profile.by_sender().prevalence in ("new", "outlier")
and not profile.by_sender().solicited
)
or (
profile.by_sender().any_messages_malicious_or_spam
and not profile.by_sender().any_messages_benign
)
)
// negate org domains unless they fail DMARC authentication
and (
(
sender.email.domain.root_domain in $org_domains
and not headers.auth_summary.dmarc.pass
)
or sender.email.domain.root_domain not in $org_domains
)
// negate highly trusted sender domains unless they fail DMARC authentication
and (
(
sender.email.domain.root_domain in $high_trust_sender_root_domains
and not headers.auth_summary.dmarc.pass
)
or sender.email.domain.root_domain not in $high_trust_sender_root_domains
)
Detection logic
Scope: inbound message.
Sender subject contains the display name of a user in the $org_vips list, and the sender has never been seen before. The $org_vips list must first be manually connected to a VIP group of your upstream provider (Google Workspace and Microsoft 365 only) in order for this rule to work. Once connected, the list will be automatically synced and kept up-to-date. For more information, see the $org_vips documentation: https://docs.sublimesecurity.com/docs/configure-org_vips-list This rule is recommended to be used on a relatively small list of VIPs, and is meant to reduce attack surface by detecting any message that matches the protected list of display names from a first-time or unsolicited sender. Additional rule logic can be added to look for suspicious subjects, suspicious links, etc.
- inbound message
any of
$org_vipswhere all hold:- strings.contains(subject.subject)
- .display_name contains ' '
not:
all of:
- length(recipients.to) is 1
all of
recipients.towhere:any of
$org_vipswhere all hold:- .email is .email.email
- strings.contains(subject.subject)
- .display_name contains ' '
any of:
- length(recipients.to) > 0
- length(recipients.cc) > 0
- sender.display_name is not mailbox.display_name
not:
sender.email.local_part matches any of 3 patterns
*postmaster**mailer-daemon**administrator*
not:
any of
attachmentswhere:- .content_type in ('message/rfc822', 'message/delivery-status', 'text/calendar')
any of:
all of:
- profile.by_sender().prevalence in ('new', 'outlier')
not:
- profile.by_sender().solicited
all of:
- profile.by_sender().any_messages_malicious_or_spam
not:
- profile.by_sender().any_messages_benign
any of:
all of:
- sender.email.domain.root_domain in $org_domains
not:
- headers.auth_summary.dmarc.pass
- sender.email.domain.root_domain not in $org_domains
any of:
all of:
- sender.email.domain.root_domain in $high_trust_sender_root_domains
not:
- headers.auth_summary.dmarc.pass
- sender.email.domain.root_domain not in $high_trust_sender_root_domains
Inspects: attachments[].content_type, headers.auth_summary.dmarc.pass, mailbox.display_name, recipients.cc, recipients.to, recipients.to[].email.email, sender.display_name, sender.email.domain.root_domain, sender.email.local_part, subject.subject, type.inbound. Sensors: profile.by_sender, strings.contains, strings.like. Reference lists: $high_trust_sender_root_domains, $org_domains, $org_vips.
Indicators matched (1)
| Field | Match | Value |
|---|---|---|
strings.contains | substring | |
Stages and Predicates
Stage 1: mql_rule
and
or
and
not
headers.auth_summary.dmarc.pass eq "true"
macro "sender.email.domain.root_domain in high_trust_sender_root_domains"
macro "sender.email.domain.root_domain not in high_trust_sender_root_domains"
or
and
not
headers.auth_summary.dmarc.pass eq "true"
macro "sender.email.domain.root_domain in org_domains"
macro "sender.email.domain.root_domain not in org_domains"
or
and
not
profile.by_sender func_call "profile.by_sender().any_messages_benign"
profile.by_sender func_call "profile.by_sender().any_messages_malicious_or_spam"
and
not
profile.by_sender func_call "profile.by_sender().solicited"
profile.by_sender func_call "profile.by_sender().prevalence in (new, outlier)"
any($org_vips)
and
$org_vips.display_name contains " "
strings.contains func_call "strings.contains(subject.subject)"
not
any(attachments)
attachments.content_type in ["message/delivery-status", "message/rfc822", "text/calendar"]
not
and
recipients.to length_compare "1"
macro "all(recipients.to)"
not
or
sender.email.local_part match "administrator"
sender.email.local_part match "mailer-daemon"
sender.email.local_part match "postmaster"
or
recipients.cc length_compare "0"
recipients.to length_compare "0"
sender.display_name cross_field_compare "mailbox.display_name"
type.inbound eq "true"Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
attachments | array_any | excludes:attachments | |
recipients.to | length_compare | 1 | excludes:recipients.to field:"recipients.to" value:"1" |
sender.email.local_part | match | administrator | excludes:sender.email.local_part field:"sender.email.local_part" value:"administrator" |
sender.email.local_part | match | mailer-daemon | excludes:sender.email.local_part field:"sender.email.local_part" value:"mailer-daemon" |
sender.email.local_part | match | postmaster | excludes:sender.email.local_part field:"sender.email.local_part" value:"postmaster" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
sender.display_name | cross_field_compare |
| field:"sender.display_name" kind:cross_field_compare value:"mailbox.display_name" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation with charitable donation fraud
#Fake email thread shows a VIP requesting a donation to a charity, usually addressed to Accounts Payable departments. Can result in monetary loss.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: Employee, Impersonation: VIP, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and strings.ilike(body.current_thread.text,
"*charity*",
"*gala*",
"*donation*",
"*donor*"
)
and any(ml.nlu_classifier(body.current_thread.text).entities,
.name == "financial"
)
and any(ml.nlu_classifier(body.current_thread.text).entities,
.name == "request"
)
and (
any($org_vips, strings.icontains(body.html.inner_text, .display_name))
or any($org_vips, strings.icontains(body.plain.raw, .display_name))
)
and (
(
(subject.is_forward or subject.is_reply)
and (
(length(headers.references) == 0 and headers.in_reply_to is null)
or headers.in_reply_to is null
)
)
// fake thread, but no indication in the subject line
// current_thread pulls the recent thread, but the full body contains the fake "original" email
or (
not ((subject.is_forward or subject.is_reply))
and (
3 of (
strings.icontains(body.html.display_text, "from:"),
strings.icontains(body.html.display_text, "to:"),
strings.icontains(body.html.display_text, "sent:"),
strings.icontains(body.html.display_text, "subject:")
)
or length(body.previous_threads) > 0
)
and (
length(body.current_thread.text) + 100 < length(body.html.display_text)
)
// negating bouncebacks
and not any(attachments,
.content_type in ("message/delivery-status", "message/rfc822")
)
)
)
and (
profile.by_sender().prevalence in ("new", "rare")
or profile.by_sender().days_known > 30
)
and not profile.by_sender().any_messages_benign
Detection logic
Scope: inbound message.
Fake email thread shows a VIP requesting a donation to a charity, usually addressed to Accounts Payable departments. Can result in monetary loss.
- inbound message
body.current_thread.text matches any of 4 patterns
*charity**gala**donation**donor*
any of
ml.nlu_classifier(body.current_thread.text).entitieswhere:- .name is 'financial'
any of
ml.nlu_classifier(body.current_thread.text).entitieswhere:- .name is 'request'
any of:
any of
$org_vipswhere:- strings.icontains(body.html.inner_text)
any of
$org_vipswhere:- strings.icontains(body.plain.raw)
any of:
all of:
any of:
- subject.is_forward
- subject.is_reply
any of:
all of:
- length(headers.references) is 0
- headers.in_reply_to is missing
- headers.in_reply_to is missing
all of:
none of:
- subject.is_forward
- subject.is_reply
any of:
at least 3 of 4: body.html.display_text contains any of 4 patterns
from:to:sent:subject:
- length(body.previous_threads) > 0
- length(body.current_thread.text) + 100 < length(body.html.display_text)
not:
any of
attachmentswhere:- .content_type in ('message/delivery-status', 'message/rfc822')
any of:
- profile.by_sender().prevalence in ('new', 'rare')
- profile.by_sender().days_known > 30
not:
- profile.by_sender().any_messages_benign
Inspects: attachments[].content_type, body.current_thread.text, body.html.display_text, body.html.inner_text, body.plain.raw, body.previous_threads, headers.in_reply_to, headers.references, subject.is_forward, subject.is_reply, type.inbound. Sensors: ml.nlu_classifier, profile.by_sender, strings.icontains, strings.ilike. Reference lists: $org_vips.
Indicators matched (10)
| Field | Match | Value |
|---|---|---|
strings.ilike | substring | *charity* |
strings.ilike | substring | *gala* |
strings.ilike | substring | *donation* |
strings.ilike | substring | *donor* |
ml.nlu_classifier(body.current_thread.text).entities[].name | equals | financial |
ml.nlu_classifier(body.current_thread.text).entities[].name | equals | request |
strings.icontains | substring | from: |
strings.icontains | substring | to: |
strings.icontains | substring | sent: |
strings.icontains | substring | subject: |
Stages and Predicates
Stage 1: mql_rule
and
or
and
not
any(attachments)
attachments.content_type in ["message/delivery-status", "message/rfc822"]
not
or
subject.is_forward eq "true"
subject.is_reply eq "true"
or
body.html.display_text contains "from:"
body.html.display_text contains "sent:"
body.html.display_text contains "subject:"
body.html.display_text contains "to:"
body.previous_threads length_compare "0"
macro "(length(body.current_thread.text) + 100) < length(body.html.display_text)"
and
or
and
headers.in_reply_to is_null
headers.references length_compare "0"
headers.in_reply_to is_null
or
subject.is_forward eq "true"
subject.is_reply eq "true"
or
any($org_vips)
strings.icontains func_call "strings.icontains(body.html.inner_text)"
any($org_vips)
strings.icontains func_call "strings.icontains(body.plain.raw)"
or
body.current_thread.text match "charity"
body.current_thread.text match "donation"
body.current_thread.text match "donor"
body.current_thread.text match "gala"
any(ml.nlu_classifier(body.current_thread.text).entities)
ml.nlu_classifier(body.current_thread.text).entities.name eq "financial"
any(ml.nlu_classifier(body.current_thread.text).entities)
ml.nlu_classifier(body.current_thread.text).entities.name eq "request"
not
profile.by_sender func_call "profile.by_sender().any_messages_benign"
or
profile.by_sender func_call "profile.by_sender().days_known > 30"
profile.by_sender func_call "profile.by_sender().prevalence in (new, rare)"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
body.current_thread.text | wildcard |
| field:"body.current_thread.text" kind:wildcard |
body.html.display_text | contains |
| field:"body.html.display_text" kind:contains |
headers.in_reply_to | is_null | field:"headers.in_reply_to" kind:is_null | |
subject.is_forward | eq |
| field:"subject.is_forward" kind:eq value:"true" |
subject.is_reply | eq |
| field:"subject.is_reply" kind:eq value:"true" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: Fabricated thread history with fake VIP recipients
#Detects inbound messages that contain forged prior thread histories where the fake headers use abnormal spacing around colons - a hallmark of programmatically generated preambles. The fabricated threads reference VIP recipients from the organization who are absent from the live message's actual recipients, suggesting the thread was constructed to manufacture legitimacy. Observed messages impersonate finance or accounts payable workflows, referencing overdue invoices, balance statements, and payment requests targeting real vendors and internal stakeholders.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering, Evasion, Spoofing |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and any(filter(body.previous_threads,
// single recipient in the previous thread
length(.recipients.to) == 1
and length(.recipients.cc) == 0
// contains spaces around the colons of the "headers"
and strings.count(.preamble, ' : ') >= 2
and regex.icount(.preamble, '(?m)^\s*[a-z]+ +: +\S') == regex.icount(.preamble,
'(?m)^.'
)
),
// the previous thread with the goofy spaces
// includes a VIP as a recipient
any(map(filter(.recipients.to,
// via email
.email.email != ""
and any($org_vips,
strings.icontains(..email.email, .email)
or strings.icontains(..display_name,
.display_name
)
)
),
.email.email
),
// email is not in the "live" messages
not strings.icontains(sender.email.email, .)
and not any(recipients.to, strings.icontains(.email.email, ..))
and not any(recipients.cc, strings.icontains(.email.email, ..))
)
or any(map(filter(.recipients.to,
// via display_name
.email.email == ""
and any($org_vips,
strings.icontains(..display_name,
.display_name
)
)
),
.display_name
),
// display name is not in the "live" messages
not any(recipients.to, .display_name == ..)
and not any(recipients.cc, .display_name == ..)
and sender.display_name != .
)
)
Detection logic
Scope: inbound message.
Detects inbound messages that contain forged prior thread histories where the fake headers use abnormal spacing around colons - a hallmark of programmatically generated preambles. The fabricated threads reference VIP recipients from the organization who are absent from the live message's actual recipients, suggesting the thread was constructed to manufacture legitimacy. Observed messages impersonate finance or accounts payable workflows, referencing overdue invoices, balance statements, and payment requests targeting real vendors and internal stakeholders.
- inbound message
any of
filter(body.previous_threads)where any holds:any of
map(...)where all hold:not:
- strings.icontains(sender.email.email)
not:
any of
recipients.towhere:- strings.icontains(.email.email)
not:
any of
recipients.ccwhere:- strings.icontains(.email.email)
any of
map(...)where all hold:not:
any of
recipients.towhere:- .display_name is .
not:
any of
recipients.ccwhere:- .display_name is .
- sender.display_name is not .
Inspects: body.previous_threads, body.previous_threads[].preamble, body.previous_threads[].recipients.cc, body.previous_threads[].recipients.to, recipients.cc, recipients.cc[].display_name, recipients.cc[].email.email, recipients.to, recipients.to[].display_name, recipients.to[].email.email, sender.display_name, sender.email.email, type.inbound. Sensors: regex.icount, strings.count, strings.icontains. Reference lists: $org_vips.
Indicators matched (3)
| Field | Match | Value |
|---|---|---|
regex.icount | regex | (?m)^\s*[a-z]+ +: +\S |
regex.icount | regex | (?m)^. |
filter(body.previous_threads)[].recipients.to[].email.email | equals | |
Stages and Predicates
Stage 1: mql_rule
and
any(filter(body.previous_threads))
or
any(map(...))
and
not
any(recipients.cc)
recipients.cc.display_name cross_field_compare "map(...)"
not
any(recipients.to)
recipients.to.display_name cross_field_compare "map(...)"
sender.display_name cross_field_compare "map(...)"
any(map(...))
and
not
any(recipients.cc)
strings.icontains func_call "strings.icontains(recipients.cc[].email.email)"
not
any(recipients.to)
strings.icontains func_call "strings.icontains(recipients.to[].email.email)"
not
strings.icontains func_call "strings.icontains(sender.email.email)"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: Fake forwarded indicator with VIP recipient impersonation
#Detects inbound messages that fabricate a forwarded email thread — using patterns like '=== Forwarded Message:' — where a prior thread appears to involve an organizational VIP as a recipient. The messages observed follow an invoice lure pattern referencing admired leadership figures by name, designed to create the appearance of legitimacy by spoofing internal executive communication chains.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering, Evasion |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and length(coalesce(body.plain.raw, body.html.display_text)) < 6000
and (
// the current thread contains a goofy forwarded message
(
strings.icontains(body.current_thread.text, 'forwarded')
// not this specific format
and not strings.icontains(body.current_thread.text,
'============ Forwarded Message ============'
)
and regex.icontains(body.current_thread.text,
'(?m)^[=\x{2012}\x{2013}\x{2014}\x{2015}\s]*message that has been forwarded',
'={3,}\s*(?:forwarded message|message that has been forwarded)',
'[\x{2013}]{2,}\s*(?:forwarded message|message that has been forwarded)'
)
)
// any previous thread contains a goofy forwarded message
or any(body.previous_threads,
strings.icontains(.text, 'forwarded')
// not this specific format
and not strings.icontains(.text,
'============ Forwarded Message ============'
)
and regex.icontains(.text,
'(?m)^[=\x{2012}\x{2013}\x{2014}\x{2015}\s]*message that has been forwarded',
'={3,}\s*(?:forwarded message|message that has been forwarded)',
'[\x{2013}]{2,}\s*(?:forwarded message|message that has been forwarded)'
)
)
)
// any of the previous threads were sent to a VIP
and any(body.previous_threads,
any(.recipients.to,
any($org_vips,
strings.icontains(..display_name, .display_name)
or (.email != "" and strings.icontains(..email.email, .email))
)
)
)
Detection logic
Scope: inbound message.
Detects inbound messages that fabricate a forwarded email thread — using patterns like '=== Forwarded Message:' — where a prior thread appears to involve an organizational VIP as a recipient. The messages observed follow an invoice lure pattern referencing admired leadership figures by name, designed to create the appearance of legitimacy by spoofing internal executive communication chains.
- inbound message
- length(coalesce(body.plain.raw, body.html.display_text)) < 6000
any of:
all of:
- body.current_thread.text contains 'forwarded'
not:
- body.current_thread.text contains '============ Forwarded Message ============'
body.current_thread.text matches any of 3 patterns
(?m)^[=\x{2012}\x{2013}\x{2014}\x{2015}\s]*message that has been forwarded={3,}\s*(?:forwarded message|message that has been forwarded)[\x{2013}]{2,}\s*(?:forwarded message|message that has been forwarded)
any of
body.previous_threadswhere all hold:- .text contains 'forwarded'
not:
- .text contains '============ Forwarded Message ============'
.text matches any of 3 patterns
(?m)^[=\x{2012}\x{2013}\x{2014}\x{2015}\s]*message that has been forwarded={3,}\s*(?:forwarded message|message that has been forwarded)[\x{2013}]{2,}\s*(?:forwarded message|message that has been forwarded)
any of
body.previous_threadswhere:any of
.recipients.towhere:any of
$org_vipswhere any holds:- strings.icontains(.display_name)
all of:
- .email is not ''
- strings.icontains(.email.email)
Inspects: body.current_thread.text, body.html.display_text, body.plain.raw, body.previous_threads, body.previous_threads[].recipients.to, body.previous_threads[].recipients.to[].display_name, body.previous_threads[].recipients.to[].email.email, body.previous_threads[].text, type.inbound. Sensors: regex.icontains, strings.icontains. Reference lists: $org_vips.
Indicators matched (4)
| Field | Match | Value |
|---|---|---|
strings.icontains | substring | forwarded |
regex.icontains | regex | (?m)^[=\x{2012}\x{2013}\x{2014}\x{2015}\s]*message that has been forwarded |
regex.icontains | regex | ={3,}\s*(?:forwarded message|message that has been forwarded) |
regex.icontains | regex | [\x{2013}]{2,}\s*(?:forwarded message|message that has been forwarded) |
Stages and Predicates
Stage 1: mql_rule
and
any(body.previous_threads)
any(body.previous_threads.recipients.to)
any($org_vips)
or
and
$org_vips.email ne ""
strings.icontains func_call "strings.icontains(body.previous_threads[].recipients.to[].email.email)"
strings.icontains func_call "strings.icontains(body.previous_threads[].recipients.to[].display_name)"
or
any(body.previous_threads)
and
not
body.previous_threads.text contains "============ Forwarded Message ============"
or
body.previous_threads.text regex_match "(?m)^[=\\x{2012}\\x{2013}\\x{2014}\\x{2015}\\s]*message that has been forwarded"
body.previous_threads.text regex_match "={3,}\\s*(?:forwarded message|message that has been forwarded)"
body.previous_threads.text regex_match "[\\x{2013}]{2,}\\s*(?:forwarded message|message that has been forwarded)"
body.previous_threads.text contains "forwarded"
and
not
body.current_thread.text contains "============ Forwarded Message ============"
or
body.current_thread.text regex_match "(?m)^[=\\x{2012}\\x{2013}\\x{2014}\\x{2015}\\s]*message that has been forwarded"
body.current_thread.text regex_match "={3,}\\s*(?:forwarded message|message that has been forwarded)"
body.current_thread.text regex_match "[\\x{2013}]{2,}\\s*(?:forwarded message|message that has been forwarded)"
body.current_thread.text contains "forwarded"
coalesce(body.plain.raw, body.html.display_text) length_compare "6000"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
body.current_thread.text | contains |
| field:"body.current_thread.text" kind:contains value:"forwarded" |
body.current_thread.text | regex_match |
| field:"body.current_thread.text" kind:regex_match |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: Fake thread with display name match, email mismatch
#This rule is intended to detect fake threads that are impersonating a VIP. It looks for a matching $org_vips display name and checks the email address following it does not match what is in the $org_vips list.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Evasion, Impersonation: VIP, Social engineering, Spoofing |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and any($org_vips,
strings.icontains(body.html.display_text,
strings.concat("From: ", .display_name, " <")
)
and not strings.icontains(body.html.display_text,
strings.concat("From: ",
.display_name,
" <",
.email,
">"
)
)
)
and any([body.current_thread.text, body.html.display_text, body.plain.raw],
3 of (
strings.icontains(., "from:"),
strings.icontains(., "to:"),
strings.icontains(., "sent:"),
strings.icontains(., "date:"),
strings.icontains(., "cc:"),
strings.icontains(., "subject:")
)
)
and (length(headers.references) == 0 or headers.in_reply_to is null)
Detection logic
Scope: inbound message.
This rule is intended to detect fake threads that are impersonating a VIP. It looks for a matching $org_vips display name and checks the email address following it does not match what is in the $org_vips list.
- inbound message
any of
$org_vipswhere all hold:- strings.icontains(body.html.display_text)
not:
- strings.icontains(body.html.display_text)
any of
[body.current_thread.text, body.html.display_text, body.plain.raw]where:at least 3 of 6: . contains any of 6 patterns
from:to:sent:date:cc:subject:
any of:
- length(headers.references) is 0
- headers.in_reply_to is missing
Inspects: body.current_thread.text, body.html.display_text, body.plain.raw, headers.in_reply_to, headers.references, type.inbound. Sensors: strings.concat, strings.icontains. Reference lists: $org_vips.
Indicators matched (6)
| Field | Match | Value |
|---|---|---|
strings.icontains | substring | from: |
strings.icontains | substring | to: |
strings.icontains | substring | sent: |
strings.icontains | substring | date: |
strings.icontains | substring | cc: |
strings.icontains | substring | subject: |
Stages and Predicates
Stage 1: mql_rule
and
any($org_vips)
and
not
strings.icontains func_call "strings.icontains(body.html.display_text)"
strings.icontains func_call "strings.icontains(body.html.display_text)"
any([body.current_thread.text, body.html.display_text, body.plain.raw])
or
[body.current_thread.text, body.html.display_text, body.plain.raw] contains "cc:"
[body.current_thread.text, body.html.display_text, body.plain.raw] contains "date:"
[body.current_thread.text, body.html.display_text, body.plain.raw] contains "from:"
[body.current_thread.text, body.html.display_text, body.plain.raw] contains "sent:"
[body.current_thread.text, body.html.display_text, body.plain.raw] contains "subject:"
[body.current_thread.text, body.html.display_text, body.plain.raw] contains "to:"
or
headers.in_reply_to is_null
headers.references length_compare "0"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
headers.in_reply_to | is_null | field:"headers.in_reply_to" kind:is_null | |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: Fake thread with VIPs missing email metadata
#Detects inbound messages that weaponize fabricated invoice or payment thread histories to impersonate or involve organizational VIPs. The rule identifies conversations where the two oldest visible threads discuss invoices, payments, or executive engagements, but contain incomplete sender or recipient email addresses — a hallmark of stitched-together or forged thread context. The targeted VIP appears in the fabricated thread history but is conspicuously absent from the current message's recipients, suggesting the VIP's name is being leveraged to establish false legitimacy while routing the live message away from their oversight. Matched messages span executive search retainer invoices, past-due account notices, wire transfer instructions, and advisory billing lures targeting named executives at known organizations.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering, Spoofing |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
// we need at least two threads
and length(body.previous_threads) > 1
and (
// NLU on previous threads
( // oldest thread is invoice/payment related
(
any(ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text,
subject=body.previous_threads[length(body.previous_threads
) - 1].subject.base
).tags,
.name in ("invoice", "payment") and .confidence != "low"
)
or any(ml.nlu_classifier(body.previous_threads[length(body.previous_threads
) - 1].text,
subject=body.previous_threads[length(body.previous_threads
) - 1].subject.base
).topics,
.name in ("Request to View Invoice", "Payment Information")
and .confidence != "low"
)
)
// second to oldest thread is invoice/payment related
and (
any(ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text,
subject=body.previous_threads[length(body.previous_threads
) - 2].subject.base
).tags,
.name in ("invoice", "payment") and .confidence != "low"
)
or any(ml.nlu_classifier(body.previous_threads[length(body.previous_threads
) - 2].text,
subject=body.previous_threads[length(body.previous_threads
) - 2].subject.base
).topics,
.name in ("Request to View Invoice", "Payment Information")
and .confidence != "low"
)
)
)
// if we don't get NLU but there is a W9 or Inv attached, we can assume it's invoice related
or any(attachments, strings.istarts_with(.file_name, 'INV', "W-9", 'W9'))
)
// each of the two oldest threads have 1 or less recipients (sometimes the "to" header is missing)
and length(body.previous_threads[length(body.previous_threads) - 1].recipients.to
) <= 1
and length(body.previous_threads[length(body.previous_threads) - 2].recipients.to
) <= 1
// at least one party in the two oldest threads is missing an email
and (
body.previous_threads[length(body.previous_threads) - 1].sender.email.email == ""
or body.previous_threads[length(body.previous_threads) - 1].recipients.to[0].email.email == ""
or body.previous_threads[length(body.previous_threads) - 2].sender.email.email == ""
or body.previous_threads[length(body.previous_threads) - 2].recipients.to[0].email.email == ""
)
// an org vip sent/received messages
and any($org_vips,
(
body.previous_threads[length(body.previous_threads) - 1].recipients.to[0].display_name == .display_name
and body.previous_threads[length(body.previous_threads) - 2].sender.display_name == .display_name
)
or (
body.previous_threads[length(body.previous_threads) - 1].sender.display_name == .display_name
and body.previous_threads[length(body.previous_threads) - 2].recipients.to[0].display_name == .display_name
)
)
// the VIP is no longer in the current message
and not any(flatten([recipients.to, recipients.cc, recipients.bcc]),
any($org_vips,
.email != ""
and strings.icontains(..email.email, .email)
and (
(
body.previous_threads[length(body.previous_threads) - 1].recipients.to[0].display_name == .display_name
and body.previous_threads[length(body.previous_threads) - 2].sender.display_name == .display_name
)
or (
body.previous_threads[length(body.previous_threads) - 1].sender.display_name == .display_name
and body.previous_threads[length(body.previous_threads) - 2].recipients.to[0].display_name == .display_name
)
)
)
)
// not from authorized senders on the org's domain
and not (
sender.email.domain.domain in $org_domains
and coalesce(headers.auth_summary.dmarc.pass, false)
)
Detection logic
Scope: inbound message.
Detects inbound messages that weaponize fabricated invoice or payment thread histories to impersonate or involve organizational VIPs. The rule identifies conversations where the two oldest visible threads discuss invoices, payments, or executive engagements, but contain incomplete sender or recipient email addresses — a hallmark of stitched-together or forged thread context. The targeted VIP appears in the fabricated thread history but is conspicuously absent from the current message's recipients, suggesting the VIP's name is being leveraged to establish false legitimacy while routing the live message away from their oversight. Matched messages span executive search retainer invoices, past-due account notices, wire transfer instructions, and advisory billing lures targeting named executives at known organizations.
- inbound message
- length(body.previous_threads) > 1
any of:
all of:
any of:
any of
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).tagswhere all hold:- .name in ('invoice', 'payment')
- .confidence is not 'low'
any of
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).topicswhere all hold:- .name in ('Request to View Invoice', 'Payment Information')
- .confidence is not 'low'
any of:
any of
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).tagswhere all hold:- .name in ('invoice', 'payment')
- .confidence is not 'low'
any of
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).topicswhere all hold:- .name in ('Request to View Invoice', 'Payment Information')
- .confidence is not 'low'
any of
attachmentswhere:.file_name starts with any of 3 patterns
INVW-9W9
- length(body.previous_threads[].recipients.to) ≤ 1
- length(body.previous_threads[].recipients.to) ≤ 1
any of:
- body.previous_threads[].sender.email.email is ''
- body.previous_threads[].recipients.to[0].email.email is ''
- body.previous_threads[].sender.email.email is ''
- body.previous_threads[].recipients.to[0].email.email is ''
any of
$org_vipswhere any holds:all of:
- body.previous_threads[].recipients.to[0].display_name is .display_name
- body.previous_threads[].sender.display_name is .display_name
all of:
- body.previous_threads[].sender.display_name is .display_name
- body.previous_threads[].recipients.to[0].display_name is .display_name
not:
any of
flatten(...)where:any of
$org_vipswhere all hold:- .email is not ''
- strings.icontains(.email.email)
any of:
all of:
- body.previous_threads[].recipients.to[0].display_name is .display_name
- body.previous_threads[].sender.display_name is .display_name
all of:
- body.previous_threads[].sender.display_name is .display_name
- body.previous_threads[].recipients.to[0].display_name is .display_name
not:
all of:
- sender.email.domain.domain in $org_domains
- coalesce(headers.auth_summary.dmarc.pass)
Inspects: attachments[].file_name, body.previous_threads, body.previous_threads[].recipients.to, body.previous_threads[].recipients.to[0].display_name, body.previous_threads[].recipients.to[0].email.email, body.previous_threads[].sender.display_name, body.previous_threads[].sender.email.email, body.previous_threads[].subject.base, body.previous_threads[].text, headers.auth_summary.dmarc.pass, recipients.bcc, recipients.cc, recipients.to, sender.email.domain.domain, type.inbound. Sensors: ml.nlu_classifier, strings.icontains, strings.istarts_with. Reference lists: $org_domains, $org_vips.
Indicators matched (13)
| Field | Match | Value |
|---|---|---|
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).tags[].name | member | invoice |
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).tags[].name | member | payment |
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).topics[].name | member | Request to View Invoice |
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).topics[].name | member | Payment Information |
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).tags[].name | member | invoice |
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).tags[].name | member | payment |
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).topics[].name | member | Request to View Invoice |
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).topics[].name | member | Payment Information |
strings.istarts_with | prefix | INV |
strings.istarts_with | prefix | W-9 |
strings.istarts_with | prefix | W9 |
body.previous_threads[].sender.email.email | equals | |
1 more
body.previous_threads[].recipients.to[0].email.email | equals | |
Stages and Predicates
Stage 1: mql_rule
and
or
and
or
any(ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).tags)
and
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).tags.confidence ne "low"
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).tags.name in ["invoice", "payment"]
any(ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).topics)
and
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).topics.confidence ne "low"
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).topics.name in ["Payment Information", "Request to View Invoice"]
or
any(ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).tags)
and
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).tags.confidence ne "low"
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).tags.name in ["invoice", "payment"]
any(ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).topics)
and
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).topics.confidence ne "low"
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).topics.name in ["Payment Information", "Request to View Invoice"]
any(attachments)
or
attachments.file_name starts_with "INV"
attachments.file_name starts_with "W-9"
attachments.file_name starts_with "W9"
not
any(flatten(...))
any($org_vips)
and
$org_vips.email ne ""
body.previous_threads[].recipients.to[0].display_name cross_field_compare "$org_vips.display_name"
body.previous_threads[].sender.display_name cross_field_compare "$org_vips.display_name"
strings.icontains func_call "strings.icontains(flatten(...)[].email.email)"
any($org_vips)
and
body.previous_threads[].recipients.to[0].display_name cross_field_compare "$org_vips.display_name"
body.previous_threads[].sender.display_name cross_field_compare "$org_vips.display_name"
not
and
coalesce func_call "coalesce(headers.auth_summary.dmarc.pass)"
macro "sender.email.domain.domain in org_domains"
or
body.previous_threads[].recipients.to[0].email.email eq ""
body.previous_threads[].sender.email.email eq ""
body.previous_threads length_compare "1"
body.previous_threads[].recipients.to length_compare "1"
type.inbound eq "true"Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
flatten(...) | array_any | excludes:flatten(...) |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: Invoice fraud with mobile device sign-off
#Detects inbound messages where a prior thread contains a reply from a known VIP - identifiable by display name or email address and signed off with a 'Sent from my iPhone' or 'Sent from my iPad' footer. However that VIP has been silently dropped from the current message's recipients. This pattern is consistent with fraudulent invoice and payment request lures where an attacker impersonates an internal executive in a thread, then removes them before requesting payment action from the remaining recipients.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and any(map(filter(body.previous_threads,
any($org_vips,
strings.icontains(..sender.display_name, .display_name)
or (
.email != ""
and strings.icontains(..sender.email.email, .email)
)
)
and (
strings.iends_with(.text, "sent from my iphone.")
or strings.iends_with(.text, "sent from my ipad.")
)
and (
// any previous thread authored by the "VIP" has invoice/payment
any(ml.nlu_classifier(.text, subject=.subject.base).tags,
.name in ("invoice", "payment")
and .confidence != "low"
)
or any(ml.nlu_classifier(.text, subject=.subject.base).topics,
.name in (
"Request to View Invoice",
"Payment Information"
)
and .confidence != "low"
)
// if we don't get NLU but there is a W9 or Inv attached, we can assume it's invoice related
or any(attachments,
strings.istarts_with(.file_name, 'INV', "W-9", 'W9')
)
)
),
.sender.email.email
),
. == ""
or (
not strings.icontains(sender.email.email, .)
and not any(flatten([recipients.to, recipients.cc, recipients.bcc]),
strings.icontains(.email.email, ..)
)
)
)
Detection logic
Scope: inbound message.
Detects inbound messages where a prior thread contains a reply from a known VIP - identifiable by display name or email address and signed off with a 'Sent from my iPhone' or 'Sent from my iPad' footer. However that VIP has been silently dropped from the current message's recipients. This pattern is consistent with fraudulent invoice and payment request lures where an attacker impersonates an internal executive in a thread, then removes them before requesting payment action from the remaining recipients.
- inbound message
any of
map(...)where any holds:- . is ''
all of:
not:
- strings.icontains(sender.email.email)
not:
any of
flatten(...)where:- strings.icontains(.email.email)
Inspects: attachments[].file_name, body.previous_threads, body.previous_threads[].sender.display_name, body.previous_threads[].sender.email.email, body.previous_threads[].subject.base, body.previous_threads[].text, recipients.bcc, recipients.cc, recipients.to, sender.email.email, type.inbound. Sensors: ml.nlu_classifier, strings.icontains, strings.iends_with, strings.istarts_with. Reference lists: $org_vips.
Indicators matched (10)
| Field | Match | Value |
|---|---|---|
strings.iends_with | suffix | sent from my iphone. |
strings.iends_with | suffix | sent from my ipad. |
ml.nlu_classifier(body.previous_threads[].text, subject=body.previous_threads[].subject.base).tags[].name | member | invoice |
ml.nlu_classifier(body.previous_threads[].text, subject=body.previous_threads[].subject.base).tags[].name | member | payment |
ml.nlu_classifier(body.previous_threads[].text, subject=body.previous_threads[].subject.base).topics[].name | member | Request to View Invoice |
ml.nlu_classifier(body.previous_threads[].text, subject=body.previous_threads[].subject.base).topics[].name | member | Payment Information |
strings.istarts_with | prefix | INV |
strings.istarts_with | prefix | W-9 |
strings.istarts_with | prefix | W9 |
map(...)[] | equals | |
Stages and Predicates
Stage 1: mql_rule
and
any(map(...))
or
and
not
any(flatten(...))
strings.icontains func_call "strings.icontains(flatten(...)[].email.email)"
not
strings.icontains func_call "strings.icontains(sender.email.email)"
map(...) eq ""
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: Payment handoff with VIP display name authored fake threads
#Detects inbound messages where an external sender, absent from the original conversation, targets a single internal recipient after a VIP previously instructed that party to handle invoice or payment correspondence. The rule identifies prior threads where a known VIP explicitly redirected billing or payment contact to an internal address — using language such as forwarding instructions, accounts payable references, or billing handoff phrasing — and flags follow-up messages from external senders who are no longer accompanied by the VIP. NLU classifiers confirm financial context in the prior thread. This pattern is consistent with adversaries monitoring or fabricating invoice-related threads to insert themselves at the point of payment handoff.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
// note to rule writers
// this rule has a related rule which covers the same logic, but handles the "email address" but no display name in preivous threads
// very likely the logic will need updated in the corresponding rule
// see vip_impersonation_fake_thread_with_invoice_handoff_email.yml
type.inbound
and any(map(filter(body.previous_threads,
.sender.email.email == ""
and .sender.display_name != ""
and any($org_vips,
strings.icontains(..sender.display_name,
.display_name
)
)
and not coalesce(.subject.is_auto_reply, false)
and any(recipients.to,
.email.domain.root_domain in $org_domains
and strings.icontains(..text, .email.email)
)
// payment "handoff" phrasing.
and regex.icontains(.text,
'accounts? payable',
'(?:forward|send|rout|direct|remit|submit|issue) (?:it|a copy) (?:directly )?to',
'(?:forward|send|direct|remit|submit|route) (?:the |all |related |for )?(?:invoice|correspondence|payment|billing|processing)',
'for payment processing,? please contact',
'(?:please )?direct (?:it|all|the)[^\n]{0,60}\bto\b',
'(?:as follows|provided below|find below|details are|contact is)[^\n]{0,10}:',
'billing (?:contact|correspondence|team|department)',
'a copy.{0,20}sent to',
)
),
.sender.display_name
),
. != ""
// any previous thread authored by the "VIP" has invoice/payment
and (
any(filter(body.previous_threads, .sender.email.email == ..),
any(ml.nlu_classifier(.text, subject=.subject.base).tags,
.name in ("invoice", "payment") and .confidence != "low"
)
or any(ml.nlu_classifier(.text, subject=.subject.base).topics,
.name in ("Request to View Invoice", "Payment Information")
and .confidence != "low"
)
)
// if we don't get NLU but there is a W9 or Inv attached, we can assume it's invoice related
or any(attachments,
strings.istarts_with(.file_name, 'INV', "W-9", 'W9')
)
)
and not any(flatten([recipients.to, recipients.cc, recipients.bcc]),
any($org_vips,
strings.icontains(..., .display_name)
and .email != ""
and strings.icontains(..email.email, .email)
)
)
)
and length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]),
.email.domain.root_domain in $org_domains
)
) == 1
and not (
sender.email.domain.root_domain in $org_domains
and coalesce(headers.auth_summary.dmarc.pass, false)
)
Detection logic
Scope: inbound message.
Detects inbound messages where an external sender, absent from the original conversation, targets a single internal recipient after a VIP previously instructed that party to handle invoice or payment correspondence. The rule identifies prior threads where a known VIP explicitly redirected billing or payment contact to an internal address — using language such as forwarding instructions, accounts payable references, or billing handoff phrasing — and flags follow-up messages from external senders who are no longer accompanied by the VIP. NLU classifiers confirm financial context in the prior thread. This pattern is consistent with adversaries monitoring or fabricating invoice-related threads to insert themselves at the point of payment handoff.
- inbound message
any of
map(...)where all hold:- . is not ''
any of:
any of
filter(body.previous_threads)where any holds:any of
ml.nlu_classifier(.text, subject=.subject.base).tagswhere all hold:- .name in ('invoice', 'payment')
- .confidence is not 'low'
any of
ml.nlu_classifier(.text, subject=.subject.base).topicswhere all hold:- .name in ('Request to View Invoice', 'Payment Information')
- .confidence is not 'low'
any of
attachmentswhere:.file_name starts with any of 3 patterns
INVW-9W9
not:
any of
flatten(...)where:any of
$org_vipswhere all hold:- strings.icontains(.)
- .email is not ''
- strings.icontains(.email.email)
- length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains)) is 1
not:
all of:
- sender.email.domain.root_domain in $org_domains
- coalesce(headers.auth_summary.dmarc.pass)
Inspects: attachments[].file_name, body.previous_threads, body.previous_threads[].sender.display_name, body.previous_threads[].sender.email.email, body.previous_threads[].subject.is_auto_reply, body.previous_threads[].text, headers.auth_summary.dmarc.pass, recipients.bcc, recipients.cc, recipients.to, recipients.to[].email.domain.root_domain, recipients.to[].email.email, sender.email.domain.root_domain, type.inbound. Sensors: ml.nlu_classifier, regex.icontains, strings.icontains, strings.istarts_with. Reference lists: $org_domains, $org_vips.
Indicators matched (16)
| Field | Match | Value |
|---|---|---|
body.previous_threads[].sender.email.email | equals | |
regex.icontains | regex | accounts? payable |
regex.icontains | regex | (?:forward|send|rout|direct|remit|submit|issue) (?:it|a copy) (?:directly )?to |
regex.icontains | regex | (?:forward|send|direct|remit|submit|route) (?:the |all |related |for )?(?:invoice|correspondence|payment|billing|processing) |
regex.icontains | regex | for payment processing,? please contact |
regex.icontains | regex | (?:please )?direct (?:it|all|the)[^\n]{0,60}\bto\b |
regex.icontains | regex | (?:as follows|provided below|find below|details are|contact is)[^\n]{0,10}: |
regex.icontains | regex | billing (?:contact|correspondence|team|department) |
regex.icontains | regex | a copy.{0,20}sent to |
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].name | member | invoice |
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].name | member | payment |
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].name | member | Request to View Invoice |
4 more
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].name | member | Payment Information |
strings.istarts_with | prefix | INV |
strings.istarts_with | prefix | W-9 |
strings.istarts_with | prefix | W9 |
Stages and Predicates
Stage 1: mql_rule
and
any(map(...))
and
or
any(filter(body.previous_threads))
or
any(ml.nlu_classifier(filter(body.previous_threads).text, subject=filter(body.previous_threads).subject.base).tags)
and
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].confidence ne "low"
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].name in ["invoice", "payment"]
any(ml.nlu_classifier(filter(body.previous_threads).text, subject=filter(body.previous_threads).subject.base).topics)
and
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].confidence ne "low"
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].name in ["Payment Information", "Request to View Invoice"]
any(attachments)
or
attachments.file_name starts_with "INV"
attachments.file_name starts_with "W-9"
attachments.file_name starts_with "W9"
not
any(flatten(...))
any($org_vips)
and
$org_vips.email ne ""
strings.icontains func_call "strings.icontains(flatten(...)[].email.email)"
strings.icontains func_call "strings.icontains(map(...)[])"
map(...) ne ""
not
and
coalesce func_call "coalesce(headers.auth_summary.dmarc.pass)"
macro "sender.email.domain.root_domain in org_domains"
filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains) length_compare "1"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP Impersonation: VIP handoff with fake forwarded invoice thread
#Flags inbound messages that fabricate a forwarded thread to impersonate a VIP and redirect payment on a fake past-due invoice. The oldest thread segment shows a forward addressed to the VIP, immediately preceded by a message purportedly from that same VIP containing a mailto link pointing to an attacker-controlled address. The live message routes to that mailto address instead of the VIP's legitimate address, which has been dropped from the recipient list, while only a single organizational recipient remains. This pattern is consistent with invoice or overdue-account lures that exploit a hijacked thread to trick a target into paying a fraudulent account.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering, Spoofing, Out of band pivot |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and length(body.previous_threads) > 1
// the oldest segment is a forward and has a single recipient which is the VIP
and length(body.previous_threads[length(body.previous_threads) - 1].recipients.to
) == 1
// the segment is a forward
// handle where there is no subject
and coalesce(body.previous_threads[length(body.previous_threads) - 1].subject.is_forward,
false
)
and any($org_vips,
// oldest thread (len-1) forward is TO the VIP
body.previous_threads[length(body.previous_threads) - 1].recipients.to[0].display_name =~ .display_name
// next oldest thread (len-2) is FROM the same VIP;
and body.previous_threads[length(body.previous_threads) - 2].sender.display_name =~ .display_name
)
// the rcpt email format of the oldest thread matches the sender email format of the next oldest thread
and body.previous_threads[length(body.previous_threads) - 1].recipients.to[0].email.email =~ body.previous_threads[length(body.previous_threads
) - 2].sender.email.email
// the message authored by the "VIP" links to an email (parsed mailto:) that the live message now routes to
// this is the "VIP handoff" step, the fake VIP mentions the email address, which becomes the "victim", the actor sends it to that address and includes the faked emails emails in the chain.
and any(body.previous_threads[length(body.previous_threads) - 2].links,
.href_url.url != ""
and .href_url.scheme == "mailto"
and any(flatten([recipients.to, recipients.cc, recipients.bcc]),
strings.icontains(.email.email, ..href_url.url)
)
)
// the VIP's authoritative list-email is NOT among the live recipients (dropped)
and not any(flatten([recipients.to, recipients.cc, recipients.bcc]),
any($org_vips,
.email != ""
and strings.icontains(..email.email, .email)
and body.previous_threads[length(body.previous_threads) - 2].sender.display_name =~ .display_name
)
)
// we only have a single org_domain recipient
and length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]),
.email.domain.root_domain in $org_domains
)
) == 1
// this message is not from the org itself
and not (
sender.email.domain.root_domain in $org_domains
and coalesce(headers.auth_summary.dmarc.pass, false)
)
Detection logic
Scope: inbound message.
Flags inbound messages that fabricate a forwarded thread to impersonate a VIP and redirect payment on a fake past-due invoice. The oldest thread segment shows a forward addressed to the VIP, immediately preceded by a message purportedly from that same VIP containing a mailto link pointing to an attacker-controlled address. The live message routes to that mailto address instead of the VIP's legitimate address, which has been dropped from the recipient list, while only a single organizational recipient remains. This pattern is consistent with invoice or overdue-account lures that exploit a hijacked thread to trick a target into paying a fraudulent account.
- inbound message
- length(body.previous_threads) > 1
- length(body.previous_threads[].recipients.to) is 1
- coalesce(body.previous_threads[].subject.is_forward)
any of
$org_vipswhere all hold:- body.previous_threads[].recipients.to[0].display_name is .display_name
- body.previous_threads[].sender.display_name is .display_name
- body.previous_threads[].recipients.to[0].email.email is body.previous_threads[].sender.email.email
any of
body.previous_threads[].linkswhere all hold:- .href_url.url is not ''
- .href_url.scheme is 'mailto'
any of
flatten(...)where:- strings.icontains(.email.email)
not:
any of
flatten(...)where:any of
$org_vipswhere all hold:- .email is not ''
- strings.icontains(.email.email)
- body.previous_threads[].sender.display_name is .display_name
- length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains)) is 1
not:
all of:
- sender.email.domain.root_domain in $org_domains
- coalesce(headers.auth_summary.dmarc.pass)
Inspects: body.previous_threads, body.previous_threads[].links, body.previous_threads[].links[].href_url.scheme, body.previous_threads[].links[].href_url.url, body.previous_threads[].recipients.to, body.previous_threads[].recipients.to[0].display_name, body.previous_threads[].recipients.to[0].email.email, body.previous_threads[].sender.display_name, body.previous_threads[].sender.email.email, body.previous_threads[].subject.is_forward, headers.auth_summary.dmarc.pass, recipients.bcc, recipients.cc, recipients.to, sender.email.domain.root_domain, type.inbound. Sensors: strings.icontains. Reference lists: $org_domains, $org_vips.
Indicators matched (1)
| Field | Match | Value |
|---|---|---|
body.previous_threads[].links[].href_url.scheme | equals | mailto |
Stages and Predicates
Stage 1: mql_rule
and
not
any(flatten(...))
any($org_vips)
and
$org_vips.email ne ""
body.previous_threads[].sender.display_name cross_field_compare "$org_vips.display_name"
strings.icontains func_call "strings.icontains(flatten(...)[].email.email)"
any(body.previous_threads.links)
and
any(flatten(...))
strings.icontains func_call "strings.icontains(flatten(...)[].email.email)"
body.previous_threads[].links[].href_url.scheme eq "mailto"
body.previous_threads[].links[].href_url.url ne ""
any($org_vips)
and
body.previous_threads[].recipients.to[0].display_name cross_field_compare "$org_vips.display_name"
body.previous_threads[].sender.display_name cross_field_compare "$org_vips.display_name"
not
and
coalesce func_call "coalesce(headers.auth_summary.dmarc.pass)"
macro "sender.email.domain.root_domain in org_domains"
body.previous_threads length_compare "1"
body.previous_threads[].recipients.to length_compare "1"
body.previous_threads[].recipients.to[0].email.email cross_field_compare "body.previous_threads[].sender.email.email"
coalesce func_call "coalesce(body.previous_threads[].subject.is_forward)"
filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains) length_compare "1"
type.inbound eq "true"Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
flatten(...) | array_any | excludes:flatten(...) |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
body.previous_threads[].recipients.to[0].email.email | cross_field_compare |
| field:"body.previous_threads[].recipients.to[0].email.email" kind:cross_field_compare value:"body.previous_threads[].sender.email.email" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: VIP name within a delimited subject with fake previous threads
#Detects inbound messages where a known VIP's display name appears in the subject line surrounded by quotation marks or forward-slash delimiters (e.g., /Name/, Name (in quotes), -//Name), and where prior thread history confirms the VIP either authored a previous message or is being actively impersonated by a mismatched sender. Observed lures include fabricated invoices, leadership recognition offers, and forwarded billing threads referencing real executives by name and company. The pattern spans multiple sender domains and spoofed invoice references, all anchored to VIP identity signals extracted from thread context.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering, Spoofing |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and length(coalesce(body.plain.raw, body.html.display_text)) < 6000
and strings.icontains(strings.replace_confusables(subject.base),
'"',
'/-/',
'-//',
'-/',
'-#'
)
and any(body.previous_threads,
any($org_vips,
(
strings.icontains(strings.replace_confusables(subject.base),
strings.concat('"', .display_name)
)
or strings.icontains(strings.replace_confusables(subject.base),
strings.concat(.display_name, '"')
)
or strings.icontains(strings.replace_confusables(subject.base),
strings.concat('/-/', .display_name)
)
or strings.icontains(strings.replace_confusables(subject.base),
strings.concat('-//', .display_name)
)
or strings.icontains(strings.replace_confusables(subject.base),
strings.concat('-/', .display_name)
)
or strings.icontains(strings.replace_confusables(subject.base),
strings.concat('-#', .display_name)
)
)
and (
strings.icontains(..sender.display_name, .display_name)
or (
.email != "" and strings.icontains(..sender.email.email, .email)
)
or (
strings.icontains(sender.display_name, .display_name)
and sender.email.email != .email
)
)
)
)
and any(ml.nlu_classifier(body.current_thread.text).topics,
.name in (
"Financial Communications",
"Request to View Invoice",
"Payment Information"
)
and .confidence != "low"
)
Detection logic
Scope: inbound message.
Detects inbound messages where a known VIP's display name appears in the subject line surrounded by quotation marks or forward-slash delimiters (e.g., /Name/, Name (in quotes), -//Name), and where prior thread history confirms the VIP either authored a previous message or is being actively impersonated by a mismatched sender. Observed lures include fabricated invoices, leadership recognition offers, and forwarded billing threads referencing real executives by name and company. The pattern spans multiple sender domains and spoofed invoice references, all anchored to VIP identity signals extracted from thread context.
- inbound message
- length(coalesce(body.plain.raw, body.html.display_text)) < 6000
strings.replace_confusables(subject.base) contains any of 5 patterns
"/-/-//-/-#
any of
body.previous_threadswhere:any of
$org_vipswhere all hold:any of:
- strings.icontains(strings.replace_confusables(subject.base))
- strings.icontains(strings.replace_confusables(subject.base))
- strings.icontains(strings.replace_confusables(subject.base))
- strings.icontains(strings.replace_confusables(subject.base))
- strings.icontains(strings.replace_confusables(subject.base))
- strings.icontains(strings.replace_confusables(subject.base))
any of:
- strings.icontains(.sender.display_name)
all of:
- .email is not ''
- strings.icontains(.sender.email.email)
all of:
- strings.icontains(sender.display_name)
- sender.email.email is not .email
any of
ml.nlu_classifier(body.current_thread.text).topicswhere all hold:- .name in ('Financial Communications', 'Request to View Invoice', 'Payment Information')
- .confidence is not 'low'
Inspects: body.current_thread.text, body.html.display_text, body.plain.raw, body.previous_threads, body.previous_threads[].sender.display_name, body.previous_threads[].sender.email.email, sender.display_name, sender.email.email, subject.base, type.inbound. Sensors: ml.nlu_classifier, strings.concat, strings.icontains, strings.replace_confusables. Reference lists: $org_vips.
Indicators matched (8)
| Field | Match | Value |
|---|---|---|
strings.icontains | substring | " |
strings.icontains | substring | /-/ |
strings.icontains | substring | -// |
strings.icontains | substring | -/ |
strings.icontains | substring | -# |
ml.nlu_classifier(body.current_thread.text).topics[].name | member | Financial Communications |
ml.nlu_classifier(body.current_thread.text).topics[].name | member | Request to View Invoice |
ml.nlu_classifier(body.current_thread.text).topics[].name | member | Payment Information |
Stages and Predicates
Stage 1: mql_rule
and
any(body.previous_threads)
any($org_vips)
and
or
and
$org_vips.email ne ""
strings.icontains func_call "strings.icontains(body.previous_threads[].sender.email.email)"
and
sender.email.email cross_field_compare "$org_vips.email"
strings.icontains func_call "strings.icontains(sender.display_name)"
strings.icontains func_call "strings.icontains(body.previous_threads[].sender.display_name)"
strings.icontains func_call "strings.icontains(strings.replace_confusables(subject.base))"
any(ml.nlu_classifier(body.current_thread.text).topics)
and
ml.nlu_classifier(body.current_thread.text).topics.confidence ne "low"
ml.nlu_classifier(body.current_thread.text).topics.name in ["Financial Communications", "Payment Information", "Request to View Invoice"]
or
strings.replace_confusables(subject.base) contains "-#"
strings.replace_confusables(subject.base) contains "-/"
strings.replace_confusables(subject.base) contains "-//"
strings.replace_confusables(subject.base) contains "/-/"
strings.replace_confusables(subject.base) contains "\""
coalesce(body.plain.raw, body.html.display_text) length_compare "6000"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
strings.replace_confusables(subject.base) | contains |
| field:"strings.replace_confusables(subject.base)" kind:contains |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: VIP payment redirect handoff via fake threads
#Detects inbound messages where an external sender, absent from the original conversation, targets a single internal recipient after a VIP previously instructed that party to handle invoice or payment correspondence. The rule identifies prior threads where a known VIP explicitly redirected billing or payment contact to an internal address — using language such as forwarding instructions, accounts payable references, or billing handoff phrasing — and flags follow-up messages from external senders who are no longer accompanied by the VIP. NLU classifiers confirm financial context in the prior thread. This pattern is consistent with adversaries monitoring or fabricating invoice-related threads to insert themselves at the point of payment handoff.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
// note to rule writers
// this rule has a related rule which covers the same logic, but handles the display name but no email address in preivous threads
// very likely the logic will need updated in the corresponding rule
// see vip_impersonation_fake_thread_with_invoice_handoff_display_name.yml
type.inbound
and any(map(filter(body.previous_threads,
// VIP authored the segment: display_name OR real email (OR, not AND -- the actor guesses
// the exec's address, so a fabricated From: is often the wrong format at the right org).
any($org_vips,
strings.icontains(..sender.display_name, .display_name)
or (
.email != ""
and strings.icontains(..sender.email.email, .email)
)
)
// skip auto-replies; coalesce the null on subject-less segments before `not`.
and not coalesce(.subject.is_auto_reply, false)
// the live recipient is the payee the VIP names, at an org domain.
and any(recipients.to,
.email.email != ..sender.email.email
and .email.domain.root_domain in $org_domains
and strings.icontains(..text, .email.email)
)
// payment "handoff" phrasing.
and regex.icontains(.text,
'accounts? payable',
'(?:forward|send|rout|direct|remit|submit|issue) (?:it|a copy) (?:directly )?to',
'(?:forward|send|direct|remit|submit|route) (?:the |all |related |for )?(?:invoice|correspondence|payment|billing|processing)',
'for payment processing,? please contact',
'(?:please )?direct (?:it|all|the)[^\n]{0,60}\bto\b',
'(?:as follows|provided below|find below|details are|contact is)[^\n]{0,10}:',
'billing (?:contact|correspondence|team|department)',
'a copy.{0,20}sent to',
)
),
.sender.email.email
),
. != ""
// the VIP is no longer present in the live message.
and not strings.icontains(sender.email.email, .)
and not any(flatten([recipients.to, recipients.cc, recipients.bcc]),
strings.icontains(.email.email, ..)
)
// any previous thread authored by the "VIP" has invoice/payment
and (
any(filter(body.previous_threads, .sender.email.email == ..),
any(ml.nlu_classifier(.text, subject=.subject.base).tags,
.name in ("invoice", "payment") and .confidence != "low"
)
or any(ml.nlu_classifier(.text, subject=.subject.base).topics,
.name in ("Request to View Invoice", "Payment Information")
and .confidence != "low"
)
)
// if we don't get NLU but there is a W9 or Inv attached, we can assume it's invoice related
or any(attachments,
strings.istarts_with(.file_name, 'INV', "W-9", 'W9')
)
)
)
// exactly one org-domain recipient (the payee); external sender.
and length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]),
.email.domain.root_domain in $org_domains
)
) == 1
and not sender.email.domain.root_domain in $org_domains
Detection logic
Scope: inbound message.
Detects inbound messages where an external sender, absent from the original conversation, targets a single internal recipient after a VIP previously instructed that party to handle invoice or payment correspondence. The rule identifies prior threads where a known VIP explicitly redirected billing or payment contact to an internal address — using language such as forwarding instructions, accounts payable references, or billing handoff phrasing — and flags follow-up messages from external senders who are no longer accompanied by the VIP. NLU classifiers confirm financial context in the prior thread. This pattern is consistent with adversaries monitoring or fabricating invoice-related threads to insert themselves at the point of payment handoff.
- inbound message
any of
map(...)where all hold:- . is not ''
not:
- strings.icontains(sender.email.email)
not:
any of
flatten(...)where:- strings.icontains(.email.email)
any of:
any of
filter(body.previous_threads)where any holds:any of
ml.nlu_classifier(.text, subject=.subject.base).tagswhere all hold:- .name in ('invoice', 'payment')
- .confidence is not 'low'
any of
ml.nlu_classifier(.text, subject=.subject.base).topicswhere all hold:- .name in ('Request to View Invoice', 'Payment Information')
- .confidence is not 'low'
any of
attachmentswhere:.file_name starts with any of 3 patterns
INVW-9W9
- length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains)) is 1
not:
- sender.email.domain.root_domain in $org_domains
Inspects: attachments[].file_name, body.previous_threads, body.previous_threads[].sender.display_name, body.previous_threads[].sender.email.email, body.previous_threads[].subject.is_auto_reply, body.previous_threads[].text, recipients.bcc, recipients.cc, recipients.to, recipients.to[].email.domain.root_domain, recipients.to[].email.email, sender.email.domain.root_domain, sender.email.email, type.inbound. Sensors: ml.nlu_classifier, regex.icontains, strings.icontains, strings.istarts_with. Reference lists: $org_domains, $org_vips.
Indicators matched (15)
| Field | Match | Value |
|---|---|---|
regex.icontains | regex | accounts? payable |
regex.icontains | regex | (?:forward|send|rout|direct|remit|submit|issue) (?:it|a copy) (?:directly )?to |
regex.icontains | regex | (?:forward|send|direct|remit|submit|route) (?:the |all |related |for )?(?:invoice|correspondence|payment|billing|processing) |
regex.icontains | regex | for payment processing,? please contact |
regex.icontains | regex | (?:please )?direct (?:it|all|the)[^\n]{0,60}\bto\b |
regex.icontains | regex | (?:as follows|provided below|find below|details are|contact is)[^\n]{0,10}: |
regex.icontains | regex | billing (?:contact|correspondence|team|department) |
regex.icontains | regex | a copy.{0,20}sent to |
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].name | member | invoice |
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].name | member | payment |
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].name | member | Request to View Invoice |
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].name | member | Payment Information |
3 more
strings.istarts_with | prefix | INV |
strings.istarts_with | prefix | W-9 |
strings.istarts_with | prefix | W9 |
Stages and Predicates
Stage 1: mql_rule
and
any(map(...))
and
or
any(filter(body.previous_threads))
or
any(ml.nlu_classifier(filter(body.previous_threads).text, subject=filter(body.previous_threads).subject.base).tags)
and
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].confidence ne "low"
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].name in ["invoice", "payment"]
any(ml.nlu_classifier(filter(body.previous_threads).text, subject=filter(body.previous_threads).subject.base).topics)
and
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].confidence ne "low"
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].name in ["Payment Information", "Request to View Invoice"]
any(attachments)
or
attachments.file_name starts_with "INV"
attachments.file_name starts_with "W-9"
attachments.file_name starts_with "W9"
not
any(flatten(...))
strings.icontains func_call "strings.icontains(flatten(...)[].email.email)"
not
strings.icontains func_call "strings.icontains(sender.email.email)"
map(...) ne ""
not
macro "sender.email.domain.root_domain in org_domains"
filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains) length_compare "1"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP impersonation: VIP recipient of previous thread with HTML generator
#Detects inbound messages that impersonate invoice communications impersonating organizational VIPs, where the message appears in a thread previously involving VIP recipients. The rule identifies a specific template technique where the HTML body contains markers associated with 'Advanced HTML parser' tooling or a suspicious '<title>HTML Message</title>' tag. Both are artifacts commonly left by tools used to craft or obfuscate invoice lure content. Observed samples spoof leadership-recognition invoice themes naming specific executives as the invoiced party.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Impersonation: VIP, Social engineering, Evasion |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and any(body.previous_threads,
any(.recipients.to,
any($org_vips,
strings.icontains(..display_name, .display_name)
or strings.icontains(..email.email, .email)
)
)
)
and (
strings.icontains(body.html.raw, 'Advanced HTML parser')
or regex.icontains(body.html.raw, '<title>\s*HTML Message\s*</title>')
)
Detection logic
Scope: inbound message.
Detects inbound messages that impersonate invoice communications impersonating organizational VIPs, where the message appears in a thread previously involving VIP recipients. The rule identifies a specific template technique where the HTML body contains markers associated with 'Advanced HTML parser' tooling or a suspicious '<title>HTML Message</title>' tag. Both are artifacts commonly left by tools used to craft or obfuscate invoice lure content. Observed samples spoof leadership-recognition invoice themes naming specific executives as the invoiced party.
- inbound message
any of
body.previous_threadswhere:any of
.recipients.towhere:any of
$org_vipswhere any holds:- strings.icontains(.display_name)
- strings.icontains(.email.email)
any of:
- body.html.raw contains 'Advanced HTML parser'
- body.html.raw matches '<title>\\s*HTML Message\\s*</title>'
Inspects: body.html.raw, body.previous_threads, body.previous_threads[].recipients.to, body.previous_threads[].recipients.to[].display_name, body.previous_threads[].recipients.to[].email.email, type.inbound. Sensors: regex.icontains, strings.icontains. Reference lists: $org_vips.
Indicators matched (2)
| Field | Match | Value |
|---|---|---|
strings.icontains | substring | Advanced HTML parser |
regex.icontains | regex | <title>\s*HTML Message\s*</title> |
Stages and Predicates
Stage 1: mql_rule
and
any(body.previous_threads)
any(body.previous_threads.recipients.to)
any($org_vips)
or
strings.icontains func_call "strings.icontains(body.previous_threads[].recipients.to[].display_name)"
strings.icontains func_call "strings.icontains(body.previous_threads[].recipients.to[].email.email)"
or
body.html.raw contains "Advanced HTML parser"
body.html.raw regex_match "<title>\\s*HTML Message\\s*</title>"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
body.html.raw | contains |
| field:"body.html.raw" kind:contains value:"Advanced HTML parser" |
body.html.raw | regex_match |
| field:"body.html.raw" kind:regex_match value:"<title>\s*HTML Message\s*</title>" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
VIP local_part impersonation from unsolicited sender
#This rule identifies potential impersonation attempts involving the local part of an $org_vip email address. Specifically, it checks for cases where the local part of an $org_vip email (e.g., local_part@domain.com) appears with a different domain (e.g., local_part@foreigndomain.com). Additionally, the rule flags messages that match an $org_vip address exactly but fail authentication.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Tactics and techniques | Impersonation: VIP, Spoofing |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and strings.contains(sender.email.local_part, ".")
and any($org_vips,
strings.starts_with(.email, sender.email.local_part)
and (
sender.email.email != .email
or (
sender.email.email == .email and not headers.auth_summary.dmarc.pass
)
)
)
and (
// ignore personal <> work emails
// where the sender and mailbox's display name are the same
length(recipients.to) > 0
or length(recipients.cc) > 0
or sender.display_name != mailbox.display_name
)
// bounce-back negations
and not strings.like(sender.email.local_part,
"*postmaster*",
"*mailer-daemon*",
"*administrator*"
)
// negate org domains unless they fail DMARC authentication
and (
(
sender.email.domain.root_domain in $org_domains
and not headers.auth_summary.dmarc.pass
)
or sender.email.domain.root_domain not in $org_domains
)
// negate highly trusted sender domains unless they fail DMARC authentication
and (
(
sender.email.domain.root_domain in $high_trust_sender_root_domains
and not headers.auth_summary.dmarc.pass
)
or sender.email.domain.root_domain not in $high_trust_sender_root_domains
)
// sender profile
and (
not profile.by_sender_email().solicited
or not profile.by_sender_email().any_messages_benign
or (
profile.by_sender_email().any_messages_malicious_or_spam
and not profile.by_sender_email().any_messages_benign
)
or (
sender.email.domain.domain in $org_domains
and not coalesce(headers.auth_summary.dmarc.pass, false)
)
)
Detection logic
Scope: inbound message.
This rule identifies potential impersonation attempts involving the local part of an $org_vip email address. Specifically, it checks for cases where the local part of an $org_vip email (e.g., local_part@domain.com) appears with a different domain (e.g., local_part@foreigndomain.com). Additionally, the rule flags messages that match an $org_vip address exactly but fail authentication.
- inbound message
- sender.email.local_part contains '.'
any of
$org_vipswhere all hold:- strings.starts_with(.email)
any of:
- sender.email.email is not .email
all of:
- sender.email.email is .email
not:
- headers.auth_summary.dmarc.pass
any of:
- length(recipients.to) > 0
- length(recipients.cc) > 0
- sender.display_name is not mailbox.display_name
not:
sender.email.local_part matches any of 3 patterns
*postmaster**mailer-daemon**administrator*
any of:
all of:
- sender.email.domain.root_domain in $org_domains
not:
- headers.auth_summary.dmarc.pass
- sender.email.domain.root_domain not in $org_domains
any of:
all of:
- sender.email.domain.root_domain in $high_trust_sender_root_domains
not:
- headers.auth_summary.dmarc.pass
- sender.email.domain.root_domain not in $high_trust_sender_root_domains
any of:
not:
- profile.by_sender_email().solicited
not:
- profile.by_sender_email().any_messages_benign
all of:
- profile.by_sender_email().any_messages_malicious_or_spam
not:
- profile.by_sender_email().any_messages_benign
all of:
- sender.email.domain.domain in $org_domains
not:
- coalesce(headers.auth_summary.dmarc.pass)
Inspects: headers.auth_summary.dmarc.pass, mailbox.display_name, recipients.cc, recipients.to, sender.display_name, sender.email.domain.domain, sender.email.domain.root_domain, sender.email.email, sender.email.local_part, type.inbound. Sensors: profile.by_sender_email, strings.contains, strings.like, strings.starts_with. Reference lists: $high_trust_sender_root_domains, $org_domains, $org_vips.
Indicators matched (1)
| Field | Match | Value |
|---|---|---|
strings.contains | substring | . |
Stages and Predicates
Stage 1: mql_rule
and
any($org_vips)
and
or
and
not
headers.auth_summary.dmarc.pass eq "true"
sender.email.email cross_field_compare "$org_vips.email"
sender.email.email cross_field_compare "$org_vips.email"
strings.starts_with func_call "strings.starts_with($org_vips[].email)"
or
and
not
coalesce func_call "coalesce(headers.auth_summary.dmarc.pass)"
macro "sender.email.domain.domain in org_domains"
and
not
profile.by_sender_email func_call "profile.by_sender_email().any_messages_benign"
profile.by_sender_email func_call "profile.by_sender_email().any_messages_malicious_or_spam"
not
profile.by_sender_email func_call "profile.by_sender_email().any_messages_benign"
not
profile.by_sender_email func_call "profile.by_sender_email().solicited"
or
and
not
headers.auth_summary.dmarc.pass eq "true"
macro "sender.email.domain.root_domain in high_trust_sender_root_domains"
macro "sender.email.domain.root_domain not in high_trust_sender_root_domains"
or
and
not
headers.auth_summary.dmarc.pass eq "true"
macro "sender.email.domain.root_domain in org_domains"
macro "sender.email.domain.root_domain not in org_domains"
not
or
sender.email.local_part match "administrator"
sender.email.local_part match "mailer-daemon"
sender.email.local_part match "postmaster"
or
recipients.cc length_compare "0"
recipients.to length_compare "0"
sender.display_name cross_field_compare "mailbox.display_name"
sender.email.local_part contains "."
type.inbound eq "true"Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
sender.email.local_part | match | administrator | excludes:sender.email.local_part field:"sender.email.local_part" value:"administrator" |
sender.email.local_part | match | mailer-daemon | excludes:sender.email.local_part field:"sender.email.local_part" value:"mailer-daemon" |
sender.email.local_part | match | postmaster | excludes:sender.email.local_part field:"sender.email.local_part" value:"postmaster" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
sender.display_name | cross_field_compare |
| field:"sender.display_name" kind:cross_field_compare value:"mailbox.display_name" |
sender.email.local_part | contains |
| field:"sender.email.local_part" kind:contains value:"." |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |