8 Commits
Author SHA1 Message Date
greg eb444f1191 Merge pull request 'Improve SpamAssassin config, add custom rules' (#668) from email/spamassassin_rules into master
Reviewed-on: #668
Reviewed-by: Greg <greg@kosmos.org>
2026-08-17 21:16:44 +00:00
raucao eae40fd55b Add custom spam rules
Targeting known campaigns
2026-08-17 14:58:42 -06:00
raucao 413f02d9c1 Fix commercial validity lists poisoning Bayes db, harden Bayes learning 2026-08-17 14:57:10 -06:00
raucao 2bd6da0064 Auto-update spamassassin rules 2026-08-17 14:56:29 -06:00
greg 2042339174 Merge pull request 'Cache successful Blossom responses' (#666) from feature/blossom_cache into master
Reviewed-on: #666
Reviewed-by: Greg <greg@kosmos.org>
2026-08-16 18:52:30 +00:00
raucao 6b967c9ac1 Update nodes 2026-08-16 12:19:19 -06:00
raucao 0224a99fdb Add upstream_cache_status to Nginx JSON log format 2026-08-16 12:17:34 -06:00
raucao f6f0d5b22d Cache successful Blossom responses
Cache returned files from Blossom on the Nginx hosts, until either
deleted by the user or hitting the global cache size limit (1 GB)
2026-08-16 12:17:09 -06:00
7 changed files with 105 additions and 4 deletions
+1 -1
Submodule nodes updated: 5fd2e23a01...90c15e40f8
@@ -19,7 +19,7 @@ server {
ssl_certificate <%= @ssl_cert %>; ssl_certificate <%= @ssl_cert %>;
ssl_certificate_key <%= @ssl_key %>; ssl_certificate_key <%= @ssl_key %>;
location ~ ^/(?<sha256>[a-f0-9]{64})(\.[a-zA-Z0-9]+)?$ { location ~ "^/(?<sha256>[a-f0-9]{64})(\.[a-zA-Z0-9]+)?$" {
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -29,6 +29,12 @@ template "/etc/spamassassin/local.cf" do
notifies :restart, "service[spamassassin]", :delayed notifies :restart, "service[spamassassin]", :delayed
end end
template "/etc/spamassassin/kosmos.cf" do
source "spamassassin_kosmos.cf.erb"
mode 0644
notifies :restart, "service[spamassassin]", :delayed
end
service "spamassassin" do service "spamassassin" do
action [:enable, :start] action [:enable, :start]
end end
@@ -30,4 +30,4 @@ PIDFILE="/var/run/spamd.pid"
# Cronjob # Cronjob
# Set to anything but 0 to enable the cron job to automatically update # Set to anything but 0 to enable the cron job to automatically update
# spamassassin's rules on a nightly basis # spamassassin's rules on a nightly basis
CRON=0 CRON=1
@@ -0,0 +1,64 @@
###########################################################################
# Kosmos custom SpamAssassin rules
#
# The rules below are designed to be individually weak but combine via meta
# rules into strong, low-FP signals.
###########################################################################
# --- Individual signals --------------------------------------------------
# URLs of the form https://////////////... (3+ slashes after the scheme).
# Legitimate mailers never produce this; it is an obfuscation artefact.
rawbody KOSMOS_MULTI_SLASH_URL /https?:\/{3,}/
describe KOSMOS_MULTI_SLASH_URL URL with three or more consecutive slashes
# Authoring-tool fingerprint left in the HTML by the spam toolchain.
body KOSMOS_MSHTML_11_9600 /MSHTML 11\.00\.9600\.17037/
describe KOSMOS_MSHTML_11_9600 HTML generated by MSHTML 11.00.9600.17037
# Display name pattern used by the campaign.
header KOSMOS_FROM_LUXURY_GOODS From:name =~ /Luxury (Watches|Bags|Handbags|Timepieces)\b/i
describe KOSMOS_FROM_LUXURY_GOODS From display name advertises luxury goods
# Base64-encoded unsubscribe links: return.php?p=<long base64>
uri KOSMOS_RETURN_PHP_B64 /return\.php\?p=[A-Za-z0-9+\/=%]{20,}/
describe KOSMOS_RETURN_PHP_B64 Base64-encoded return.php unsubscribe link
# Fabricated "security" headers injected to evade heuristic filters.
# No legitimate MTA or mailing-list manager emits these.
header KOSMOS_FAKE_HDR_PHISHSIM exists:X-PhishSimulator-Mode
header KOSMOS_FAKE_HDR_DECEPTION exists:X-Deception-Asset-Type
header KOSMOS_FAKE_HDR_OBFUSCATION exists:X-Obfuscation-Trace-ID
header KOSMOS_FAKE_HDR_QUARANTINE exists:X-Quarantine-Reason-Code
header KOSMOS_FAKE_HDR_TRUST exists:X-Behavioral-Trust-Index
# --- Scores for individual signals --------------------------------------
score KOSMOS_MULTI_SLASH_URL 2.0
score KOSMOS_MSHTML_11_9600 1.2
score KOSMOS_FROM_LUXURY_GOODS 0.5
score KOSMOS_RETURN_PHP_B64 1.5
score KOSMOS_FAKE_HDR_PHISHSIM 1.0
score KOSMOS_FAKE_HDR_DECEPTION 1.0
score KOSMOS_FAKE_HDR_OBFUSCATION 1.0
score KOSMOS_FAKE_HDR_QUARANTINE 1.0
score KOSMOS_FAKE_HDR_TRUST 1.0
# --- Meta rules ----------------------------------------------------------
# Core campaign signature: luxury-goods From name + MSHTML fingerprint +
# HTML-only body. Covers the bulk of the campaign corpus.
meta KOSMOS_LUXURY_SPAM_CAMPAIGN (KOSMOS_FROM_LUXURY_GOODS && KOSMOS_MSHTML_11_9600 && MIME_HTML_ONLY)
describe KOSMOS_LUXURY_SPAM_CAMPAIGN Luxury-goods From + MSHTML 11.00.9600 + HTML-only
score KOSMOS_LUXURY_SPAM_CAMPAIGN 3.5
# Luxury-goods From name combined with a suspicious URI signal or a
# Spamhaus-listed relay.
meta KOSMOS_LUXURY_SPAM_URI (KOSMOS_FROM_LUXURY_GOODS && (KOSMOS_MULTI_SLASH_URL || KOSMOS_RETURN_PHP_B64 || RCVD_IN_SBL_CSS))
describe KOSMOS_LUXURY_SPAM_URI Luxury-goods From + suspicious URI or SBL relay
score KOSMOS_LUXURY_SPAM_URI 2.5
# Two or more fabricated "security" headers. Genuine mail never carries
# these; their presence indicates a header-injection evasion kit.
meta KOSMOS_FAKE_SECURITY_HEADERS (KOSMOS_FAKE_HDR_PHISHSIM + KOSMOS_FAKE_HDR_DECEPTION + KOSMOS_FAKE_HDR_OBFUSCATION + KOSMOS_FAKE_HDR_QUARANTINE + KOSMOS_FAKE_HDR_TRUST >= 2)
describe KOSMOS_FAKE_SECURITY_HEADERS Two or more fabricated security headers
score KOSMOS_FAKE_SECURITY_HEADERS 4.0
@@ -16,6 +16,37 @@ whitelist_auth <%= @whitelist_auth %>
# _CONTACTADDRESS_ in the report template) # _CONTACTADDRESS_ in the report template)
report_contact <%= @report_contact %> report_contact <%= @report_contact %>
###########################################################################
# Kosmos custom score overrides
#
# The Validity (Return Path / SenderScore) "certified sender" whitelists
# (RCVD_IN_VALIDITY_CERTIFIED, RCVD_IN_VALIDITY_SAFE) hand out up to -5.0
# of credit to sending IPs. These lists are commercially gamed and
# routinely award -5.0 to IPs that are simultaneously listed on Spamhaus
# SBL-CSS, SpamCop, MSPIKE and Validity's own RPBL. Neutralise them.
###########################################################################
score RCVD_IN_VALIDITY_CERTIFIED 0
score RCVD_IN_VALIDITY_SAFE 0
###########################################################################
# Bayes hardening
###########################################################################
use_bayes 1
bayes_auto_learn 1
# Do not let Bayes learn from SpamAssassin's own result headers or from
# Authentication-Results, which leak signal about prior scoring runs.
bayes_ignore_header X-Spam-Flag
bayes_ignore_header X-Spam-Status
bayes_ignore_header X-Spam-Level
bayes_ignore_header X-Spam-Checker-Version
bayes_ignore_header Authentication-Results
# Only learn ham when the message is clearly clean, and only learn spam
# when it is clearly spam. The defaults (0.1 / 6.0) let marginally-spam
# or marginally-ham messages poison the database.
bayes_auto_learn_threshold_nonspam -1.0
bayes_auto_learn_threshold_spam 8.0
# Add *****SPAM***** to the Subject header of spam e-mails # Add *****SPAM***** to the Subject header of spam e-mails
# #
@@ -3,7 +3,7 @@
# Recipe:: default # Recipe:: default
# #
node.normal['openresty']['log_formats']['json'] = '{"ip":"$remote_addr","time":"$time_local","host":"$host","method":"$request_method","uri":"$uri","status":$status,"size":$body_bytes_sent,"referer":"$http_referer","upstream_addr":"$upstream_addr","upstream_response_time":"$upstream_response_time","ua":"$http_user_agent"}' node.normal['openresty']['log_formats']['json'] = '{"ip":"$remote_addr","time":"$time_local","host":"$host","method":"$request_method","uri":"$uri","status":$status,"size":$body_bytes_sent,"referer":"$http_referer","upstream_addr":"$upstream_addr","upstream_response_time":"$upstream_response_time","upstream_cache_status":"$upstream_cache_status","ua":"$http_user_agent"}'
# Install openresty from official packages # Install openresty from official packages
include_recipe 'openresty::apt_package' include_recipe 'openresty::apt_package'