A high-performance Discord security bot designed to catch and remove self-bots and malicious users who ignore server warnings. The bot monitors specific "Trap" channels and instantly bans anyone who sends a message there.
- Multi-Server Support โ One bot instance serves any number of servers. Each server has its own trap channels, log channel, ban reason and purge duration, managed with slash commands (no restart needed).
- Instant Detection โ Real-time monitoring of multiple restricted channels.
- Auto-Ban โ Immediately bans violators with zero tolerance.
- Message Purge โ Deletes the banned user's recent messages across the server (24h by default, configurable per server).
- DM Notification โ Sends a notification to the user before banning (best-effort, 3 s timeout, fails silently if DMs are disabled).
- Safety Net โ Administrators and configurable exempt roles are never banned (their trap message is only deleted). A per-server dry-run mode reports violations without banning, and failed bans (missing permission, role hierarchy) are reported to the log channel.
- Privacy โ Captured message content is cleared from the ban history after
MESSAGE_RETENTION_DAYS(default 90). - MySQL Database โ Persistent ban history storage with search and statistics.
- Detailed Ban Logs โ Rich embed logs with:
- User avatar, tag, ID, and mention
- Account creation date
- Server join date
- Display name and roles
- Captured message content
- Suspicious account age warning (< 7 days old)
- Slash Commands:
/helpโ List all commands (built from the loaded commands, always up to date)/setup-trapโ Register the current channel as a trap for this server and post the warning/trap-settingsโ View or change this server's trap channels, log channel, ban reason, purge duration, dry-run mode and exempt roles/add-logchannel [channel]โ Choose where ban logs are posted on this server (any channel, any name)/remove-logchannelโ Stop posting ban logs/unbanโ Unban a user by ID (ban history is kept)/purge-userโ Bot owner only: delete all stored ban records of a user (data deletion requests)/statusโ View bot uptime, statistics, and recent bans/ban-infoโ Look up ban information for a user by ID (Discord ban lookup)/bansโ Ban history from database:/bans recent [limit]โ Show recent bans/bans search <user_id>โ Search bans by user ID/bans statsโ Show database statistics- Results are limited to the current server (the bot owner can add
global:true)
- Unban from the log โ Every ban log has an Unban button for administrators.
- Change log โ Every settings change and unban (from a command, the log button or the dashboard) is recorded with who made it, in the
audit_logtable. - Owner alerts โ When the bot cannot ban on a server (missing permission, role too low), the server owner gets at most one direct message a day about it.
- Optional web dashboard โ The hosted bot at voidsentry.vercel.app has a dashboard for settings, ban history, statistics, unbans and CSV export. Its website is not open source; the API it uses is in this repository and switched off unless you configure it. Self-hosted bots are fully usable with slash commands alone (see Running without the dashboard).
- Live Status โ The bot's "Watching" status rotates between your
STATUS_TEXT, the number of servers and the total number of users banned. - Structured Logging โ Pino-based logger with categories (system, security, ban, detection, command)
- Statistics Tracking โ In-memory tracking of bans, detections, and DM success rate
- Branded Design โ Custom logo and professional embeds
VoidSentry/
โโโ assets/ # Branding assets
โ โโโ logo.png
โ โโโ VoidSentryBanner.gif
โ โโโ VoidSentryLogo.gif
โโโ src/
โ โโโ config/
โ โ โโโ index.js # Environment config, MySQL settings
โ โโโ commands/
โ โ โโโ help.js # /help
โ โ โโโ setupTrap.js # /setup-trap
โ โ โโโ trapSettings.js # /trap-settings (per-server settings)
โ โ โโโ addLogChannel.js # /add-logchannel
โ โ โโโ removeLogChannel.js # /remove-logchannel
โ โ โโโ unban.js # /unban
โ โ โโโ purgeUser.js # /purge-user (bot owner only)
โ โ โโโ status.js # /status
โ โ โโโ banInfo.js # /ban-info (Discord ban lookup)
โ โ โโโ bans.js # /bans (database history)
โ โโโ embeds/
โ โ โโโ warningEmbed.js # Trap channel warning
โ โ โโโ banLogEmbed.js # Detailed ban log
โ โ โโโ banNotificationEmbed.js # DM notification
โ โโโ events/
โ โ โโโ ready.js # Bot startup & database init
โ โ โโโ interactionCreate.js # Slash command router
โ โ โโโ guildDelete.js # Cleans up when the bot leaves a server
โ โ โโโ channelDelete.js # Unregisters deleted trap channels
โ โ โโโ messageCreate.js # Core trap detection & ban logic
โ โโโ utils/
โ โโโ db.js # MySQL queries
โ โโโ migrations.js # Versioned schema migrations
โ โโโ deploy.js # Slash command deployment
โ โโโ guildSettings.js # Per-server settings & trap channels (cached)
โ โโโ audit.js # Change log entries for commands and buttons
โ โโโ unbanButton.js # Unban button on ban logs
โ โโโ ownerAlerts.js # Daily DM to a server owner when bans fail
โ โโโ messageEvidence.js # Summary of the message that triggered a ban
โ โโโ helpers.js # Utility functions
โ โโโ logger.js # Pino structured logger
โ โโโ presence.js # Rotating bot status
โ โโโ statsServer.js # Optional stats endpoint (+ dashboard API)
โ โโโ dashboardApi.js # Optional API for the hosted dashboard
โ โโโ dashboardSettings.js # Settings changes made through the dashboard
โ โโโ banStats.js # Ban statistics (per day, hour, weekday, channel) for the dashboard
โ โโโ rateLimit.js # Dashboard API rate limits
โ โโโ sessionRevocations.js # "Sign out everywhere" for the dashboard
โ โโโ permissions.js # Permission checker
โ โโโ stats.js # In-memory statistics
โ โโโ constants.js # Colors, paths, branding
โโโ scripts/
โ โโโ backup.js # npm run backup
โ โโโ backupCrypto.js # Backup encryption
โ โโโ decrypt-backup.js # npm run backup:decrypt
โโโ test/ # node --test (npm test)
โโโ .env # Your secrets (not committed)
โโโ .env.example # Example config template
โโโ Dockerfile
โโโ index.js # Entry point
โโโ package.json
โโโ SECURITY.md # How to report vulnerabilities
โโโ README.md
- Requirement: Node.js v20 or higher (CI and the Docker image use Node 20).
- Database: Set up a MySQL database (see configuration below).
- Download: Clone or download this repository.
- Install Dependencies:
npm install
Copy .env.example to .env and fill in your credentials:
| Key | Description |
|---|---|
DISCORD_TOKEN |
Your bot's secret token from the Developer Portal. |
CLIENT_ID |
Your bot's Application ID. |
OWNER_ID |
Your Discord User ID (for admin commands). |
GUILD_ID |
Discord Server ID for development (guild commands). |
BAN_CHANNEL_IDS |
Optional. Trap channel IDs, separated by commas, that apply on every server. Normally leave empty and use /setup-trap instead. |
LOG_CHANNEL_ID |
Optional default log channel ID. Only works on the server that owns the channel; other servers set theirs with /trap-settings log-channel. |
STATUS_TEXT |
First line of the rotating bot status (default: Watching for breaches...). Server and ban counts are added automatically. |
DELETE_MESSAGE_SECONDS |
Default messages to delete on ban (default: 86400 = 24h). Servers can override it. |
BAN_REASON |
Default ban reason. Servers can override it. |
STATS_PORT |
Optional. Port of a read-only JSON endpoint (GET /stats) with aggregate numbers (servers, members, total bans, bans in the last 7 days) for the website. 0 or empty = disabled. |
STATS_HOST |
Interface for the stats endpoint (default 127.0.0.1). Keep it on localhost and expose it through a tunnel or reverse proxy. |
STATS_TOKEN |
Optional bearer token the stats endpoint requires. Use a random value of at least 32 characters (for example openssl rand -hex 32); the bot logs a warning at start-up if it is shorter. |
STATS_TOKEN_PREVIOUS |
Only during a token rotation: the old token, still accepted (see Rotating STATS_TOKEN). |
DASHBOARD_API |
true (default) serves the dashboard API under /api/ when STATS_TOKEN is set. Set false to serve only /stats (see Running without the dashboard). |
BACKUP_DIR, BACKUP_KEEP, BACKUP_KEY, BACKUP_COPY_DIR |
Used by npm run backup only (see Backups and migrations). |
MESSAGE_RETENTION_DAYS |
Days to keep captured message content in the ban history (default: 90, 0 = keep forever). |
NODE_ENV |
development for guild commands, production for global. |
| Key | Description | Default |
|---|---|---|
DB_HOST |
MySQL server hostname | required |
DB_PORT |
MySQL server port | 3306 |
DB_USER |
MySQL username | required |
DB_PASSWORD |
MySQL password | required |
DB_NAME |
Existing database name | required |
Note: The tables are created and updated automatically by migrations at startup. The database itself must exist and the configured user only needs access to that database.
-
Create Database (once, using an administrator account):
CREATE DATABASE voidsentry;
-
Invite the Bot: Ensure the bot has
Ban Members,Manage Messages, andView Channelspermissions. -
Role Hierarchy: Move the bot's role to the very top of your Role list.
-
Deploy Commands:
# For development (faster, guild-specific) npm run deploy:guild # For production (global commands, ~1hr propagation) npm run deploy
-
Launch:
npm start
-
Initialize: On every server, go to each trap channel and use
/setup-trap. Choose the log channel with/add-logchannel(use any channel you like, e.g.#mod-logs).
| Command | Description | Permissions |
|---|---|---|
/help |
List all commands | Everyone |
/setup-trap |
Registers the channel as a trap, clears it and posts the warning | Owner / Administrator |
/trap-settings view |
Show this server's trap channels and settings | Owner / Administrator |
/trap-settings remove-trap [channel] |
Stop treating a channel as a trap | Owner / Administrator |
/add-logchannel [channel] |
Set the ban log channel (checks bot permissions, posts a confirmation) | Owner / Administrator |
/remove-logchannel |
Stop posting ban logs | Owner / Administrator |
/trap-settings log-channel [channel] |
Same as the two above, without the permission check | Owner / Administrator |
/trap-settings reason [text] |
Set (or reset) the ban reason | Owner / Administrator |
/trap-settings purge [seconds] |
Set (or reset) the message purge duration | Owner / Administrator |
/trap-settings dry-run <enabled> |
Report violations without banning | Owner / Administrator |
/trap-settings exempt-add <role> |
Never ban members with this role | Owner / Administrator |
/trap-settings exempt-remove <role> |
Remove a role exemption | Owner / Administrator |
/unban <user_id> |
Unban a user on this server | Owner / Administrator |
/purge-user <user_id> |
Delete all stored ban records of a user on every server | Bot owner only |
/status |
Bot uptime, stats, ping, recent bans | Owner / Administrator |
/ban-info |
Look up Discord ban by user ID | Owner / Administrator |
/bans recent [limit] |
Recent bans from database | Owner / Administrator |
/bans search <user_id> |
Search database by user ID | Owner / Administrator |
/bans stats |
Database statistics | Owner / Administrator |
When a user is banned, the log embed includes:
| Field | Description |
|---|---|
| ๐ค User | Tag, ID, and mention |
| ๐ Account Created | When the Discord account was created |
| ๐ฅ Joined Server | When the user joined your server |
| ๐ท๏ธ Display Name | Server nickname or username |
| ๐ญ Roles | All roles the user had (up to 15) |
| Why the ban occurred | |
| ๐ Detected In | Which trap channel triggered the ban |
| ๐ Action Taken At | Exact timestamp of the ban |
| ๐ฌ Message Content | What the user wrote (truncated if long) |
| ๐ฉ Suspicious Flag | Warns if the account is less than 7 days old |
- ๐ Detection โ Message detected in trap channel
- ๐ก๏ธ Exemption โ Administrators and exempt roles are skipped (message deleted only)
- โ Validation โ Bot permissions and role hierarchy checked (failures reported to the log channel)
- ๐งช Dry run โ If enabled, the violation is reported and nothing else happens
- ๐จ DM Notification โ User notified via DM before the ban (best-effort)
- ๐จ Ban Execution โ User banned, recent messages purged
- ๐๏ธ Cleanup โ Trigger message deleted if it survived the purge
- ๐พ Database โ Ban record stored in MySQL
- ๐ Stats โ Ban recorded in statistics tracker
- ๐ Logging โ Detailed embed sent to log channel
- Message Content Intent: Must be enabled in Discord Developer Portal โ Bot โ Privileged Intents.
- Database: MySQL is required. The database must exist; its tables are created on first run.
- No Appeals: The bot uses a "zero-tolerance" policy. Anyone typing in the trap channel is banned immediately.
- Message Purge: The banned user's recent messages are deleted (24 hours by default).
- Docker: A
Dockerfileis included. The bot exits on uncaught exceptions, so run it with a restart policy (docker run --restart unless-stopped --env-file .env voidsentry). - DM Notification: Best-effort, fails silently if user has DMs disabled.
The tables are created automatically. Ban history (bans):
CREATE TABLE bans (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(20) NOT NULL,
user_tag VARCHAR(100) NOT NULL,
guild_id VARCHAR(20) NOT NULL,
guild_name VARCHAR(100) NOT NULL,
channel_id VARCHAR(20) NOT NULL,
channel_name VARCHAR(100) NOT NULL,
message_content TEXT,
reason VARCHAR(500) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_guild_id (guild_id),
INDEX idx_created_at (created_at)
);Per-server settings, trap channels and exempt roles:
CREATE TABLE guild_settings (
guild_id VARCHAR(20) NOT NULL PRIMARY KEY,
log_channel_id VARCHAR(20) NULL,
ban_reason VARCHAR(500) NULL,
delete_message_seconds INT NULL,
dry_run TINYINT(1) NULL
);
CREATE TABLE trap_channels (
channel_id VARCHAR(20) NOT NULL PRIMARY KEY,
guild_id VARCHAR(20) NOT NULL,
INDEX idx_trap_guild_id (guild_id)
);
CREATE TABLE exempt_roles (
guild_id VARCHAR(20) NOT NULL,
role_id VARCHAR(20) NOT NULL,
PRIMARY KEY (guild_id, role_id)
);Also created by migrations: audit_log (who changed which setting or lifted which ban, and when), session_revocations (dashboard "sign out everywhere") and schema_migrations (which migrations have run). src/utils/migrations.js has the exact definitions.
The web dashboard belongs to the hosted bot and its website is not open source. A self-hosted bot does not need it: every setting and action is available as a slash command, ban logs carry an Unban button, and the change log is still written to the database.
The dashboard API is never served unless you set both STATS_PORT and STATS_TOKEN. To run the bot on its own:
- Leave
STATS_PORTempty (or0). No HTTP port is opened at all. - Or, to publish only the aggregate
/statsnumbers (for your own status page or monitoring), setSTATS_PORTand optionallySTATS_TOKEN, and setDASHBOARD_API=false. Then/api/*answers404even with a valid token.
The dashboard tables (audit_log, session_revocations) are still created; they stay small and unused apart from the change log.
The schema is managed by versioned migrations (src/utils/migrations.js) that run automatically at startup and are recorded in the schema_migrations table. Never edit a shipped migration; add a new one.
npm run backup writes a gzip-compressed mysqldump to BACKUP_DIR (default ./backups) and keeps the newest BACKUP_KEEP files (default 14). It needs the mysqldump program and uses the DB_* settings. Run it daily, for example with cron:
15 3 * * * cd /path/to/VoidSentry && npm run backup
To restore: gunzip -c backups/voidsentry-YYYYMMDD-HHMMSS.sql.gz | mysql -h HOST -u USER -p DATABASE.
Encryption and an off-machine copy. Set BACKUP_KEY to a long random passphrase and backups are written as .sql.gz.enc (AES-256-GCM, key derived with scrypt). Without it they stay unencrypted and the script prints a warning. Keep the key in a password manager, not next to the backups: without it an encrypted backup cannot be restored. Set BACKUP_COPY_DIR to a folder that is synced to another machine or cloud storage to keep a second copy; it is pruned to BACKUP_KEEP files as well. The copy is only made when BACKUP_KEY is set, so an unencrypted dump never leaves the machine; otherwise the script skips it and exits with code 1.
To restore an encrypted backup, decrypt it first, then restore as above:
npm run backup:decrypt -- backups/voidsentry-YYYYMMDD-HHMMSS.sql.gz.enc
gunzip -c backups/voidsentry-YYYYMMDD-HHMMSS.sql.gz | mysql -h HOST -u USER -p DATABASE
The decrypt step refuses a wrong key or a changed file and leaves no partial output. Do one test restore after turning encryption on.
When STATS_PORT and STATS_TOKEN are set (and DASHBOARD_API is not false), the stats server also serves the API behind the website dashboard under /api/ (see src/utils/dashboardApi.js for the routes, including settings, unban, CSV export and statistics, and the owner-only routes for server trends, leaving a server and deleting a user's data). It always requires the bearer token and acts for the Discord user named in X-Discord-User; the bot checks that user rights in each server itself. Anyone who holds STATS_TOKEN can act as any user, so keep it secret and expose the port only through a tunnel or reverse proxy.
- Sign out everywhere. The website sends the issue time of the session in
X-Session-Issued.POST /api/me/revokestores the current time for that user (tablesession_revocations), and every older session of that user then gets403, owner routes included. - Rate limits. Per user 120 requests and 20 searches a minute, and 1,200 requests a minute in total; over the limit the API answers
429. All requests arrive from the website's servers, so limits are per Discord user, not per IP. Sign-in on the website never reaches the bot; limit it with the Vercel Firewall if needed.
Rotate it at once if it may have leaked (a commit, a screenshot, a shared log), and otherwise now and then. The bot accepts two tokens during the switch, so the dashboard keeps working:
- Generate a new token, for example
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))". - In the bot's
.env: move the current value toSTATS_TOKEN_PREVIOUSand put the new one inSTATS_TOKEN. Restart the bot. - In Vercel, set
BOT_STATS_TOKENto the new token and redeploy the website. - Check that the dashboard and
/statuswork, then emptySTATS_TOKEN_PREVIOUSand restart the bot again. The old token is now refused.
If the token leaked, also use Sign out everywhere on the dashboard and check the audit log of each server for changes you did not make.
A separate owner-only token would add little: the website needs both to work, so both would sit in the same Vercel environment and leak together. The owner routes are instead protected by the same token, the owner's Discord sign-in and session revocation.
See SECURITY.md for how to report a vulnerability.
Created for secure Discord community management.
