type.inbound
// limit evaluation of a regex heavy rule
and length(body.current_thread.text) < 2000000
and (
any([
strings.replace_confusables(sender.display_name),
strings.replace_confusables(subject.subject),
// domain parts of sender
sender.email.local_part,
sender.email.domain.sld
],
// quick checks first
strings.icontains(., 'Capital One')
or strings.icontains(., 'CapitalOne')
// slower checks next
or regex.icontains(., 'Capital.?One')
// levenshtein distince similar to captial one
or strings.ilevenshtein(., 'Capital One') <= 2
)
or any(ml.logo_detect(file.message_screenshot()).brands,
.name == "Capital One Bank" and .confidence != "low"
)
)
and not (
sender.email.domain.root_domain in (
"capitalone.co.uk",
"capitalone.com",
"capitaloneshopping.com",
"capitalonesoftware.com",
"capitalonebooking.com",
"capitalonetravel.com",
"olbanking.com", // a fiserv.one domain
"bynder.com", // Digital Assest Mgmt
"gcs-web.com", // investor relations run by capital one
"capitalonearena.com", // the arena
"monumentalsports.com", // the company that owns a bunch of teams that play at the arena?
"ticketmaster.com", // sell and advertises tickets at Capital One Arena
"credible.com", // known loan marketplace
"capitalonetradecredit.com" // domain associated with Capital One's trade credit platform
)
and headers.auth_summary.dmarc.pass
)
// and the sender is not from high trust sender root domains
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
)
and // suspicious indicators here
(
// // password theme
(
strings.icontains(body.current_thread.text, "new password")
or regex.icontains(body.current_thread.text,
'(?:credentials?|password)\s*(?:\w+\s+){0,3}\s*(?:compromise|reset|expir(?:ation|ed)|update|invalid|incorrect|changed|(?:mis)?match)',
'(?:compromise|reset|expir(?:ation|ed)|update|invalid|incorrect|changed|(?:mis)?match)\s*(?:\w+\s+){0,3}\s*(?:credentials?|password)',
'(?:short|weak|chang(?:e|ing)|reset)\s*(?:\w+\s+){0,3}\s*(?:credentials?|password)',
'(?:credentials?|password)\s*(?:\w+\s+){0,3}\s*(?:short|weak|chang(?:e|ing)|reset)',
)
)
// // login failures
or (
strings.icontains(body.current_thread.text, "unusual number of")
or strings.icontains(body.current_thread.text, "security breach")
or (
strings.icontains(body.current_thread.text, "security alert")
// some capital one notiifcaitons include directions to
// change notificaiton preferences to only security alerts
and (
strings.icount(body.current_thread.text, "security alert") > strings.icount(body.current_thread.text,
"sign in to your account and select Security Alerts."
)
)
)
or strings.icontains(body.current_thread.text, "account remains secure")
or strings.icontains(body.current_thread.text, "please verify your account")
or strings.icontains(body.current_thread.text,
"suspicious activity detected"
)
or strings.icontains(body.current_thread.text, "temporarily locked out")
or regex.icontains(body.current_thread.text,
'(?:invalid|unrecognized|unauthorized|fail(?:ed|ure)?|suspicious|unusual|attempt(?:ed)?\b|tried to)\s*(?:\w+\s+){0,3}\s*(?:log(?:.?in)?|sign(?:.?in)?|account|access|activity)',
'(?:log(?:.?in)?|sign(?:.?in)?|account|access|activity)\s*(?:\w+\s+){0,3}\s*(?:invalid|unrecognized|fail(?:ed|ure)?|suspicious|unusual|attempt(?:ed)?\b)'
)
)
// // account locked
or (
strings.icontains(body.current_thread.text, "been suspend")
or strings.icontains(body.current_thread.text, "will be restored")
or strings.icontains(body.current_thread.text, "security reasons")
or strings.icontains(body.current_thread.text,
"temporarily restricted access"
)
or regex.icontains(body.current_thread.text,
'acc(?:ou)?n?t\s*(?:\w+\s+){0,3}\s*(?:authenticat(?:e|ion)|activity|\bho[li]d\b|terminat|[il1]{2}m[il1]t(?:s|ed|ation)|b?locked|de-?activat|suspen(?:ed|sion)|restrict(?:ed|ion)?|expir(?:ed?|ing)|v[il]o[li]at|verif(?:y|ication))',
'(?:authenticat(?:e|ion)|activity|\bho[li]d\b|terminat|[il1]{2}m[il1]t(?:s|ed|ation)|b?locked|de-?activat|suspen(?:ed|sion)|restrict(?:ed|ion)?|expir(?:ed?|ing)|v[il]o[li]at|verif(?:y|ication))\s*(?:\w+\s+){0,3}\s*acc(?:ou)?n?t\b'
)
)
// // secure messages
or (
regex.icontains(body.current_thread.text,
'(?:encrypt(?:ion|ed)?|secur(?:ed?|ity)) (?:\w+\s+){0,3}\s*message'
)
or strings.icontains(body.current_thread.text, "document portal")
or regex.icontains(body.current_thread.text,
"has been (?:encrypt|sent secure)"
)
or regex.icontains(body.current_thread.text,
'encryption (?:\w+\s+){0,3}\s*tech'
)
)
// // documents to view
or (
// we can skip the regex if the diplay_text doesn't contain document
// this might need to be removed if the regex is expanded
strings.icontains(body.current_thread.text, 'document')
and regex.icontains(body.current_thread.text,
'document\s*(?:\w+\s+){0,3}\s*(?:ready|posted|review|available|online)',
'(?:ready|posted|review|available|online)\s*(?:\w+\s+){0,3}\s*document'
)
)
// // account/profile details
or (
strings.icontains(body.current_thread.text, "about your account")
or strings.icontains(body.current_thread.text, "action required")
or regex.icontains(body.current_thread.text,
'(update|\bedit\b|modify|revise|verif(?:y|ication)|discrepanc(?:y|ies)|mismatch(?:es)?|inconsistenc(?:y|ies)?|difference(?:s)?|anomal(?:y|ies)?|irregularit(?:y|ies)?)\s*(?:\w+\s+){0,4}\s*(?:account|ownership|detail|record|data|info(?:rmation)?)',
'(?:account|ownership|detail|record|data|info(?:rmation)?)\s*(?:\w+\s+){0,4}\s*(update|\bedit\b|modify|revise|verif(?:y|ication)|discrepanc(?:y|ies)|mismatch(?:es)?|inconsistenc(?:y|ies)?|difference(?:s)?|anomal(?:y|ies)?|irregularit(?:y|ies)?)'
)
)
// // other calls to action that are unexpected
or (strings.icontains(body.current_thread.text, "download the attachment"))
// the links contain suspect wording
or (
0 < length(body.links) <= 50
and any(body.links,
(
regex.icontains(.display_text, '(?:log|sign).?in')
or strings.icontains(.display_text, 'confirm')
or strings.icontains(.display_text, 'i recongize it')
or strings.icontains(.display_text, "something\'s wrong")
or regex.icontains(.display_text,
'(?:(?:re)?view|see|read)\s*(?:\w+\s*){0,3}\s*(?:document|message|now|account)'
)
or regex.icontains(.display_text,
'restore\s*(?:\w+\s*){0,3}\s*(?:account|access)'
)
or regex.icontains(.display_text,
'review\s*(?:\w+\s*){0,3}\s*(?:payment)'
)
)
and not regex.icontains(.display_text,
'confirm\s*(?:\w+\s*){0,3}\s*this message'
)
and .href_url.domain.root_domain != "capitalone.com"
)
)
// the message contains a disclaimer but isn't from capitalone
or (
regex.icontains(body.current_thread.text,
'To ensure delivery, add [^\@]+@[^\s]*capitalone.com to your address book.'
)
and sender.email.domain.root_domain != "capitalone.com"
)
)
// negation of inbound org domains which path eamil auth
and not (
type.inbound
and sender.email.domain.domain in $org_domains
and headers.auth_summary.spf.pass
and headers.auth_summary.dmarc.pass
and not 'fail' in~ distinct(map(headers.hops, .authentication_results.dkim))
)
and not any(beta.ml_topic(body.html.display_text).topics,
(
.name in (
// lots of newsletters talk about capital one
"Newsletters and Digests",
// lots of recruiting mention oppurtunties at capital one, often including the logo
"Professional and Career Development",
)
and .confidence == "high"
)
or (
.name in (
// Outage events are often news worthy
"News and Current Events"
)
and .confidence != "low"
)
)
// negating legit replies/forwards
// https://github.com/sublime-security/sublime-rules/blob/main/insights/authentication/org_inbound_auth_pass.yml
and not (
(
strings.istarts_with(subject.subject, "RE:")
or strings.istarts_with(subject.subject, "FW:")
or strings.istarts_with(subject.subject, "FWD:")
or regex.imatch(subject.subject,
'(\[[^\]]+\]\s?){0,3}(re|fwd?|automat.*)\s?:.*'
)
or strings.istarts_with(subject.subject, "Réponse automatique")
)
and (
length(headers.references) > 0
and any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
)
)
// negate bounce backs
and not (
strings.like(sender.email.local_part,
"*postmaster*",
"*mailer-daemon*",
"*administrator*"
)
and any(attachments,
.content_type in (
"message/rfc822",
"message/delivery-status",
"text/calendar"
)
)
)
Playground
Test against your own EMLs or sample data.