Custom CrowdSec parser and scenarios for detecting attacks on Stalwart mail server.
This integration provides:
- Log parser for Stalwart's structured log format
- Attack scenarios for brute force, scanning, and abuse detection
- Full coverage across SMTP, IMAP, ManageSieve, and HTTP (WebAdmin/JMAP)
Stalwart has built-in IP blocking, but it cannot update firewall rules. This integration delegates all blocking to CrowdSec, which can leverage firewall bouncers (iptables, nftables, etc.) for true network-level protection.
| Event | Description | Level |
|---|---|---|
auth.success |
Successful authentication | INFO |
auth.failed |
Failed authentication | DEBUG |
auth.error |
Authentication error | ERROR |
auth.too-many-attempts |
Too many auth attempts | WARN |
| Event | Description | Level |
|---|---|---|
smtp.ehlo |
EHLO command | INFO |
smtp.invalid-ehlo |
Invalid EHLO command | INFO |
smtp.auth-not-allowed |
Auth attempt on port 25 | INFO |
smtp.spf-ehlo-fail |
SPF EHLO check failed | INFO |
smtp.spf-from-fail |
SPF From check failed | INFO |
smtp.iprev-fail |
IP reverse DNS check failed | INFO |
| Event | Description | Level |
|---|---|---|
imap.connection-start |
IMAP connection started | DEBUG |
imap.connection-end |
IMAP connection ended | DEBUG |
| Event | Description | Level |
|---|---|---|
manage-sieve.connection-start |
ManageSieve connection started | DEBUG |
manage-sieve.connection-end |
ManageSieve connection ended | DEBUG |
| Event | Description | Level |
|---|---|---|
http.connection-start |
HTTP connection started | DEBUG |
http.connection-end |
HTTP connection ended | DEBUG |
http.error |
HTTP error occurred | DEBUG |
| Event | Description | Level |
|---|---|---|
limit.concurrent-connection |
Concurrent connection limit reached | WARN |
limit.too-many-requests |
Too many requests | WARN |
limit.quota |
Quota limit reached | DEBUG |
| Scenario | Description | Capacity | Leak Speed | Trigger |
|---|---|---|---|---|
stalwart-auth-bf |
Authentication brute force | 5 | 10s | 5 auth failures in ~50s |
stalwart-user-enum |
User enumeration | 5 distinct | 10s | 5 different users from same IP |
stalwart-smtp-scan |
SMTP scanning/probing | 5 | 30s | 5 invalid EHLO or port 25 auth attempts |
stalwart-rate-limit |
Rate limit abuse | 3 | 60s | 3 rate limit events in ~3min |
stalwart-http-scan |
HTTP vulnerability scanning | 10 | 10s | 10 HTTP 404s on scan paths |
Stalwart uses a structured key-value log format:
<timestamp> <LEVEL> <message> (<event_type>) key = "value", key = value, ...
Important: Quoting is inconsistent across fields. String fields like listenerId and accountName are quoted ("smtp"), but remoteIp values are not quoted (remoteIp = 203.0.113.23). The parser uses "? in its grok pattern to handle both quoted and unquoted values.
2026-02-16T05:24:06Z INFO Authentication successful (auth.success) listenerId = "imaptls", localPort = 993, remoteIp = 198.51.100.10, remotePort = 39265, accountName = "user@example.com", accountId = 2
2026-02-16T04:42:56Z INFO Invalid EHLO command (smtp.invalid-ehlo) listenerId = "smtp", localPort = 25, remoteIp = 203.0.113.23, remotePort = 65208, domain = "User"
2026-02-16T04:42:56Z INFO Authentication not allowed (smtp.auth-not-allowed) listenerId = "smtp", localPort = 25, remoteIp = 203.0.113.23, remotePort = 65208
-
Disable Stalwart's built-in auto-ban (recommended, but not strictly required — CrowdSec can coexist with Stalwart's auto-ban):
# In /opt/stalwart/data/etc/config.toml server.auto-ban.abuse.rate = "0/1d" server.auto-ban.scan.rate = "0/1d"
-
Install files (two methods):
Method A: Copy files
# Parser cp parsers/s01-parse/stalwart-logs.yaml /opt/crowdsec/config/parsers/s01-parse/ # Scenarios cp scenarios/*.yaml /opt/crowdsec/config/scenarios/ # Log acquisition cp acquis.d/stalwart.yaml /opt/crowdsec/config/acquis.d/
Method B: Clone & Symlink (preferred —
git pullupdates everything)git clone https://github.com/<org>/crowdsec-stalwart.git \ /opt/crowdsec/config/crowdsec-stalwart # See INSTALL.md for full symlink instructions
-
Restart CrowdSec:
docker restart crowdsec
-
Verify:
docker exec crowdsec cscli parsers list docker exec crowdsec cscli scenarios list
See INSTALL.md for detailed instructions.
Stalwart uses separate listeners for each protocol:
| Listener ID | Port | Protocol | Auth |
|---|---|---|---|
smtp |
25 | SMTP | Not allowed |
submission |
587 | SMTP + STARTTLS | Required |
submissions |
465 | SMTPS | Required |
imaptls |
993 | IMAPS | Required |
https |
443 | HTTP/WebAdmin/JMAP | Required |
http |
80 | HTTP (ACME) | None |
sieve |
4190 | ManageSieve | Required |
- CrowdSec 1.7.6
- Stalwart 0.10+
- CrowdSec firewall bouncer (iptables/nftables)
- CrowdSec 1.4+
- Stalwart 0.10+
- CrowdSec firewall bouncer installed on the host
├── README.md # This file
├── LICENSE # MIT License
├── INSTALL.md # Installation instructions
├── parsers/
│ └── s01-parse/
│ └── stalwart-logs.yaml # Log parser
├── scenarios/
│ ├── stalwart-auth-bf.yaml # Auth brute force detection
│ ├── stalwart-user-enum.yaml # User enumeration detection
│ ├── stalwart-smtp-scan.yaml # SMTP scanning detection
│ ├── stalwart-rate-limit.yaml # Rate limit abuse detection
│ └── stalwart-http-scan.yaml # HTTP vulnerability scanning
├── acquis.d/
│ └── stalwart.yaml # Log acquisition config
└── test/
├── README.md # Testing instructions
└── samples/ # Sample log files
- Stalwart Documentation
- Stalwart Event Types
- CrowdSec Parser Documentation
- CrowdSec Scenario Documentation
MIT License - see LICENSE