Detection rules › Sublime MQL

Sublime MQL rules: vip

VIP / Executive impersonation (strict match, untrusted)

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • attachments
  • body
  • body.current_thread
  • headers
  • headers.auth_summary
  • headers.reply_to
  • mailbox
  • recipients
  • sender.email
  • type

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.

  1. inbound message
  2. any of:
    • any of $org_vips where:
      • .display_name is sender.display_name
    • all of:
      • any of $org_vips where all hold:
        • strings.istarts_with(sender.display_name)
        • length(sender.display_name) > length(.display_name)
      • any of ml.nlu_classifier(body.current_thread.text).intents where all hold:
        • .name is 'bec'
        • .confidence is 'high'
  3. any of:
    • length(recipients.to) > 0
    • length(recipients.cc) > 0
    • sender.display_name is not mailbox.display_name
  4. not:
    • sender.email.local_part matches any of 3 patterns
      • *postmaster*
      • *mailer-daemon*
      • *administrator*
  5. not:
    • any of attachments where:
      • .content_type in ('message/rfc822', 'message/delivery-status', 'text/calendar')
  6. 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_to where 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'
  7. 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
  8. 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)

FieldMatchValue
ml.nlu_classifier(body.current_thread.text).intents[].nameequalsbec
ml.nlu_classifier(body.current_thread.text).intents[].confidenceequalshigh

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.

FieldKindExcluded valuesSearch
attachmentsarray_any(no value, null check)excludes:attachments
sender.email.local_partmatchadministratorexcludes:sender.email.local_part field:"sender.email.local_part" value:"administrator"
sender.email.local_partmatchmailer-daemonexcludes:sender.email.local_part field:"sender.email.local_part" value:"mailer-daemon"
sender.email.local_partmatchpostmasterexcludes:sender.email.local_part field:"sender.email.local_part" value:"postmaster"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
sender.display_namecross_field_compare
  • mailbox.display_name transforms: op:ne
field:"sender.display_name" kind:cross_field_compare value:"mailbox.display_name"
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP / Executive impersonation in subject (untrusted)

#
Severity
medium
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • attachments
  • headers
  • headers.auth_summary
  • mailbox
  • recipients
  • recipients.to
  • sender.email
  • subject
  • type

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.

  1. inbound message
  2. any of $org_vips where all hold:
    • strings.contains(subject.subject)
    • .display_name contains ' '
  3. not:
    • all of:
      • length(recipients.to) is 1
      • all of recipients.to where:
        • any of $org_vips where all hold:
          • .email is .email.email
          • strings.contains(subject.subject)
          • .display_name contains ' '
  4. any of:
    • length(recipients.to) > 0
    • length(recipients.cc) > 0
    • sender.display_name is not mailbox.display_name
  5. not:
    • sender.email.local_part matches any of 3 patterns
      • *postmaster*
      • *mailer-daemon*
      • *administrator*
  6. not:
    • any of attachments where:
      • .content_type in ('message/rfc822', 'message/delivery-status', 'text/calendar')
  7. 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
  8. 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
  9. 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)

FieldMatchValue
strings.containssubstring

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.

FieldKindExcluded valuesSearch
attachmentsarray_any(no value, null check)excludes:attachments
recipients.tolength_compare1excludes:recipients.to field:"recipients.to" value:"1"
sender.email.local_partmatchadministratorexcludes:sender.email.local_part field:"sender.email.local_part" value:"administrator"
sender.email.local_partmatchmailer-daemonexcludes:sender.email.local_part field:"sender.email.local_part" value:"mailer-daemon"
sender.email.local_partmatchpostmasterexcludes:sender.email.local_part field:"sender.email.local_part" value:"postmaster"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
sender.display_namecross_field_compare
  • mailbox.display_name transforms: op:ne
field:"sender.display_name" kind:cross_field_compare value:"mailbox.display_name"
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP impersonation with charitable donation fraud

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: Employee, Impersonation: VIP, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • attachments
  • body
  • body.current_thread
  • body.html
  • body.plain
  • headers
  • subject
  • type

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.

  1. inbound message
  2. body.current_thread.text matches any of 4 patterns
    • *charity*
    • *gala*
    • *donation*
    • *donor*
  3. any of ml.nlu_classifier(body.current_thread.text).entities where:
    • .name is 'financial'
  4. any of ml.nlu_classifier(body.current_thread.text).entities where:
    • .name is 'request'
  5. any of:
    • any of $org_vips where:
      • strings.icontains(body.html.inner_text)
    • any of $org_vips where:
      • strings.icontains(body.plain.raw)
  6. 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 attachments where:
          • .content_type in ('message/delivery-status', 'message/rfc822')
  7. any of:
    • profile.by_sender().prevalence in ('new', 'rare')
    • profile.by_sender().days_known > 30
  8. 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)

FieldMatchValue
strings.ilikesubstring*charity*
strings.ilikesubstring*gala*
strings.ilikesubstring*donation*
strings.ilikesubstring*donor*
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsfinancial
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsrequest
strings.icontainssubstringfrom:
strings.icontainssubstringto:
strings.icontainssubstringsent:
strings.icontainssubstringsubject:

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.

VIP impersonation: Fabricated thread history with fake VIP recipients

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering, Evasion, Spoofing

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.previous_threads
  • recipients
  • recipients.cc
  • recipients.to
  • sender.email
  • type

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.

  1. inbound message
  2. 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.to where:
          • strings.icontains(.email.email)
      • not:
        • any of recipients.cc where:
          • strings.icontains(.email.email)
    • any of map(...) where all hold:
      • not:
        • any of recipients.to where:
          • .display_name is .
      • not:
        • any of recipients.cc where:
          • .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)

FieldMatchValue
regex.icountregex(?m)^\s*[a-z]+ +: +\S
regex.icountregex(?m)^.
filter(body.previous_threads)[].recipients.to[].email.emailequals

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.

FieldKindValuesSearch
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP impersonation: Fake forwarded indicator with VIP recipient impersonation

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering, Evasion

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • body.html
  • body.plain
  • body.previous_threads
  • type

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.

  1. inbound message
  2. length(coalesce(body.plain.raw, body.html.display_text)) < 6000
  3. 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_threads where 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)
  4. any of body.previous_threads where:
    • any of .recipients.to where:
      • any of $org_vips where 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)

FieldMatchValue
strings.icontainssubstringforwarded
regex.icontainsregex(?m)^[=\x{2012}\x{2013}\x{2014}\x{2015}\s]*message that has been forwarded
regex.icontainsregex={3,}\s*(?:forwarded message|message that has been forwarded)
regex.icontainsregex[\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.

FieldKindValuesSearch
body.current_thread.textcontains
  • forwarded
field:"body.current_thread.text" kind:contains value:"forwarded"
body.current_thread.textregex_match
  • (?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)
field:"body.current_thread.text" kind:regex_match
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP impersonation: Fake thread with display name match, email mismatch

#
Severity
medium
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesEvasion, Impersonation: VIP, Social engineering, Spoofing

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • body.html
  • body.plain
  • headers
  • type

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.

  1. inbound message
  2. any of $org_vips where all hold:
    • strings.icontains(body.html.display_text)
    • not:
      • strings.icontains(body.html.display_text)
  3. 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:
  4. 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)

FieldMatchValue
strings.icontainssubstringfrom:
strings.icontainssubstringto:
strings.icontainssubstringsent:
strings.icontainssubstringdate:
strings.icontainssubstringcc:
strings.icontainssubstringsubject:

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.

FieldKindValuesSearch
headers.in_reply_tois_null
  • (no value, null check)
field:"headers.in_reply_to" kind:is_null
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP impersonation: Fake thread with VIPs missing email metadata

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering, Spoofing

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • attachments
  • body
  • body.previous_threads
  • headers
  • headers.auth_summary
  • recipients
  • sender.email
  • type

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.

  1. inbound message
  2. length(body.previous_threads) > 1
  3. 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).tags where 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).topics where 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).tags where 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).topics where all hold:
          • .name in ('Request to View Invoice', 'Payment Information')
          • .confidence is not 'low'
    • any of attachments where:
      • .file_name starts with any of 3 patterns
        • INV
        • W-9
        • W9
  4. length(body.previous_threads[].recipients.to) ≤ 1
  5. length(body.previous_threads[].recipients.to) ≤ 1
  6. 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 ''
  7. any of $org_vips where 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
  8. not:
    • any of flatten(...) where:
      • any of $org_vips where 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
  9. 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)

FieldMatchValue
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).tags[].namememberinvoice
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).tags[].namememberpayment
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 1].text, subject=body.previous_threads[length(body.previous_threads) - 1].subject.base).topics[].namememberRequest 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[].namememberPayment 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[].namememberinvoice
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).tags[].namememberpayment
ml.nlu_classifier(body.previous_threads[length(body.previous_threads) - 2].text, subject=body.previous_threads[length(body.previous_threads) - 2].subject.base).topics[].namememberRequest 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[].namememberPayment Information
strings.istarts_withprefixINV
strings.istarts_withprefixW-9
strings.istarts_withprefixW9
body.previous_threads[].sender.email.emailequals
1 more
body.previous_threads[].recipients.to[0].email.emailequals

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.

FieldKindExcluded valuesSearch
flatten(...)array_any(no value, null check)excludes:flatten(...)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP impersonation: Invoice fraud with mobile device sign-off

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • attachments
  • body
  • body.previous_threads
  • recipients
  • sender.email
  • type

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.

  1. inbound message
  2. 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)

FieldMatchValue
strings.iends_withsuffixsent from my iphone.
strings.iends_withsuffixsent from my ipad.
ml.nlu_classifier(body.previous_threads[].text, subject=body.previous_threads[].subject.base).tags[].namememberinvoice
ml.nlu_classifier(body.previous_threads[].text, subject=body.previous_threads[].subject.base).tags[].namememberpayment
ml.nlu_classifier(body.previous_threads[].text, subject=body.previous_threads[].subject.base).topics[].namememberRequest to View Invoice
ml.nlu_classifier(body.previous_threads[].text, subject=body.previous_threads[].subject.base).topics[].namememberPayment Information
strings.istarts_withprefixINV
strings.istarts_withprefixW-9
strings.istarts_withprefixW9
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.

FieldKindValuesSearch
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP impersonation: Payment handoff with VIP display name authored fake threads

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • attachments
  • body
  • body.previous_threads
  • headers
  • headers.auth_summary
  • recipients
  • recipients.to
  • sender.email
  • type

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.

  1. inbound message
  2. 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).tags where all hold:
          • .name in ('invoice', 'payment')
          • .confidence is not 'low'
        • any of ml.nlu_classifier(.text, subject=.subject.base).topics where all hold:
          • .name in ('Request to View Invoice', 'Payment Information')
          • .confidence is not 'low'
      • any of attachments where:
        • .file_name starts with any of 3 patterns
          • INV
          • W-9
          • W9
    • not:
      • any of flatten(...) where:
        • any of $org_vips where all hold:
          • strings.icontains(.)
          • .email is not ''
          • strings.icontains(.email.email)
  3. length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains)) is 1
  4. 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)

FieldMatchValue
body.previous_threads[].sender.email.emailequals
regex.icontainsregexaccounts? payable
regex.icontainsregex(?:forward|send|rout|direct|remit|submit|issue) (?:it|a copy) (?:directly )?to
regex.icontainsregex(?:forward|send|direct|remit|submit|route) (?:the |all |related |for )?(?:invoice|correspondence|payment|billing|processing)
regex.icontainsregexfor payment processing,? please contact
regex.icontainsregex(?:please )?direct (?:it|all|the)[^\n]{0,60}\bto\b
regex.icontainsregex(?:as follows|provided below|find below|details are|contact is)[^\n]{0,10}:
regex.icontainsregexbilling (?:contact|correspondence|team|department)
regex.icontainsregexa copy.{0,20}sent to
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].namememberinvoice
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].namememberpayment
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].namememberRequest to View Invoice
4 more
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].namememberPayment Information
strings.istarts_withprefixINV
strings.istarts_withprefixW-9
strings.istarts_withprefixW9

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.

FieldKindValuesSearch
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP Impersonation: VIP handoff with fake forwarded invoice thread

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering, Spoofing, Out of band pivot

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.previous_threads
  • headers
  • headers.auth_summary
  • recipients
  • sender.email
  • type

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.

  1. inbound message
  2. length(body.previous_threads) > 1
  3. length(body.previous_threads[].recipients.to) is 1
  4. coalesce(body.previous_threads[].subject.is_forward)
  5. any of $org_vips where all hold:
    • body.previous_threads[].recipients.to[0].display_name is .display_name
    • body.previous_threads[].sender.display_name is .display_name
  6. body.previous_threads[].recipients.to[0].email.email is body.previous_threads[].sender.email.email
  7. any of body.previous_threads[].links where all hold:
    • .href_url.url is not ''
    • .href_url.scheme is 'mailto'
    • any of flatten(...) where:
      • strings.icontains(.email.email)
  8. not:
    • any of flatten(...) where:
      • any of $org_vips where all hold:
        • .email is not ''
        • strings.icontains(.email.email)
        • body.previous_threads[].sender.display_name is .display_name
  9. length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains)) is 1
  10. 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)

FieldMatchValue
body.previous_threads[].links[].href_url.schemeequalsmailto

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.

FieldKindExcluded valuesSearch
flatten(...)array_any(no value, null check)excludes:flatten(...)

Indicators

These rows show field, operator, and value matches.

VIP impersonation: VIP name within a delimited subject with fake previous threads

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering, Spoofing

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • body.html
  • body.plain
  • body.previous_threads
  • sender.email
  • subject
  • type

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.

  1. inbound message
  2. length(coalesce(body.plain.raw, body.html.display_text)) < 6000
  3. strings.replace_confusables(subject.base) contains any of 5 patterns
    • "
    • /-/
    • -//
    • -/
    • -#
  4. any of body.previous_threads where:
    • any of $org_vips where 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
  5. any of ml.nlu_classifier(body.current_thread.text).topics where 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)

FieldMatchValue
strings.icontainssubstring"
strings.icontainssubstring/-/
strings.icontainssubstring-//
strings.icontainssubstring-/
strings.icontainssubstring-#
ml.nlu_classifier(body.current_thread.text).topics[].namememberFinancial Communications
ml.nlu_classifier(body.current_thread.text).topics[].namememberRequest to View Invoice
ml.nlu_classifier(body.current_thread.text).topics[].namememberPayment 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.

VIP impersonation: VIP payment redirect handoff via fake threads

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • attachments
  • body
  • body.previous_threads
  • recipients
  • recipients.to
  • sender.email
  • type

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.

  1. inbound message
  2. 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).tags where all hold:
          • .name in ('invoice', 'payment')
          • .confidence is not 'low'
        • any of ml.nlu_classifier(.text, subject=.subject.base).topics where all hold:
          • .name in ('Request to View Invoice', 'Payment Information')
          • .confidence is not 'low'
      • any of attachments where:
        • .file_name starts with any of 3 patterns
          • INV
          • W-9
          • W9
  3. length(filter(flatten([recipients.to, recipients.cc, recipients.bcc]), .email.domain.root_domain in $org_domains)) is 1
  4. 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)

FieldMatchValue
regex.icontainsregexaccounts? payable
regex.icontainsregex(?:forward|send|rout|direct|remit|submit|issue) (?:it|a copy) (?:directly )?to
regex.icontainsregex(?:forward|send|direct|remit|submit|route) (?:the |all |related |for )?(?:invoice|correspondence|payment|billing|processing)
regex.icontainsregexfor payment processing,? please contact
regex.icontainsregex(?:please )?direct (?:it|all|the)[^\n]{0,60}\bto\b
regex.icontainsregex(?:as follows|provided below|find below|details are|contact is)[^\n]{0,10}:
regex.icontainsregexbilling (?:contact|correspondence|team|department)
regex.icontainsregexa copy.{0,20}sent to
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].namememberinvoice
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).tags[].namememberpayment
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].namememberRequest to View Invoice
ml.nlu_classifier(filter(body.previous_threads)[].text, subject=filter(body.previous_threads)[].subject.base).topics[].namememberPayment Information
3 more
strings.istarts_withprefixINV
strings.istarts_withprefixW-9
strings.istarts_withprefixW9

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.

FieldKindValuesSearch
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

VIP impersonation: VIP recipient of previous thread with HTML generator

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesImpersonation: VIP, Social engineering, Evasion

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.html
  • body.previous_threads
  • type

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.

  1. inbound message
  2. any of body.previous_threads where:
    • any of .recipients.to where:
      • any of $org_vips where any holds:
        • strings.icontains(.display_name)
        • strings.icontains(.email.email)
  3. 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)

FieldMatchValue
strings.icontainssubstringAdvanced HTML parser
regex.icontainsregex<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.

VIP local_part impersonation from unsolicited sender

#
Severity
high
Type
rule
Source
github.com/sublime-security/sublime-rules

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).

CategoryValues
Tactics and techniquesImpersonation: VIP, Spoofing

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • headers
  • headers.auth_summary
  • mailbox
  • recipients
  • sender.email
  • type

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.

  1. inbound message
  2. sender.email.local_part contains '.'
  3. any of $org_vips where 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
  4. any of:
    • length(recipients.to) > 0
    • length(recipients.cc) > 0
    • sender.display_name is not mailbox.display_name
  5. not:
    • sender.email.local_part matches any of 3 patterns
      • *postmaster*
      • *mailer-daemon*
      • *administrator*
  6. 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
  7. 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
  8. 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)

FieldMatchValue
strings.containssubstring.

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.

Indicators

These rows show field, operator, and value matches.