diff --git a/signals/attachments/attachment_file_name_rfq.yml b/signals/attachments/attachment_file_name_rfq.yml new file mode 100644 index 00000000000..e27288359e3 --- /dev/null +++ b/signals/attachments/attachment_file_name_rfq.yml @@ -0,0 +1,4 @@ +name: "Attachment: Contains RFQ File Name" +type: "query" +source: | + any(attachments, regex.icontains(.file_name, "(purchase.?order|Quot(e|ation))")) diff --git a/signals/content/body_contains_btc_wallet.yml b/signals/content/body_contains_btc_wallet.yml new file mode 100644 index 00000000000..84f02c7c5d7 --- /dev/null +++ b/signals/content/body_contains_btc_wallet.yml @@ -0,0 +1,4 @@ +name: "Body: Bitcoin address" +type: "query" +source: | + any([body.plain.raw, body.html.display_text], regex.contains(., '\b[13]{1}[a-km-zA-HJ-NP-Z1-9]{25,34}\b')) \ No newline at end of file diff --git a/signals/content/body_contains_emoji.yml b/signals/content/body_contains_emoji.yml new file mode 100644 index 00000000000..052185b6932 --- /dev/null +++ b/signals/content/body_contains_emoji.yml @@ -0,0 +1,6 @@ +name: "Body: Contains Emoji" +type: "query" +source: | + regex.contains(body.plain.raw, + '[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F900}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{2300}-\x{23FF}]' + ) \ No newline at end of file diff --git a/signals/content/body_contains_recipient_email.yml b/signals/content/body_contains_recipient_email.yml new file mode 100644 index 00000000000..4383663f2b7 --- /dev/null +++ b/signals/content/body_contains_recipient_email.yml @@ -0,0 +1,4 @@ +name: "Body: Contains Recipient Email" +type: "query" +source: | + any(recipients.to, strings.icontains(body.current_thread.text, .email.email)) diff --git a/signals/content/body_contains_recipient_local_part.yml b/signals/content/body_contains_recipient_local_part.yml new file mode 100644 index 00000000000..7814ec0a5ed --- /dev/null +++ b/signals/content/body_contains_recipient_local_part.yml @@ -0,0 +1,7 @@ +name: "Body: Contains Recipient Local Part" +type: "query" +source: | + any(recipients.to, + strings.icontains(body.current_thread.text, .email.local_part) and not + strings.icontains(body.current_thread.text, .email.email) + ) \ No newline at end of file diff --git a/signals/content/body_contains_rfq_language.yml b/signals/content/body_contains_rfq_language.yml new file mode 100644 index 00000000000..3ddfa2c0179 --- /dev/null +++ b/signals/content/body_contains_rfq_language.yml @@ -0,0 +1,8 @@ +name: "Body: Contains RFQ Language" +type: "query" +source: | + 1 of ( + (regex.icontains(body.current_thread.text, '(discuss.{0,15}purchas(e|ing))')), + (regex.icontains(body.current_thread.text, '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)')), + (regex.icontains(body.current_thread.text, '(please|kindly).{0,30}quote')) + ) \ No newline at end of file diff --git a/signals/content/body_contains_vm_language.yml b/signals/content/body_contains_vm_language.yml new file mode 100644 index 00000000000..d949e1deceb --- /dev/null +++ b/signals/content/body_contains_vm_language.yml @@ -0,0 +1,4 @@ +name: "Body: Contains Voicemail Language" +type: "query" +source: | + regex.contains(body.current_thread.text, '(voice)\s?(mail|message|recording|call)') \ No newline at end of file diff --git a/signals/content/body_excessive_space.yml b/signals/content/body_excessive_space.yml new file mode 100644 index 00000000000..949bfef384c --- /dev/null +++ b/signals/content/body_excessive_space.yml @@ -0,0 +1,4 @@ +name: "Body: Space Obfuscation" +type: "query" +source: | + regex.icontains(coalesce(body.html.inner_text, body.html.display_text), '([a-zA-Z\d\.]\s){30,}') diff --git a/signals/content/body_length.yml b/signals/content/body_length.yml new file mode 100644 index 00000000000..2b032f6289c --- /dev/null +++ b/signals/content/body_length.yml @@ -0,0 +1,4 @@ +name: "Content: Body Length" +type: "query" +source: | + length(body.current_thread.text) diff --git a/signals/content/subject_contains_auth_language.yml b/signals/content/subject_contains_auth_language.yml new file mode 100644 index 00000000000..afac8ec40f9 --- /dev/null +++ b/signals/content/subject_contains_auth_language.yml @@ -0,0 +1,6 @@ +name: "Subject: Contains Authentication Language" +type: "query" +source: | + regex.contains(subject.subject, + "(Authenticat(e|or|ion)|2fa|Multi.Factor|(qr|bar).code|action.require|alert|Att(n|ention):)" + ) diff --git a/signals/content/subject_contains_emoji.yml b/signals/content/subject_contains_emoji.yml new file mode 100644 index 00000000000..b3ed06b018b --- /dev/null +++ b/signals/content/subject_contains_emoji.yml @@ -0,0 +1,6 @@ +name: "Subject: Contains Emoji" +type: "query" +source: | + regex.contains(subject.subject, + '[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F900}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{2300}-\x{23FF}]' + ) \ No newline at end of file diff --git a/signals/content/subject_contains_recipient_sld.yml b/signals/content/subject_contains_recipient_sld.yml new file mode 100644 index 00000000000..b1b2f21d28e --- /dev/null +++ b/signals/content/subject_contains_recipient_sld.yml @@ -0,0 +1,4 @@ +name: "Subject: Contains Recipient SLD" +type: "query" +source: | + any(recipients.to, strings.icontains(subject.subject, .email.domain.sld)) diff --git a/signals/content/subject_contains_rfq_language.yml b/signals/content/subject_contains_rfq_language.yml new file mode 100644 index 00000000000..910ae916548 --- /dev/null +++ b/signals/content/subject_contains_rfq_language.yml @@ -0,0 +1,4 @@ +name: "Subject: Contains RFQ Language" +type: "query" +source: | + regex.icontains(subject.subject, '(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b)') \ No newline at end of file diff --git a/signals/content/subject_contains_vm_language.yml b/signals/content/subject_contains_vm_language.yml new file mode 100644 index 00000000000..b4d7d6ac60c --- /dev/null +++ b/signals/content/subject_contains_vm_language.yml @@ -0,0 +1,4 @@ +name: "Subject: Contains Voicemail Language" +type: "query" +source: | + regex.icontains(subject.subject, 'voice.*?(mail|message|recording|call)') \ No newline at end of file diff --git a/signals/headers/headers_all_recipients_bcc.yml b/signals/headers/headers_all_recipients_bcc.yml new file mode 100644 index 00000000000..7af0f3986d4 --- /dev/null +++ b/signals/headers/headers_all_recipients_bcc.yml @@ -0,0 +1,6 @@ +name: "Headers: All recipients BCCd" +type: "query" +source: | + length(recipients.to) == 0 + and length(recipients.cc) == 0 + and length(recipients.bcc) == 1 diff --git a/signals/headers/headers_replyto_similar_to_sender.yml b/signals/headers/headers_replyto_similar_to_sender.yml new file mode 100644 index 00000000000..4946a704c9e --- /dev/null +++ b/signals/headers/headers_replyto_similar_to_sender.yml @@ -0,0 +1,12 @@ +name: "Headers: Reply-to Domain Similar To Sender Domain" +type: "query" +source: | + any(headers.reply_to, + length(headers.reply_to) > 0 + and all(headers.reply_to, + ( + strings.ilevenshtein(.email.domain.root_domain, sender.email.domain.root_domain) <= 1 + and .email.domain.root_domain != sender.email.domain.root_domain + ) + ) + ) diff --git a/signals/links/link_contains_recipient_email.yml b/signals/links/link_contains_recipient_email.yml new file mode 100644 index 00000000000..f1ab8f78932 --- /dev/null +++ b/signals/links/link_contains_recipient_email.yml @@ -0,0 +1,5 @@ +name: "Link: Contains Recipient Email" +type: "query" +source: | + any(body.links, any(recipients.to, strings.icontains(..href_url.query_params, .email.email))) + diff --git a/signals/sender/sender_display_contains_honorific.yml b/signals/sender/sender_display_contains_honorific.yml new file mode 100644 index 00000000000..2c8ee120a28 --- /dev/null +++ b/signals/sender/sender_display_contains_honorific.yml @@ -0,0 +1,4 @@ +name: "Sender: Display Name Contains Honorific" +type: "query" +source: | + regex.icontains(sender.display_name, "(?:Mr.?|Mrs.?|Ms.?|Miss.?|Dr.?|Prof.?|Sir.?|Lady.?|Rev.?)[ \t]+") \ No newline at end of file diff --git a/signals/sender/sender_display_contains_recipient_local_part.yml b/signals/sender/sender_display_contains_recipient_local_part.yml new file mode 100644 index 00000000000..c3d5297572d --- /dev/null +++ b/signals/sender/sender_display_contains_recipient_local_part.yml @@ -0,0 +1,5 @@ +name: "Sender: Display Name Contains Recipient Local-Part" +type: "query" +source: | + any(recipients.to, strings.icontains(sender.display_name, .email.local_part)) + and not any(recipients.to, strings.icontains(.display_name, "recipients")) \ No newline at end of file diff --git a/signals/sender/sender_display_contains_vm_language.yml b/signals/sender/sender_display_contains_vm_language.yml new file mode 100644 index 00000000000..7f142f2161b --- /dev/null +++ b/signals/sender/sender_display_contains_vm_language.yml @@ -0,0 +1,4 @@ +name: "Sender: Display Name Contains Voicemail Language" +type: "query" +source: | + regex.icontains(sender.display_name, 'voice.*?(mail|message|recording|call)') \ No newline at end of file diff --git a/signals/sender/sender_display_is_upper.yml b/signals/sender/sender_display_is_upper.yml new file mode 100644 index 00000000000..04a29ffc9e5 --- /dev/null +++ b/signals/sender/sender_display_is_upper.yml @@ -0,0 +1,4 @@ +name: "Sender: Display Name Contains All Capital Letters" +type: "query" +source: | + regex.match(sender.display_name, "^[^a-z]*[A-Z][^a-z]*$") \ No newline at end of file diff --git a/signals/sender/sender_display_name_contains_via.yml b/signals/sender/sender_display_name_contains_via.yml new file mode 100644 index 00000000000..6901b2786ac --- /dev/null +++ b/signals/sender/sender_display_name_contains_via.yml @@ -0,0 +1,4 @@ +name: "Sender: Display Name Contains All Capital Letters" +type: "query" +source: | + regex.icontains(sender.display_name, "(?:^|[^a-zA-Z0-9])via(?:$|[^a-zA-Z0-9])|[\"']via[\"']") \ No newline at end of file diff --git a/signals/sender/sender_domain_not_in_tranco10k.yml b/signals/sender/sender_domain_not_in_tranco10k.yml new file mode 100644 index 00000000000..97148597d21 --- /dev/null +++ b/signals/sender/sender_domain_not_in_tranco10k.yml @@ -0,0 +1,4 @@ +name: "Sender: Domain not in Tranco 10k" +type: "query" +source: | + sender.email.domain.domain not in $tranco_10k diff --git a/signals/sender/sender_domain_not_in_tranco1m.yml b/signals/sender/sender_domain_not_in_tranco1m.yml new file mode 100644 index 00000000000..a38a2d288ce --- /dev/null +++ b/signals/sender/sender_domain_not_in_tranco1m.yml @@ -0,0 +1,4 @@ +name: "Sender: Domain not in Tranco 1m" +type: "query" +source: | + sender.email.domain.domain not in $tranco_1m diff --git a/signals/sender/sender_domain_similar_to_recipient_domain.yml b/signals/sender/sender_domain_similar_to_recipient_domain.yml new file mode 100644 index 00000000000..89eb3ad8c9b --- /dev/null +++ b/signals/sender/sender_domain_similar_to_recipient_domain.yml @@ -0,0 +1,9 @@ +name: "Sender: Domain Similar To Recipient Domain" +type: "query" +source: | + any(recipients.to, + ( + strings.ilevenshtein(.email.email, sender.email.email) <= 1 and + .email.email != sender.email.email + ) + ) diff --git a/signals/sender/sender_email_mismatched_from_and_reply-to.yml b/signals/sender/sender_email_mismatched_from_and_reply-to.yml new file mode 100644 index 00000000000..5b9bcd37900 --- /dev/null +++ b/signals/sender/sender_email_mismatched_from_and_reply-to.yml @@ -0,0 +1,7 @@ +name: "Sender: Mismatched From and Reply-to Email" +type: "query" +source: | + any(headers.reply_to, + length(headers.reply_to) > 0 + and all(headers.reply_to, .email.email != sender.email.email) + ) diff --git a/signals/sender/sender_length_local_part.yml b/signals/sender/sender_length_local_part.yml new file mode 100644 index 00000000000..c2aed250c49 --- /dev/null +++ b/signals/sender/sender_length_local_part.yml @@ -0,0 +1,4 @@ +name: "Sender: Local-Part Length" +type: "query" +source: | + length(sender.email.local_part)