BanBot - XMPP Multi-Room Ban Management Bot -
/ 
BanBot is an XMPP bot for centralized ban management across multiple MUC rooms (Multi-User Chat).
It provides admin-room based moderation, protects configured MUCs from unwanted users, and supports temporary bans, domain bans, ban synchronization, audit logging, ignorelists, health checks, protections, RTBL/PubSub integration, and optional OMEMO support for encrypted commands and replies.
Features
- 🛡️ Central admin room for all administrative commands
- 🧩 Dynamic addition/removal of protected rooms
- 🔒 Optional OMEMO support: encrypted commands receive encrypted replies
- ❌ Ban, temporary ban, unban, banlist, bansearch, why, and redaction commands
- 🌐 Domain-based bans (
*.domain.tld) to ban all users from a domain - ⏱️ Automatic temporary ban expiration with human-readable durations
- 📊 Smart duplicate ban handling with automatic conversion between permanent and temporary bans
- 🐞 Nick-only ban support with best-effort JID upgrade when the user rejoins
- ⚠️ Admin/owner protection for direct, nick-based, and domain-based bans
- 🚫 Global ignorelist/whitelist for exact JIDs and domain-based ban protection
- 📦 Startup and manual synchronization of room bans/admins
- 🏥 Health checks, reconnect awareness, admin-right monitoring, and dynamic
!status - 🛡️ RTBL subscriptions via PubSub for SHA-256 JID hashes and plaintext domain bans
- 🔄 Periodic RTBL refresh with quiet/no-change behavior and snapshot reconciliation
- ♻️ RTBL snapshot reconciliation with stale local ban cleanup
- 📡 Optional own RTBL publish feed for local bans
- 🧾 SQLite audit log and structured JSON event logs
- 💾 CSV import/export with managed safety backups
- 🗄️ Managed ZIP backup archives with manifest, restore command, verification, and automatic startup backups
- ✅ Startup/runtime config validation with safe
!reloadconfig - 🚦 Rate limiting for public protected-room commands
- 🧯 Protections for flood spam, first-message media, mention limits, wordlists, join waves, trusted reporters, and policy-change notifications
- 📜 Optional public room policy text via
!rules/!policy - ⬆️ Optional GitHub release checks
- 🖼️ Avatar/vCard support via XEP-0054, XEP-0084, and XEP-0153
- 🧪 Extensive pytest suite, coverage, property tests, mutation-testing support, and Drone CI
Installation / Quickstart
Requires Python 3.12+. The project is developed and tested with Python 3.13.
Recommended hardened systemd deployment
Keep the application checkout separate from mutable configuration and data:
/srv/adminbot/muc_banbot/ repository + virtualenv
/etc/muc_banbot/config.py runtime configuration
/var/lib/muc_banbot/ database, backups, exports and OMEMO state
Bootstrap the service account and checkout, pin a tagged release, then use the preservation-first deploy helper:
sudo useradd -m -s /bin/bash adminbot -d /srv/adminbot
sudo -u adminbot git clone https://git.envs.net/envs/muc_banbot.git /srv/adminbot/muc_banbot
cd /srv/adminbot/muc_banbot
git fetch --tags
LATEST_TAG="$(git tag --sort=-v:refname | head -n1)"
git checkout "$LATEST_TAG"
./scripts/deploy.sh install --dry-run
sudo ./scripts/deploy.sh install
The deploy frontend uses the shared envs-xmpp operations layer. On a fresh checkout it bootstraps the exact deployment-tooling version into $XDG_CACHE_HOME/envs-xmpp/deploy/ (or ~/.cache/envs-xmpp/deploy/) before continuing; no manual pre-install is required. ENVS_XMPP_DEPLOY_SOURCE can point to a local checkout or wheel for pre-release testing.
On a fresh install the helper creates /etc/muc_banbot/config.py once, with
absolute mutable paths below /var/lib/muc_banbot, and then stops so credentials
can be edited safely. Hardened deployments must keep DB_FILE, DB_BACKUP_DIR,
EXPORT_DIR, and OMEMO_STORAGE_FILE below the configured data directory; old
relative source-tree paths are rejected by deploy.sh check. The expected
permission baseline is /etc/muc_banbot 0750, config.py 0600, and
/var/lib/muc_banbot 0700, owned by the service user/group. Rerun
sudo ./scripts/deploy.sh install after editing the config. Existing config,
database/data and systemd unit files are never overwritten automatically. A bare
./scripts/deploy.sh only prints help.
Useful read-only checks:
./scripts/deploy.sh status
sudo ./scripts/deploy.sh check
The recommended contrib/muc_banbot.service uses
Type=notify, WatchdogSec=60, ProtectSystem=strict, disables Python
bytecode writes in /etc, and grants write access only to /etc/muc_banbot
and /var/lib/muc_banbot. See
docs/deployment.md for the complete deployment and
migration notes.
PyPI installation
BanBot is also published on PyPI as muc-banbot. For local testing, development
environments, or non-systemd installs it can be installed directly into a
virtualenv:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install muc-banbot
# Optional OMEMO support:
# pip install "muc-banbot[omemo]"
python -m pip show muc-banbot
To run the wheel directly, create an operator config.py in the working
directory (or point MUC_BANBOT_CONFIG at one). The installed sample can be
copied without a source checkout:
python - <<'PY'
from pathlib import Path
import config_sample
Path("config.py").write_bytes(Path(config_sample.__file__).read_bytes())
PY
chmod 600 config.py
$EDITOR config.py
muc_banbot
The wheel includes the default avatar as a packaged read-only asset, so the
default AVATAR_PATH = "avatar.png" also works outside a source checkout. An
existing working-directory avatar.png still takes precedence, and configured
subpaths or absolute paths keep their historical operator-controlled semantics.
The PyPI package does not create /etc/muc_banbot, /var/lib/muc_banbot, or a
systemd unit; the tagged Git checkout plus ./scripts/deploy.sh remains the
recommended production deployment path.
Legacy/source-tree installation (still supported)
The historical installation layout remains supported for operators who prefer manual control or are not ready to migrate existing systems:
sudo useradd -m -s /bin/bash adminbot -d /srv/adminbot
sudo su - adminbot
cd /srv/adminbot
git clone https://git.envs.net/envs/muc_banbot.git
cd muc_banbot
git fetch --tags
LATEST_TAG="$(git tag --sort=-v:refname | head -n1)"
git checkout "$LATEST_TAG"
python3 -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
pip install -e .
# Optional OMEMO support:
# pip install -e ".[omemo]"
install -m 0600 config_sample.py config.py
$EDITOR config.py
muc_banbot
In this mode relative paths such as DB_FILE = "banbot.db" and
DB_BACKUP_DIR = "data/backups" continue to resolve below the checkout. The
legacy python muc_banbot.py launcher also remains supported. For systemd, use
contrib/muc_banbot-legacy.service or keep
your existing unit. MUC_BANBOT_CONFIG remains available for custom layouts.
main is the development branch. Production deployments should use stable
vX.Y.Z release tags.
Updating to a New Release
The deploy helper is the recommended update path:
cd /srv/adminbot/muc_banbot
./scripts/deploy.sh update --dry-run
sudo ./scripts/deploy.sh update
# Explicit release when desired:
# sudo ./scripts/deploy.sh update --to v2.6.4
It refuses tracked local Git modifications, never deploys main automatically,
queries remote release tags without bulk-overwriting local tags, fetches only
the selected stable tag, asks separately before stopping/starting the service,
and creates a consistent SQLite pre-update backup while the service is stopped.
For legacy source-tree layouts it additionally protects operator files inside
the checkout across the tag switch.
The previous fully manual update workflow remains supported:
cd /srv/adminbot/muc_banbot
git fetch --tags
LATEST_TAG="$(git tag --sort=-v:refname | head -n1)"
git checkout "$LATEST_TAG"
source venv/bin/activate
pip install -e .
# Optional: pip install -e ".[omemo]"
sudo systemctl restart muc_banbot
If release notes mention new configuration options, compare the active config
with config_sample.py and add settings you want to customize.
Minimal Configuration
Copy config_sample.py to config.py and set at least:
JID = "adminbot@example.org"
PASSWORD = "secret"
RESOURCE = "service"
ADMIN_ROOM = "admin@conference.example.org"
NICK = "BanBot"
DB_FILE = "banbot.db"
Most runtime settings can be reloaded with !reloadconfig. Startup-only settings such as the bot account/room identity, database path, RTBL enable/publish setup, and OMEMO setup require a restart.
See docs/configuration.md for the full configuration reference.
Important Commands
Examples assume the default command prefix !.
| Command | Description |
|---|---|
!help [all|page|last] / !help <command> |
Show available commands or focused help for every command topic, including subtopics such as room invite and rtbl publish |
!status |
Show bot health, uptime, rooms, bans, RTBL, DB state, and protection status |
!tasks [all|failed] |
Show supervised background workers, restart/backoff state, restart counts, and runtime/systemd watchdog health |
!config [all|page|last] / !config show [all|page|last] |
Show active configuration grouped in config_sample.py section order; secrets are hidden |
!config search/find <query> |
Search config option names and displayed values |
!config diff [all|page|last] |
Show current values that differ from config_sample.py defaults |
!config set <KEY> <value> |
Change a runtime-writable configuration value |
!config unset <KEY> |
Reset a runtime-writable configuration value to config_sample.py default |
!reload / !reloadconfig |
Validate and reload runtime configuration |
!restart confirm |
Stop the bot so a supervisor can restart it |
!checkupdate / !updatecheck |
Check whether a newer release is available |
!whoami |
Show your affiliation, role, and permissions |
!audit [all/page/last/query] |
Show audit log entries |
!backup |
Create a managed full ZIP backup archive |
!backup list [all/page/last] |
List managed full backup archives |
!backup show <file/latest> |
Inspect one managed backup archive |
!backup verify <file/latest> |
Verify a managed backup archive |
!backup delete/remove/del/rm <file/latest> |
Delete a managed full backup archive |
!restore <file/latest> confirm |
Restore a managed full backup archive |
!room add <room> |
Add a protected room |
!room remove/delete/del/rm <room> |
Remove a protected room |
!room list [all/page] |
List protected rooms with join state and bot affiliation |
!room rejoin <room/all> |
Retry joining one or all protected rooms |
!room invite list [all/page/last] |
List pending room invites |
!room invite accept/decline/remove/delete/del/rm <id> |
Accept or decline a pending room invite |
!policy / !rules show/set/clear/delete/remove/enable/disable |
Manage public room policy text |
!ban <jid/nick/domain> [comment] |
Add a permanent ban or update an existing ban reason |
!tempban <jid/nick> <10m/2h/1d> [comment] |
Add or update a temporary ban; omitted comments preserve the old reason |
!unban <jid/nick/domain> |
Remove a ban |
!redact <jid> [reason] / !redact id ... / !redact cleanup |
Redact indexed messages or clean old redaction index entries |
!protections list [all/page/last] |
List protection enabled/disabled and observe state |
!protection enable/disable <name> |
Toggle a protection |
!protections <name> config/set |
Show or edit one protection config |
!report <nick/jid> [reason] |
Trusted reporter command when enabled |
!banlist / !blacklist [all/page/last] |
Show active bans |
!banlist rtbl / !blacklist rtbl [all/page/last] |
Show raw RTBL hash/domain entries |
!bansearch <query> [all/page/last] |
Search bans by target, issuer, comment, or RTBL reason |
!why <nick/jid> |
Explain why a user is banned |
!ignore [list/all/page/last] |
Show the global ignorelist |
!ignore add/remove/delete/del/rm <jid/domain> |
Manage protected exact JIDs and domains |
!whitelist [list/all/page/last/add/remove/delete/del/rm] |
Alias for !ignore ... |
!sync |
Rejoin rooms, verify admin rights, and enforce active bans |
!syncadmins |
Update admin list from the admin room |
!syncbans |
Sync bans from rooms into the database and enforce them |
!omemo status |
Inspect OMEMO readiness and storage state |
!omemo devices |
List visible admin-room recipients and local storage hints |
!omemo reset [confirm] |
Rotate local OMEMO storage after confirmation |
!rtbl list [all|page|last], !rtbl add/delete/remove/refresh |
Manage RTBL subscriptions |
!rtbl publish status/sync |
Manage the bot's own RTBL publish feed |
!export [list/show/delete/remove/del/rm] |
Manage CSV ban exports |
!import <file> [dryrun] |
Import bans from CSV with validation and optional dry-run |
For paginated commands, the standalone all argument disables paging and prints the complete result set. Examples: !audit all, !banlist all, !banlist rtbl all, !bansearch all spam, !ignore list all, !whitelist all, and !room list all.
Full command reference: docs/commands.md.
Room Invite Service
When ROOM_INVITES_ENABLED=True, BanBot can receive MUC invites for potential protected rooms. Invites are announced in the admin room and must be accepted or declined with !room invite commands. Pending invites older than ROOM_INVITE_MAX_AGE_DAYS are expired automatically; set it to 0 to keep them indefinitely. BanBot does not auto-join invited rooms.
Message Redaction
Optional redaction support indexes room-assigned stanza IDs for messages BanBot sees in protected rooms. Message bodies are not stored. Admins can redact all known messages from a bare JID with !redact <jid> [reason] or target a specific stanza ID with !redact id <room_jid> <stanza_id> [reason].
If a server applies a moderation request without returning a usable IQ result or live confirmation, BanBot verifies the resulting moderation tombstone through the room's MAM archive before counting the request as successful. Servers without verifiable MAM results remain reported as Unconfirmed rather than failed.
See docs/commands.md and docs/configuration.md.
Protections
BanBot includes an optional protection system for common MUC spam and abuse patterns. Protections can be listed, enabled, disabled, and tuned at runtime from the admin room:
!protections list all
!protection enable flood
!protections flood config
!protections flood set max_messages 10
Currently available protections cover flood spam, repeated/similar messages, first-message media spam, excessive mentions, monitored words from new joiners, join waves, trusted reporter workflows, and policy-change notifications.
Use conservative settings first and enable individual protections per need. For active rooms, notify or short tempban settings are useful while tuning thresholds; stronger actions such as permanent bans should only be enabled after the behavior is verified for your community.
See docs/protections.md for all protection names, aliases, actions, configuration keys, and operational notes.
OMEMO
BanBot supports optional OMEMO replies. OMEMO dependencies are not required for normal plaintext operation. If OMEMO_ENABLED=True but the optional Python/system libraries are missing, BanBot starts with OMEMO disabled and logs a clear warning.
The behavior is dynamic:
plaintext command -> plaintext reply
OMEMO command -> OMEMO reply
Encrypted MUC replies are sent to current occupants with visible real JIDs as far as possible. Occupants with unusable OMEMO devices are skipped; plaintext fallback is controlled by configuration. Admins can inspect local OMEMO state with !omemo status, show current visible admin-room OMEMO recipients with !omemo devices, and rotate the local OMEMO store with !omemo reset confirm when the bot identity changed or devices got stale. !omemo devices also shows conservative local storage hints, but those hints are diagnostic only and may be stale.
See docs/omemo.md.
RTBL / PubSub
BanBot can subscribe to RTBL PubSub nodes containing SHA-256 bare-JID hashes and plaintext domain bans. Successful refreshes reconcile local RTBL cache state with the current node snapshot. Removed RTBL items are cleaned up locally, and stale issuer=rtbl bans are automatically unbanned.
!rtbl add xmppbl.org muc_bans_sha256
!rtbl add xmppbl.org spam_source_domains
!rtbl refresh
!banlist rtbl all
BanBot can also publish local non-RTBL bans to its own RTBL feed.
See docs/rtbl.md and docs/rtbl_pubsub-setup.md.
Tests and CI
Install dev dependencies and run tests:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
pytest
Run with coverage:
pytest --cov=banbot --cov-report=term-missing
The test suite also includes Hypothesis-based property tests for pure helper logic such as duration parsing, human-readable time formatting, JID/domain normalization, RTBL utilities, paging helpers, and ban-target normalization.
Drone CI runs the offline pytest suite with coverage on pushes and tags to main. Live XMPP/Prosody and OMEMO integration tests are opt-in and skipped by default.
See docs/testing.md for the full testing workflow and tests/README.md for the test-suite layout.
Documentation
The full documentation is split into focused guides. Start with the documentation index, or jump directly to a topic:
- Architecture
- Configuration
- Commands
- Backups and Restore
- Rooms and Invites
- Import / Export
- OMEMO
- RTBL / PubSub
- Prosody PubSub Setup
- Public Policy / Rules
- Protections
- Admin Protection
- Database
- Testing and CI
- Release Checklist
- Troubleshooting
Security Notes
- The bot account must have admin/owner rights in every protected room.
- The admin room is the single source of truth for command permissions.
- Admins/owners are protected from manual, nick-based, domain-based, and RTBL-applied bans.
- Domain bans such as
*.domain.tldreject overly generic targets such as*.com. - CSV imports create a managed full backup before writing data, but dry-runs do not create backups or change the database.
- The ignorelist protects exact JIDs from all bans and domains from domain-based/RTBL domain matches.
- If RTBL publishing is enabled, ensure the configured PubSub nodes are not writable by arbitrary users.
Moderation inspection and editing
!baninfo <target>shows the complete current ban metadata.!history <target> [all|page|last]shows the audit-backed moderation history.!banedit <target> reason <text>changes a reason.!banedit <target> duration <duration>resets a tempban from now.!banedit <target> extend|reduce <duration>adjusts a tempban.!banedit <target> permanentconverts a tempban to a permanent ban.!banedit <target> temp <duration>converts or resets a ban to a tempban.!banedit <nick> jid <user@domain.tld>converts a nick-only ban into a JID ban.
Protection observe mode can be enabled with !protections <name> observe on. Matching events are announced and audited, but no kick, ban, redaction, warning, or lockdown is applied. Disable it with observe off or enforce. !protections list marks action-capable protections with [observe] and the notification-only PolicyChangeNotification with [notify-only]; attempting to enable observe mode for a notification-only protection is rejected.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file muc_banbot-3.0.0.tar.gz.
File metadata
- Download URL: muc_banbot-3.0.0.tar.gz
- Upload date:
- Size: 359.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4674517bcd3931aa2f3042bf61c074d682b6a208004d2644a2ee024eab6f314
|
|
| MD5 |
475d94c81e626df1f00a95066547e106
|
|
| BLAKE2b-256 |
0b5a159ece63542c0cf2b29c02320a73bef5a15bc6df3408eb95c3959098e492
|
Provenance
The following attestation bundles were made for muc_banbot-3.0.0.tar.gz:
Publisher:
release.yml on envs-net/muc_banbot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
muc_banbot-3.0.0.tar.gz -
Subject digest:
c4674517bcd3931aa2f3042bf61c074d682b6a208004d2644a2ee024eab6f314 - Sigstore transparency entry: 2770086170
- Sigstore integration time:
-
Permalink:
envs-net/muc_banbot@bad81d8ec51ea1f1df708aa686ab1936327ed816 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/envs-net
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bad81d8ec51ea1f1df708aa686ab1936327ed816 -
Trigger Event:
push
-
Statement type:
File details
Details for the file muc_banbot-3.0.0-py3-none-any.whl.
File metadata
- Download URL: muc_banbot-3.0.0-py3-none-any.whl
- Upload date:
- Size: 271.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c711b9b0a60cfdc85dc71fa82a6a32665478ff16d1cf649a818ea585a9c210a
|
|
| MD5 |
44f3697b5c629e235a52ed9d23e06caf
|
|
| BLAKE2b-256 |
42fbb8ceff60f035d9d189f3c2421a47d167e3f76f087f7227d77fc4956ad14c
|
Provenance
The following attestation bundles were made for muc_banbot-3.0.0-py3-none-any.whl:
Publisher:
release.yml on envs-net/muc_banbot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
muc_banbot-3.0.0-py3-none-any.whl -
Subject digest:
6c711b9b0a60cfdc85dc71fa82a6a32665478ff16d1cf649a818ea585a9c210a - Sigstore transparency entry: 2770086558
- Sigstore integration time:
-
Permalink:
envs-net/muc_banbot@bad81d8ec51ea1f1df708aa686ab1936327ed816 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/envs-net
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bad81d8ec51ea1f1df708aa686ab1936327ed816 -
Trigger Event:
push
-
Statement type: