- Python 88%
- HTML 11.6%
- Shell 0.3%
- Mako 0.1%
| alembic | ||
| deploy | ||
| docs | ||
| fediguard | ||
| helm/fediguard | ||
| tests | ||
| .gitignore | ||
| alembic.ini | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
______ _ _ _____ _
| ____| | (_)/ ____| | |
| |__ ___ __| |_| | __ _ _ __ _ _ __ __| |
| __/ _ \/ _` | | | |_ | | | |/ _` | '__/ _` |
| | | __/ (_| | | |__| | |_| | (_| | | | (_| |
|_| \___|\__,_|_|\_____|\__,_|\__,_|_| \__,_|
FediGuard
Autonomous spam protection for the Fediverse.
FediGuard is a self-hosted microservice that monitors email bounces from Mastodon instances, automatically detects and removes bot/spam accounts, blocklists disposable email domains, and federates blocklists with other instances via ActivityPub.
Built for instance administrators who are tired of manually hunting spam registrations. FediGuard watches your mail server, makes intelligent decisions, and acts — while keeping humans in the loop for edge cases.
What It Does
When someone registers on your Mastodon instance with a fake or disposable email, the confirmation email bounces. FediGuard catches that bounce and acts:
| Bounce Type | Action |
|---|---|
| Disposable domain (mailinator, guerrillamail, etc.) | Block domain + delete account immediately |
| No TLS support | Block domain (bot infrastructure) |
| Nonexistent mailbox on trusted provider (e.g. Gmail) | Delete account only (don't block Gmail!) |
| Hard bounce from unknown domain | Review on 1st occurrence, auto-block on 2nd |
| Soft bounce (temporary, quota full) | Ignore completely |
Every action is logged in an immutable audit trail. Users can appeal via a public form. Admins review everything through a terminal-aesthetic web GUI.
Architecture
┌─────────────────────────────────────┐
│ PostgreSQL │
│ (encrypted credentials, blocklists, │
│ audit log, tenant config) │
└──────────┬──────────┬───────────────┘
│ │
┌─────────────┘ └──────────────┐
│ │
┌────────┴────────┐ ┌─────────┴────────┐
│ fediguard-web │ │ fediguard-worker │
│ │ │ │
│ FastAPI + HTMX │ │ IMAP polling │
│ Admin GUI │ │ Bounce processing│
│ OAuth2 login │ │ Domain blocking │
│ ActivityPub │ │ Account deletion │
│ Health checks │ │ Rate limiting │
└────────┬────────┘ └─────────┬────────┘
│ │
├── Mastodon Admin API ─────────────────┤
│ (suspend/delete accounts, │
│ email domain blocks) │
│ │
└── Federation Peers ───────────────────┘
(ActivityPub blocklist exchange)
Two separate processes share the same PostgreSQL database:
- Web serves the GUI, handles OAuth, exposes ActivityPub endpoints
- Worker polls IMAP, processes bounces, executes actions
Features
Bounce Intelligence
- RFC 3464 DSN parser with authenticity validation (rejects forged bounces)
- Cross-references against 3 disposable email lists (5000+ domains)
- DNS/MX analysis, Spamhaus RBL checks, reverse IP co-hosting detection
- Rate-limited HackerTarget lookups for IP expansion
- Safe domain protection — Gmail, Outlook, ProtonMail, Disroot, Riseup, etc. are never blocklisted
Multi-Tenant
- Each Mastodon instance is a tenant with isolated data
- Credentials (IMAP, Mastodon API token) encrypted with AES-256-GCM in PostgreSQL
- Configure everything through the GUI — zero credentials in config files
- OAuth2 login via Mastodon (admin/moderator role required)
Security Hardened
- SSRF prevention on all outbound HTTP requests (private IP ranges blocked)
- CSRF protection on all POST routes (itsdangerous tokens)
- Bounce forgery detection (RFC 3464/5321 compliance checks)
- Rate limiting (10 deletions/hour, 100 domain blocks/hour)
- PII redaction in all logs (email hashing, IP masking)
- Immutable audit log (cannot be updated or deleted, even by admins)
- Security headers (CSP, HSTS, X-Frame-Options, Referrer-Policy)
- Secret key validation (fail-fast on startup if default/missing)
Federation (ActivityPub)
- Federate blocklists between FediGuard instances
- PGP-signed blocks with revocation support
- Per-peer circuit breaker (auto-pause after 5 consecutive failures)
- Reciprocity tracking (detect free-rider peers)
- Protocol versioning headers for forward compatibility
Compliance (LGPD/GDPR)
- User appeals — public form, admin approve/reject workflow
- Data retention — configurable per-table cleanup (bounces 90d, audit 2yr)
- Data export — JSON export of all non-PII tenant data
- Appeal link included in notification messages (DM + email)
Operational
- Health checks (
/health,/health/ready,/health/live) for Kubernetes - Graceful shutdown (SIGTERM handling, flush pending writes)
- Alert system with deduplication (IMAP failures, API errors, rate limits)
- Task visibility page with auto-refresh
- Helm chart stubs and systemd service files
Quick Start
Prerequisites
- Python 3.11+
- PostgreSQL 14+
- A Mastodon instance with admin access
- An IMAP mailbox receiving bounce emails
Install
git clone https://github.com/popsolutions/fediguard.git
cd fediguard
# Install dependencies
uv sync # or: pip install -e .
# Create PostgreSQL database
createdb fediguard
# Configure boot settings
cp .config/settings.env.example .config/settings.env
# Edit: set DATABASE_URL, SECRET_KEY, BASE_URL
Configure
The settings.env file only needs boot-level config:
# Required
DATABASE_URL=postgresql://fediguard:password@localhost/fediguard
SECRET_KEY=<generate with: python3 -c 'import secrets; print(secrets.token_hex(32))'>
BASE_URL=https://fediguard.your-instance.social
# Environment
ENV=production
FORCE_HTTPS=true
ALLOWED_HOSTS=fediguard.your-instance.social
All credentials (Mastodon API token, IMAP host/user/password) are configured through the web GUI and stored encrypted in PostgreSQL. No secrets in files.
Run Database Migrations
alembic upgrade head
Start
# Web server (GUI + API + federation)
fediguard-web
# Worker (IMAP polling + bounce processing)
fediguard-worker
First Login
- Open
https://fediguard.your-instance.social - Click Admin Login — authenticates via Mastodon OAuth2
- Go to SETTINGS — enter your Mastodon admin token and IMAP credentials
- Click Save Credentials — encrypted and stored in PostgreSQL
- The worker will start processing bounces on next poll cycle
GUI
Terminal-aesthetic interface built with HTMX + Alpine.js:
| Page | Purpose |
|---|---|
| Dashboard | Bounce stats, recent activity |
| Bounces | All processed bounce events with verdicts |
| Domains | Blocked email domains (synced with Mastodon) |
| Users | Flagged accounts pending review |
| Network | Federation peers and reciprocity health |
| Tasks | Background task results (auto-refresh) |
| Safelist | Manage trusted domains + disposable list sources |
| Alerts | Operational alerts (acknowledge/resolve) |
| Appeals | User appeal requests (approve/reject) |
| Settings | Tenant config + encrypted credential management |
| Audit | Immutable log of all actions |
Domain Classification Logic
Bounce received
│
├─ Soft bounce (4.x.x)? → IGNORE
│
├─ Safe domain (Gmail, etc.)?
│ ├─ Nonexistent mailbox? → DELETE account (don't block domain)
│ └─ Other bounce? → IGNORE
│
├─ Already blocked? → IGNORE
│
├─ No TLS? → BLOCK domain immediately
│
├─ Disposable email list? → BLOCK domain
│
├─ RBL listed IP? → BLOCK domain + co-hosted domains
│
└─ Hard bounce
├─ 1st occurrence → REVIEW (create UserReview for admin)
└─ 2nd occurrence → BLOCK domain
Safety Guarantees
- Soft bounces NEVER trigger any action — temporary issues are ignored
- Trusted providers are NEVER domain-blocked — Gmail, Outlook, ProtonMail, Disroot, Riseup, and 15+ others are protected
- Rate limiting prevents runaway deletions — max 10 accounts/hour, 50/day
- Every action is audit-logged — immutable, with admin username and IP
- Users can appeal — public form with no login required
- Dry-run mode — test everything without executing actions
Tests
uv run pytest tests/ -v
359 tests covering:
- Domain classification logic (safe domains, disposable, no-TLS, hard bounce thresholds)
- DELETE_ACCOUNT_ONLY verdict for trusted domain + nonexistent mailbox
- SSRF prevention (private IPs, localhost, metadata endpoints, DNS rebinding)
- Bounce authenticity validation (RFC 3464 compliance)
- Rate limiter (hourly/daily limits, independent actions)
- Audit log immutability (cannot update or delete entries)
- PII redaction (email hashing, IP masking, token redaction)
- CSRF protection (valid/invalid/missing tokens)
- Input validation (domain format, URL scheme, port restrictions)
- Circuit breaker state machine (closed → open → half-open → closed)
- Data retention (old records deleted, audit log preserved)
- User appeals (submit, approve, reject, PII protection)
- All web routes (auth, HTMX partials, CRUD operations)
- Credential encryption (AES-256-GCM roundtrip, partial updates)
- Worker startup validation (DB, tenant, credentials)
Production Deployment
systemd
sudo cp deploy/systemd/fediguard-web.service /etc/systemd/system/
sudo cp deploy/systemd/fediguard-worker.service /etc/systemd/system/
sudo systemctl enable --now fediguard-web fediguard-worker
Kubernetes (Helm)
helm install fediguard helm/fediguard/ \
--set env.SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))') \
--set env.DATABASE_URL=postgresql://... \
--set env.BASE_URL=https://fediguard.your-instance.social
Health probes are preconfigured:
- Liveness:
GET /health/live - Readiness:
GET /health/ready
Federation Protocol
FediGuard instances exchange blocklists via ActivityPub. See docs/federation-protocol.md for the full protocol specification.
{
"type": "Add",
"actor": "https://fediguard.example.com/federation/actor",
"object": {
"type": "FediGuardBlock",
"domain": "spam.example.com",
"reason": "DISPOSABLE",
"pgp_signature": "-----BEGIN PGP SIGNATURE-----..."
}
}
Project Structure
fediguard/
├── main.py # Legacy entrypoint + bounce processing cycle
├── worker.py # Worker process (IMAP polling)
├── database.py # 19 SQLAlchemy models
├── domain_judge.py # Bounce classification engine
├── mastodon_client.py # Mastodon Admin API client
├── bounce_watcher.py # IMAP + RFC 3464 DSN parser
├── credentials.py # AES-256-GCM credential management
├── settings.py # Boot-level configuration
├── alerts.py # Operational alerting
├── activitypub.py # ActivityPub federation
├── federation.py # Peer management + reciprocity
├── crypto.py # PGP + AES encryption
├── notifier.py # DM + email notifications
├── retroactive.py # Retroactive domain scanning
├── scheduler.py # APScheduler job definitions
├── security/
│ ├── ssrf.py # SSRF prevention
│ ├── bounce_validator.py # RFC 3464 authenticity checks
│ ├── rate_limiter.py # Token bucket rate limiting
│ ├── audit.py # Immutable audit logging
│ ├── circuit_breaker.py # Per-peer circuit breaker
│ └── logging.py # PII-redacting log formatter
├── compliance/
│ ├── retention.py # Data retention cleanup
│ └── appeals.py # User appeal workflow
└── web/
├── app.py # FastAPI factory + security middleware
├── auth.py # OAuth2 via Mastodon
├── csrf.py # CSRF protection
├── middleware.py # Tenant resolution
├── schemas.py # Pydantic input validation
├── routes/ # 11 route modules
└── templates/ # 27 Jinja2 templates (HTMX)
License
AGPL-3.0
Author
Marcos Mendez — Pop.Coop
Built to protect organica.social and the broader Fediverse from spam, bots, and disposable email abuse.