News

v0.6.0

Live Config, Audit Closures & Clean-Cut Plugin API

v0.6.0 retires the leak-prone .load / .unload / .reload plugin lifecycle in favour of a three-scope live settings registry (core, plugin:<id>, chanset), folds .helpset into a unified help corpus, lands the spotify-radio plugin, and closes a 2026-05-10 stability audit (3 CRITICAL + 89 WARNING + 55 INFO) and security audit (1 CRITICAL + 28 WARNING).

Breaking

  • `.load` / `.unload` / `.reload` removedplugin enable/disable now flows through .set core plugins.<id>.enabled true|false; .restart is the canonical path for picking up code edits. The cache-busting import path that powered .reload is deleted — Node ESM has no eviction API, and every cache-busted re-import minted a permanent module-graph entry.
  • `database`, `pluginDir`, `owner.handle`, `owner.hostmask` removed from `bot.json`they are bootstrap env vars now: HEX_DB_PATH, HEX_PLUGIN_DIR, HEX_OWNER_HANDLE, HEX_OWNER_HOSTMASK. The strict-object schema rejects legacy files with a hint pointing at the env var to set.
  • `api.config` removed from PluginAPIevery shipped plugin migrated to api.settings — typed defs via api.settings.register([...]) and reads via getString / getInt / getFlag. Deeply-nested config falls back to api.settings.bootConfig.
  • `.helpset` removedfolded into .help set <scope> [<key>] against a unified HelpRegistry corpus indexed across core commands, plugin commands, and all three settings scopes.
  • `plugin-load` / `plugin-unload` / `plugin-reload` mod_log action strings retiredhistorical rows remain queryable; new rows use coreset-set / pluginset-set / chanset-set / rehash / restart.

Added

spotify-radio pluginannounces the operator's currently-playing Spotify track to a channel and rebroadcasts a Jam share link. Strict URL allowlist, in-memory refresh-token rotation, NickServ ACC verification on every mutating command. pnpm run spotify:auth for the one-time OAuth flow.
Three-scope settings registrygeneralises the per-channel chanset pattern to core (bot-wide live config), plugin:<id>, and chanset. KV-canonical-after-first-boot: bot.json / plugins.json are first-run seeds, operator .set / .unset / .rehash writes win.
`.set` / `.unset` / `.info` / `.rehash` / `.restart`unified live-config commands. Audit attribution flows through auditActor(ctx) so REPL / IRC / DCC / botlink-relay all converge on the same mod_log shape.
Reload-class metadataevery schema field annotated with @reload:live|reload|restart; .set echoes the class as a hint — (applied live) / (applied; subsystem reloaded) / (stored; takes effect after .restart).
`PluginAPI.coreSettings` + `PluginAPI.settings`read-only view of bot-wide settings and read/write own plugin scope. api.settings.bootConfig exposes a frozen merged JSON snapshot for config that doesn't flatten to typed settings.
Unified `HelpRegistry` corpusstrict prefix-preserving identifiers with a fuzzy fallback for bare !help ban lookups. .help index is permission-filtered for unprivileged DCC/IRC users; REPL and botlink remain unfiltered.
Bounded channel-join retrychannels failing JOIN with permanent-error numerics (+b/+i/+k/+r) walk a configurable backoff schedule (default 5/15/45 min) instead of being stuck until reconnect.
Per-plugin bind cap (warn 500 / hard 1000)closes a memleak finding around plugins that bind without ever unbinding; bindsByPlugin tally in the dispatcher.
Healthcheck splitseparate liveness (/tmp/.hexbot-alive) and readiness (/tmp/.hexbot-connected) sentinels distinguish process-wedged from IRC-unreachable.

Changed

  • `src/bot.ts` split into focused modules-26% LOC with no behavior change; core settings defs co-locate their onChange handlers, kv-maintenance and audit-fallback become small classes, connect() / start() / shutdown() reshaped into named phases.
  • Public `types/` `.d.ts` synced with runtime APIplugin authors relying on public declarations for IDE autocomplete were missing audit, util, settings, coreSettings, banStore and half the recent additions to PluginAPI / HandlerContext / ChannelState.
  • Plugin example configs minimisedevery key whose value the plugin's own schema already supplies is removed; only operator-required and security-relevant keys remain. ai-chat and rss flip to enabled: false so the example doesn't auto-post on first boot.

Fixed

  • 2026-05-10 stability audit3 CRITICAL + 89 WARNING + 55 INFO closed. Every mutating IRC verb now flows through the message queue (chanmod recovery storms can't trip Excess Flood); fatal SQLite errors hand control to bot.shutdown() instead of bypassing teardown via process.exit(2); ensureChannel stops growing unboundedly under stray TOPIC / RPL_CHANNELMODEIS.
  • STS upgrade plaintext-leak windowmessageQueue.clear() now fires BEFORE client.quit() on STS upgrade so the close-time flushWithDeadline(100) can't drain queued PRIVMSGs (potentially containing .adduser password material or plugin tokens) over plaintext between upgrade decision and TLS reconnect.
  • 2026-05-10 security audit1 CRITICAL + 28 WARNING closed. Deep-freeze identity.require_acc_for and bootConfig so a plugin can't disable NickServ verification bot-wide; api.say / notice / action route through splitMessage + target sanitize; STS rejects duration-only directives over plaintext; .ban / .unban thread auditActor so exactly one mod_log row attributes to the caller.
  • `importWithCacheBust` ESM-cache leak resolved by deletionload() uses a plain await import(pathToFileURL(absPath).href); importedOnce, reload(name), and the plugin:reloaded / plugin:reload_failed events are gone.
  • `kv` VACUUM spam from 32-bit `setInterval` overflowthe 30-day delay (2.59 × 10⁹ ms) exceeded Node's TIMEOUT_MAX (~24.8 days), which clamps to 1 ms and fires continuously on startup. VACUUM folds into the daily maintenance handler with an elapsed-time check.
  • First-boot plugin double-load raceseeding core.plugins.<id>.enabled into KV pre-load fired the onChange listener, which fire-and-forgets applyPluginEnabled() and raced the awaited main load loop. Seed moves to after the loop where applyPluginEnabled is idempotent.
  • Memleak audit closurecaps on pendingHandshakes (4096), SharedBanList distinct channels (1024), RelayOrchestrator virtual sessions (64/leaf), and flood channelActionRate (1024 keys, oldest-by-insertion eviction); timers stored in fields and cleared on teardown; DCC / IRCBridge.attach() / Logger.addSinkByOwner listener lifecycles made symmetric.

Removed

  • Closed audit documentsdocs/audits/stability-all-2026-05-10.md and friends deleted after every finding was resolved; commit history retains the reasoning.

See CHANGELOG.md for the full list of changes.


v0.5.0

AI Channel Regular, Ollama & BOTLINK v2

v0.5.0 reshapes ai-chat into a true channel regular — character engine, ambient participation, mood, on-demand games — adds an Ollama provider for self-hosted setups, hardens the BOTLINK handshake against replay, and closes 10 findings from a post-incident stability audit.

Added

Character engine9 personality presets (friendly, sarcastic, chaotic, deadpan, gossip, nightowl, oldhead…), per-channel assignment, runtime swap with !ai character <name>
Ambient participationbot speaks unprompted in quiet channels, answers unanswered questions, reacts to topic and join events; rate-limited per channel and globally
Thread-based engagementreplaces the old 60s timer with IRC-native floor-holding semantics; two users can be concurrently engaged in the same channel
Ollama providerself-hosted, private-by-default alternative to Gemini; flip provider between gemini and ollama with a plugin reload
On-demand game sessionsdrop a .txt into games/ and play it via !ai play <name>; ships with 20 Questions and Trivia
Founder-tier refusal gateai-chat refuses to respond when the bot's ChanServ tier is founder, with a per-line gated sender for mid-request tier changes
Bounded channel-join retrychannels stuck on +b/+i/+k/+r failures retry on a configurable backoff schedule (default 5/15/45 min) instead of waiting for reconnect
Nick collision detection + GHOST recoveryautomatic NickServ GHOST + reclaim when the configured nick is taken on connect
IDENTIFY-before-JOIN gatebot waits for confirmed identification before joining channels, eliminating the IDENTIFY/ChanServ probe race

Changed

  • BOTLINK v2 handshakeHMAC challenge-response replaces the replay-able scrypt wire token; every botnet must share a link_salt in bot.json
  • Hub-side BSAY re-checkfanout re-runs the originating user's +m permission so a compromised leaf can't bypass it
  • System-prompt assembly restructuredexplicit Persona / Right now / Rules sections with non-overridable safety clause; defense-in-depth dropper for fantasy-command prefixes

Breaking

  • `!ai` freeform removedtalk to the bot by nick (<botnick>: hello); !ai is now a subcommand console with !ai help
  • ai-chat private messaging removedthe plugin responds only in channels — PMs were a reconnaissance vector for prompt-injection probes
  • `triggers.engagement_seconds` removedreplaced by engagement.soft_timeout_minutes and engagement.hard_ceiling_minutes

Fixed

  • 2026-04-21 stability audit10 findings closed from the post-incident review of a 12+ hour outage where silent SASL failure left the bot unidentified and op-less
  • SASL silent failureNickServ "please identify" notice now triggers a one-shot password fallback and surfaces bot:identified on confirmation
  • Ambient tick-loop crash recoverywrapped in try/catch so a transient bug surfaces in the log instead of silently disabling ambient for the process lifetime
  • Engagement map leakeviction TTL with an unconditional 1000-entry cap so stale entries can't pin below the cap indefinitely

v0.4.1

Shell Injection & ReDoS Fixes

Two security fixes surfaced by audit follow-up: a command injection in the plugin build script and a polynomial ReDoS in the RSS HTML stripper.

Fixed

  • Shell command injection in plugin build scriptswitched from execSync with string interpolation to execFileSync with an argument array so paths with spaces or shell metacharacters cannot alter the command
  • Polynomial ReDoS in RSS HTML tag stripperreplaced the /<[^>]*>/g regex (O(n²) on pathological input) with a single-pass O(n) character scanner that buffers after < and flushes unclosed tags

v0.4.0

Plugin Bundling via tsup

Plugins are now compiled to self-contained bundles at build time. Polish around mode-grant safety, DCC error messages, and Docker image hygiene.

Changed

  • Plugins bundled via tsupplugins with a tsup.config.ts compile into self-contained dist/index.js bundles instead of being loaded as raw TypeScript via tsx; the loader resolves plugins/<name>/dist/index.js for bundled plugins
  • `.binds` output grouped by pluginsection headers for easier scanning
  • Topic plugin `protect_topic` renamed to `topic_lock`consistency with Eggdrop terminology
  • DCC CHAT rejection notices collapsedsingle generic "request denied" message — no longer leaks the specific denial reason to the connecting user

Fixed

  • Mode-grant commands targeting the bot itselfpreviously the bot could attempt to op/deop/voice itself, causing confusing no-ops and mode bounces
  • Docker build failure with plugin local node_modulesadded plugins/*/node_modules and plugins/*/dist to .dockerignore
  • ESLint errors on plugin `dist/` bundlesignore pattern updated to **/dist/ so plugin build output is excluded

Removed

  • `dcc.nickserv_verify` config fielddeprecated in 0.3.0; DCC now uses per-user passwords exclusively

v0.3.0

Audit Logging, AI Chat, RSS & Security Hardening

A foundation release: full mod_log audit pipeline, the ai-chat and rss plugins land, DCC gains password authentication, IRCv3 STS / account-tag / away-notify support, and four parallel audits (security, stability, memleak, quality) close ~50+ findings.

Added

  • `mod_log` audit pipelineschema rewrite with source/plugin/outcome/metadata; api.audit.log() for plugins; .modlog operator UI with filter grammar and DCC-only paging; .audit-tail REPL stream; retention knob
  • `ai-chat` pluginAI-powered chat via Gemini with provider adapter pattern, layered rate limiting, per-user token budgets, sliding-window context, multiple personality presets, on-demand game sessions, and ChanServ fantasy-command injection defense
  • RSS pluginpolls feeds and announces new items, SHA-1 dedup via KV store, first-run silent seeding, admin commands !rss list/add/remove/check, SSRF defense in depth (HTTPS-only, RFC1918 blocking, byte cap, DOCTYPE rejection)
  • DCC console log sinkper-session .console flags filter live log lines (m/o/j/k/p/b/s/d/w); .who is now the session-list command
  • ISUPPORT parsertyped ServerCapabilities snapshot covering PREFIX, CHANMODES, MODES, CHANTYPES, TARGMAX, CASEMAPPING; mode-batching and channel validation now follow what the connected IRCd advertises
  • IRCv3 caps expandedaway-notify (channel-aware), account-tag consumption, $a:account permission patterns, and Strict Transport Security (sts=) with auto-upgrade
  • Configurable command prefixcommand_prefix field in bot.json (default .)
  • Per-target message queueround-robin drain so a flooding target can't starve output to quieter channels
  • BotLink per-IP brute-force protectionescalating bans (5min → 24h cap), CIDR whitelist, per-IP pending-handshake limit, configurable handshake timeout
  • ChanServ-assisted join error recoveryasks ChanServ for help on 471/473/474/475/477 numerics with exponential backoff

Breaking

  • DCC CHAT requires per-user passwordsscrypt-hashed; existing users have no password_hash and are blocked from DCC until an admin runs .chpass <handle> <newpass>. Closes a Rizon-style vhost-persistence bypass.
  • Inline secrets removed from `bot.json`services.password, botlink.password, chanmod.nick_recovery_password, proxy.password, and +k keys must be referenced via <field>_env keys backed by .env
  • `chanmod` `channel_modes` legacy format removedvalues must start with + or - (e.g. "+nt"); unprefixed strings are rejected at parse time
  • `MessageQueue.enqueue(fn)` → `enqueue(target, fn)`core call sites updated; plugin code using api.say/api.notice/etc. is unaffected

Changed

  • Reconnect loop rewrittenHexBot owns the loop end-to-end; classifies disconnects into transient / rate-limited / fatal tiers with appropriate backoff. K/G-line and DNSBL blocks no longer cause exits — they expire on their own.
  • NickServ ACC/STATUS replies suppressed from DCC mirrorinternal verification chatter no longer narrates every !voice command twice in operator consoles

Fixed

  • 2026-04-14 security auditevery Phase 1 critical, Phase 2 warning, and Phase 3 info finding closed across the full codebase
  • 2026-04-14 stability audit10 subsystems hardened against months-of-uptime failure modes — DB error classification, plugin lifecycle fail-loud, services dedup, BotLink jitter, message-queue deadline, DCC eviction, plugin teardown
  • 2026-04-14 memleak auditevery scheduled finding closed across flood, chanmod, DCC, BotLink, RSS, services, memo, and connection-lifecycle; createPluginApi now returns a dispose that neutralises every method post-unload
  • 2026-04-14 quality auditgod-file splits across src/core/dcc/, src/core/botlink/, RSS, flood, plus cross-cutting dedup of permission and pending-request helpers
  • Stalled-reconnect zombie loop30s registration timeout fires on socket-connected so a TCP-but-no-IRC-greeting hang is classified transient and retried with backoff

v0.2.3

Startup Retry & Docker over WireGuard

Initial connection failures now back off and retry, ChanServ presence detection is automatic, and a Docker-over-WireGuard hang is fixed.

Added

  • Startup retry with exponential backofffirst-connection failures no longer exit the process
  • ChanServ auto-detectchanserv_op merged into chanserv_access

Changed

  • Refactored botlink, mode-enforce, and bot.tsreadability cleanup with no behaviour change
  • Detailed disconnect reason loggingconnection error handling enhanced

Fixed

  • IRC connection failure in Docker over WireGuarddisabled Node's Happy Eyeballs algorithm

v0.2.2

Single-Stage Dockerfile

Simpler Docker build using tsx at runtime; pnpm start is now the single entry point.

Changed

  • Dockerfile simplified to single-stage builduses tsx at runtime instead of compiling to JS
  • `tsx` moved from devDependencies to dependenciesrequired for runtime execution
  • `start:prod` script removedpnpm start is the single entry point

v0.2.1

Documentation Sync

Getting Started guide lands; README and core docs synced to current behaviour.

Added

  • Getting Started guidedocs/GETTING_STARTED.md

Changed

  • README overhauledhighlights section, full admin/bot-link/DCC command tables, documentation index
  • Comprehensive doc syncDESIGN.md, PLUGIN_API.md, DCC.md, plugins/README.md updated to match the current codebase
  • Healthcheck heartbeat uses `utimesSync`instead of writing unused file content

Fixed

  • Docker build failure on `husky` prepareran during --prod install and failed because husky is a devDependency

v0.2.0

Bot Linking & Channel Takeover Protection

Multi-bot networking inspired by Eggdrop botnet, persistent channel rejoin, ChanServ-backed takeover protection, and a round of security fixes.

Added

  • Bot linking protocolhub-and-leaf networking with state sync, command relay, party line chat, session relay, protection frames, and ban sharing — JSON-framed protocol over TCP with SHA-256 auth and rate limiting; admin commands .botlink status|disconnect|reconnect, .bots, .bottree, .relay, .whom
  • Persistent channel rejoinperiodic check every 30s; handles kick+ban, channel full, invite-only, bad key
  • ChanServ-based takeover protectiondetects unauthorized mass deop/mode changes and escalates: deop, kickban, akick
  • Enforce unauthorized `+k`/`+l` removalreactive (real-time) and proactive (on join via RPL_CHANNELMODEIS)
  • Channel mode trackingmode string, key, and limit tracked from MODE and channel info reply; new channel:modesReady event
  • Multi-stage Dockerfile + healthchecksmaller production images; healthcheck for orchestration tools

Changed

  • `channel_modes` Eggdrop-style format"+nt-s" means "ensure +n and +t, ensure -s, leave everything else alone"; modes not mentioned are no longer treated as unauthorized
  • `enforce_modes` gates both directionswhen off, neither additions nor removals run

Fixed

  • ChanServ OP on RizonOP request no longer gated on ChanServ being in the channel
  • DCC TOCTOU raceduplicate DCC CHAT requests now rejected when one is already pending
  • `!seen` cross-channel info disclosurequeries from a different channel omit channel name and message text
  • Bot-link security audit1 critical + 5 warning findings closed (permission bypass, frame validation, rate limiting)
  • Codebase security sweep8 additional warnings closed from full-codebase audit

v0.1.0

Initial Release

HexBot v0.1.0 is the first public release. The core bot framework is production-ready with a full plugin API, permission system, and Docker deployment.

Included

8 bundled pluginschanmod, flood, greeter, seen, topic, help, ctcp, 8ball
Bind system16 event types, pattern-matched handlers
Flag-based permissionsOwner, master, op, voice, deop; per-channel and global
Hot-reloadEdit and reload plugins without restarting
SASL authenticationPLAIN and EXTERNAL (CertFP)
IRCv3 capsextended-join, account-notify, chghost
SOCKS5 proxyTor and SSH tunnel support
DCC CHAT party lineRemote admin sessions
SQLite persistenceNamespaced per-plugin key/value store
Docker deploymentCompose file with host-mounted config and plugins

See the deploy guide to get started, or browse the plugin list to see what’s included.


Roadmap

upcomingRoadmap
  • XDCCFile serving over DCC
  • IdleRPGIdle-based RPG plugin