Mail app: optional SpamAssassin plugins can't be enabled with their stock rules, and `sa-update` never runs
-
Running three Cloudron servers (9.2.x, mail app 3.18.2, SpamAssassin 4.0.0) with
about 35 mailboxes between them. While hardening against a run of phishing that
was getting through, I went looking at how SpamAssassin is set up inside the mail
container and found a few things that I think are worth sharing — plus some
questions I couldn't answer from the docs.Everything below is reproducible with
docker exec mail …on the host.1. The optional plugins are off — but that's upstream, not Cloudron
My first assumption was that Cloudron had disabled them. It hasn't:
docker exec mail dpkg -V spamassassin ??5?????? c /etc/spamassassin/local.cf ??5?????? c /etc/spamassassin/v310.preOnly
local.cfandv310.prediffer from the Debian package.v342.preis
byte-identical, and that is the file holdingPhishing,FromNameSpoof,
DecodeShortURLs,URILocalBLandPhishTag— all commented out exactly as
upstream ships them. So this is stock SpamAssassin behaviour, and I want to be
clear I'm not reporting a Cloudron bug here. It only becomes a Cloudron question
because of point 2.For completeness, Cloudron's entire spamd setup is this, from
/app/code/start.sh:setup_spamd() { mkdir -p /run/spamd /app/data/spamd /run/spamd/tmp touch /app/data/spamd/custom.cf /app/data/spamd/acl.cf chown -R cloudron:cloudron /app/data/spamd /run/spamd }2.
loadpluginworks fromcustom.cf, but the stock rules don't come with itThe only persistent config surface is the Custom SpamAssassin rules field in
the Email view, which is/app/data/spamd/custom.cf,included at the end of
local.cf. Anything in/etc/spamassassin/is reset on app update.loadpluginis accepted there and lints clean:docker exec mail spamassassin --lint --cf="loadplugin Mail::SpamAssassin::Plugin::FromNameSpoof" # exit 0But it has no effect on the plugin's own rules. Those live in
/usr/share/spamassassin/72_active.cfbehind anifpluginguard, and that file
is parsed beforelocal.cfpulls incustom.cf— so by the time the plugin
loads, the guard has already been evaluated and the rules skipped for good.Verified with a message whose
From:display name is the recipient's address
(should tripT_FROMNAME_EQUALS_TO
# without the plugin tests=FROM_2_EMAILS_SHORT,… score=3.0 # with --cf="loadplugin …FromNameSpoof" tests=FROM_2_EMAILS_SHORT,… score=3.0 ← identicalWorkaround that does work: load the plugin and redefine the rules yourself
incustom.cfunder your own names. That gives 3.0 → 4.9 on the same message:loadplugin Mail::SpamAssassin::Plugin::FromNameSpoof header __MY_FROMNAME_SPOOF eval:check_fromname_spoof() header __MY_FROMNAME_EQUALS_TO eval:check_fromname_equals_to() meta MY_FROMNAME_SPOOFED_EMAIL (__MY_FROMNAME_SPOOF && !__VIA_ML && !__VIA_RESIGNER && !__RP_MATCHES_RCVD) meta MY_FROMNAME_EQUALS_TO __MY_FROMNAME_EQUALS_TO score MY_FROMNAME_SPOOFED_EMAIL 0.01 score MY_FROMNAME_EQUALS_TO 0.01It works, but you're maintaining a copy of upstream's rule definitions, which
will silently drift when SpamAssassin changes them.3.
sa-updatenever runsThis one surprised me more than the plugins:
docker exec mail ls /var/lib/spamassassin/ sa-update-keys ← only the keys, no rules dir docker exec mail ls /etc/cron.d/ ← e2scrub_all, php docker exec mail grep -rn sa-update /app/code/start.sh /etc/supervisor/ ← nothingNo versioned rules directory means
sa-updatehas never completed. The static
rule set is therefore whatever the Debian package ships —
/usr/share/spamassassin/50_scores.cfis dated 2022-07-01 on my servers.In practice this matters less than it sounds, because the adaptive parts are all
still live: DNSBL/URIBL lookups are DNS queries and always current, Bayes trains
itself, andcustom.cfis mine. But rule updates also carry rescored weights
from current corpora, and those never arrive.I can see why it isn't run:
/var/lib/spamassassinisn't persistent, so any
sa-updateresult is discarded on the next app update.4. Two smaller things
- The
Phishingplugin reads its feed file only infinish_parsing_end, i.e.
once at spamd startup. Refreshing the feed on disk does nothing until
supervisorctl restart spamd. I'm doing that from a host cron job; it works,
but it feels like reaching around the platform. - Unrelated to Cloudron, but a time-saver for anyone else wiring up that plugin:
https://openphish.com/feed.txtis now a 302 to a GitHub raw URL, socurl
without-Lsilently writes an HTML error page over your feed. And the
phishstats.info/phish_score.csvURL in the plugin's own documentation
currently returns 404.
Questions
- Is there a supported way to load a SpamAssassin plugin early enough that its
stockifplugin-guarded rules activate? A persistent.pre— or a second
textarea next to the custom-rules field that gets written to a.pre— would
solve it without anyone having to copy upstream rule definitions. - Is not running
sa-updatea deliberate choice? If so, what's the reasoning,
and is there a recommended way to keep the static rule set reasonably current
given that/var/lib/spamassassindoesn't survive an app update? - Would you consider enabling FromNameSpoof by default? It needs no external
service, no feed, and no configuration, and display-name spoofing ("Rabobank"
from a random domain) is one of the most common phishing shapes we see. - For external data files that a plugin reads at startup (like the phishing
feed): is a host cron doingdocker exec mail supervisorctl restart spamdthe
intended approach, or is there a cleaner hook?
Happy to test anything on my side — I have three servers and a corpus of real
phishing that got through, so I can measure rather than guess. - The
-
There's too much AI text here to investigate but sa-update is now run periodically. I fixed this recently.
Also, the plan is not to expose spamassassin and it's features/plugins. We will simply integrate the use cases and never implementation details. Currently, the custom SA config is allowed in the UI but this was a mistake on our side.
-
sorry @girish it was indeed written by Claude Code Opus 5. But realise that in this AI world I noticed on my 3 Cloudron-servers a huge increase of attacks, spam and phishing (and the last one was once "successful" for one user with nasty results).
As far as I can see the default "anti-spam / anti-phishing" in Cloudron is absolutely not sufficient, @d19dotca was "the pioneer" here on the forum with custom SA rules and I fight "attackers with AI" with my own "AI"
.The only thing I ask you devs is to increase attention for "attacks, spam and phishing" and tell me what I can do if you don't/can't.
-
@imc67 no worries. But yeah fighting spam has become overly complicated and products don't exist yet to tackle this kind of scale.
Currently, our gitlab is under attack. Almost 40k unique IPs. I took the IPs from our access.log and not some random blocklist. In fact, I blocked gitlab for everyone now and just put the company in a whitelist for the moment. I understand this is impractical for Email (our email too is under constant attack).

-
@imc67 no worries. But yeah fighting spam has become overly complicated and products don't exist yet to tackle this kind of scale.
Currently, our gitlab is under attack. Almost 40k unique IPs. I took the IPs from our access.log and not some random blocklist. In fact, I blocked gitlab for everyone now and just put the company in a whitelist for the moment. I understand this is impractical for Email (our email too is under constant attack).

@girish indeed I noticed git.cloudron.io gave a 403.
As you might remember I created my own "security monitor" for centrally monitoring and managing 3 Cloudron servers, it's growing with hundreds a day, the list below are lines, the unique IP's are 641.451.742 separate IPv4-adressess:

-
@girish indeed I noticed git.cloudron.io gave a 403.
As you might remember I created my own "security monitor" for centrally monitoring and managing 3 Cloudron servers, it's growing with hundreds a day, the list below are lines, the unique IP's are 641.451.742 separate IPv4-adressess:

-
haha (sorry) now you experience the same as I do, every hour the newly to be blocked IP's are pushed via API and it takes 4-6 minutes to be processed. That's why I suggested previously in a post to maken it "insert/update/delete" per IP or CIDR.
Good luck!
-
@imc67 no worries. But yeah fighting spam has become overly complicated and products don't exist yet to tackle this kind of scale.
Currently, our gitlab is under attack. Almost 40k unique IPs. I took the IPs from our access.log and not some random blocklist. In fact, I blocked gitlab for everyone now and just put the company in a whitelist for the moment. I understand this is impractical for Email (our email too is under constant attack).

-
There's too much AI text here to investigate but sa-update is now run periodically. I fixed this recently.
Also, the plan is not to expose spamassassin and it's features/plugins. We will simply integrate the use cases and never implementation details. Currently, the custom SA config is allowed in the UI but this was a mistake on our side.
Also, the plan is not to expose spamassassin and it's features/plugins. We will simply integrate the use cases and never implementation details. Currently, the custom SA config is allowed in the UI but this was a mistake on our side.
Hmm, I am not sure if I agree with that view. Why do you think that was a mistake? If it wasn't customizable, many of us would be getting many more times the spam we already do because the default SpamAssassin rules are pretty awful these days with the aggressive spam nature as a result of so many more bots and more sophisticated spam attacks.
Please don't remove that. If anything, I am in agreement with @imc67 that we need more controls. Cloudron is maturing, and with the help of AI many people are getting more advanced in their skillsets, and hiding things away from technical admins only increase friction with using Cloudron. I do hope that this will be reconsidered, and that more controls can be exposed for us.
On a separate note about the SpamAssassin sa-update and sa-learn stuff that Cloudron has a script for, I was recently looking into that myself and comparing it with a script I wrote myself for running some SpamAssassin scans for learning ham and spam (BAYES training). Here is an AI summary of this for reference:
The bundled /app/code/spamassassin-config/learn-from-user-mail.sh, scheduled daily at 22:00 UTC by /app/code/service.js, only identifies paths under .Spam as spam. Its ham search excludes exact .Drafts, .Spam, and .Trash paths, but it does not correctly handle: - .Junk or nested Junk folders, which are learned as ham - .Deleted Messages, which is learned as ham - nested Trash folders such as .Trash.CANCELLATIONS - Sent/Sent Messages, with no option for administrators to exclude them - folder and message paths containing spaces, because results from find are processed through whitespace-splitting shell variables - messages above the default spamc 512 KB limit The script also applies head -n 200 to unsorted find output, so the selected messages are not necessarily the newest 200. Could the learner use Dovecot special-use metadata (\Junk, \Trash, \Sent, and \Drafts) or, at minimum, robust Maildir folder-name matching with NUL-delimited paths? It would also be helpful to make Sent-folder learning configurable and provide an administrator option to disable or customize the bundled automatic learner. -
Also, the plan is not to expose spamassassin and it's features/plugins. We will simply integrate the use cases and never implementation details. Currently, the custom SA config is allowed in the UI but this was a mistake on our side.
Hmm, I am not sure if I agree with that view. Why do you think that was a mistake? If it wasn't customizable, many of us would be getting many more times the spam we already do because the default SpamAssassin rules are pretty awful these days with the aggressive spam nature as a result of so many more bots and more sophisticated spam attacks.
Please don't remove that. If anything, I am in agreement with @imc67 that we need more controls. Cloudron is maturing, and with the help of AI many people are getting more advanced in their skillsets, and hiding things away from technical admins only increase friction with using Cloudron. I do hope that this will be reconsidered, and that more controls can be exposed for us.
On a separate note about the SpamAssassin sa-update and sa-learn stuff that Cloudron has a script for, I was recently looking into that myself and comparing it with a script I wrote myself for running some SpamAssassin scans for learning ham and spam (BAYES training). Here is an AI summary of this for reference:
The bundled /app/code/spamassassin-config/learn-from-user-mail.sh, scheduled daily at 22:00 UTC by /app/code/service.js, only identifies paths under .Spam as spam. Its ham search excludes exact .Drafts, .Spam, and .Trash paths, but it does not correctly handle: - .Junk or nested Junk folders, which are learned as ham - .Deleted Messages, which is learned as ham - nested Trash folders such as .Trash.CANCELLATIONS - Sent/Sent Messages, with no option for administrators to exclude them - folder and message paths containing spaces, because results from find are processed through whitespace-splitting shell variables - messages above the default spamc 512 KB limit The script also applies head -n 200 to unsorted find output, so the selected messages are not necessarily the newest 200. Could the learner use Dovecot special-use metadata (\Junk, \Trash, \Sent, and \Drafts) or, at minimum, robust Maildir folder-name matching with NUL-delimited paths? It would also be helpful to make Sent-folder learning configurable and provide an administrator option to disable or customize the bundled automatic learner.@d19dotca both the statements are orthogonal. Cloudron can provide feature to tackle spam and make it more accessible for all users. At the same time, Cloudron can also not expose implementation details like SpamAssassin configs. In fact, there are newer solutions out there like say rspamd which even help with integrating email with LLM. But because Cloudron exposed SA we are now "stuck" and possibly have to break support at some point.
We have always taken this approach to our product, so it's not anything new. We don't expose docker configs, haraka configs, dovecot configs, nginx configs etc. It's not how we do things. The SpamAssasin config was done only because an immediate problem had to be fixed and was done in haste.
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login
thing is in some of our servers even the hash list of the kernel is slow when adding so many IPs !