ANetBBS Changelog
Current release: v1.0.87 (September 2026). This file covers v1.0.0
onward, which follows standard semantic versioning — patch releases are
v1.0.1, v1.0.2, and so on. The full internal beta build-number
history (v1.0a1.1 through v1.0b2.239) that got the project to this
release is preserved in
CHANGELOG-beta.md.
v1.0.57 — Node monitor visibility fix, and BBSDEV.DRP dropfile support (August 2026)
Fixes a real bug in anetbbs-monitor, the live CLI node monitor: an
already-connected user could fail to show up at all, and a freshly
logged-in user could show correctly at first and then silently vanish
a few minutes later, even while still fully connected. Root cause: the
underlying presence data (NodeActivity.last_seen) was only ever
refreshed by active menu navigation, door play, chat, or an AFK-state
change — a session that just sat idle on one screen never touched any
of those, so it aged out of the 5-minute online window every presence
surface uses (the web NodeSpy panel and the in-BBS Node Monitor share
the same underlying data, and the same fix). The cross-process watchdog
that already polls every session every 5 seconds now also keeps its
presence timestamp fresh on each poll, closing the gap at the source.
anetbbs-monitor itself also got a small polish pass: an AFK session
now shows in a distinct color, a session that still hasn't checked in
after the fix above gets flagged rather than silently disappearing, and
a database hiccup on a refresh tick shows a warning instead of taking
down the whole screen.
Adds support for BBSDEV.DRP, a newer drop-file format for launching
door games (an alternative to DOOR32.SYS with a richer, UTF-8-aware
field set). A door game can now be configured with drop file type
bbsdev.drp in the Game Center admin; a standalone converter,
tools/door32_to_bbsdev_drp.py, is also included for turning an
existing DOOR32.SYS from another BBS package into a compatible
BBSDEV.DRP file.
v1.0.56 — Fixed a real FidoNet hub queue that never drained (August 2026)
Reported live: a real FidoNet hub (1:123/3003@fidonet) had 1359
files / 66MB stuck in its queue for 9 days, resending the entire
backlog on every single poll even though ANetBBS logged every session
as a clean success. Watching a live session showed why: ANetBBS's own
code was disconnecting partway through a large receive, after going
through "quite many of the 1000+ files."
Root cause: BinkPClient._receive_messages()'s receive loop was
hard-capped at 5000 frames total. A 66MB transfer needs roughly 16,000
individual 4096-byte data frames alone, before even counting the 1359
per-file headers — more than 3x the cap. Once a real transfer grew
past that count, the loop gave up and moved on, leaving everything
still in-flight unacknowledged — which is exactly why the hub's queue
never shrank despite the session otherwise completing normally. This
is the same class of bug already fixed once elsewhere in this file (an
earlier range(20) cap on a different loop, replaced with a time-based
wait) — fixed the same way here: the fixed count is gone entirely, and
the loop is now bounded only by its existing 5-second per-frame idle
timeout, so a real stall still ends the session cleanly but a large,
healthy transfer is no longer cut off arbitrarily.
v1.0.55 — Manual social posts, and a CI test-suite OOM fix (August 2026)
New: the auto-social-posting queue now has a manual compose option
(+ New Post on /admin/social/) for anything the automatic
triggers don't catch — a version bump, a new feature, an event. Text
plus an optional image, queued for review exactly like an automatic
draft; nothing posts until approved. Queuing any draft — automatic or
manual — now also notifies every admin (bell-badge + live toast for an
open tab), answering a real gap: there was previously no way to know a
post was waiting without checking the page yourself.
Fixed: the GitHub Actions Docker-build workflow's own test run
was failing from the same class of memory issue fixed in v1.0.54 —
running the full ~2,986-test suite as one continuous process
accumulates memory gradually (no single runaway test, roughly 5MB/file
compounding to ~2.85GB by the end) and was getting OOM-killed on the
CI runner. Now runs in 15 batches, each its own process, resetting
that accumulation between groups — confirmed locally to complete the
full suite at under 600MB peak instead.
v1.0.54 — Fixed a real OOM: unbounded log reads and a logging-handler leak (August 2026)
Fixes the root cause of a severe memory problem found live: repeated
freezes and kernel OOM-kills of an 11-12GB python process during
test runs, traced to two bugs working together.
anetbbs/web/admin.py's Settings page (and its sysop-console tail
command) read a log file's last N lines with f.readlines()[-N:] —
loading the entire file into memory before slicing. Fine on a fresh
install, but a log with no rotation grows unbounded, and this one had
reached 6.1GB / 80 million lines. Both call sites now share a helper
that seeks near the end of the file and reads a small, fixed-size
window instead, regardless of file size.
Separately — and the real reason the log got that large in the first
place — _configure_logging() added a new log handler on every app
instance created without ever removing the previous one, and Flask's
per-app logger is never garbage-collected by name, so handlers piled
up within one long-running process and every log line was written
once per accumulated handler. Fixed by clearing prior handlers before
adding new ones, and by capping the log file itself with real
rotation (20MB × 5 backups) so it can't grow unbounded again either
way.
Verified with a full ~3,000-test suite run, memory-capped and
monitored throughout: peak memory across the entire run is now ~570MB,
down from the ~12GB that was crashing the machine. New regression
tests cover both fixes.
v1.0.53 — Node monitor fixes: live refresh, a real Peer bug, and Game Center status (August 2026)
Three real bugs found testing v1.0.52's new anetbbs-monitor live:
- The screen never actually refreshed.
anetbbs-monitoropened one
database session for its whole run instead of a fresh one per
refresh tick (every other poller in this codebase already does the
latter, for exactly this reason) — SQLAlchemy kept serving the
first query's cached rows, so "Doing"/"Idle" looked permanently
frozen the instant the tool started, even though the underlying
data was updating correctly the whole time. Fixed, and confirmed
with a 5000-iteration stress run that peak memory stays flat — the
fix reuses one app instance the same way the rest of this codebase
already does, not the create-a-new-app-per-call pattern that caused
the real v1.0.21 memory leak. - The Peer column showed a timestamp instead of an address, e.g.
2026-08-28 10:44— a pre-existing bug incore/session.py: the
real IP:port wasn't computed until after the node-slot was already
claimed with a session timestamp standing in for it, so that
timestamp is what ended up inNodeActivity.peer(and therefore in
the web NodeSpy panel and the in-BBS Node Monitor too, not just this
new tool). Fixed by computing the real address first and reusing it
everywhere it's needed. - "Doing" never updated for anyone just browsing the Game Center
(only actually launching a door updated it) — Game Center runs its
own menu loops entirely separate from the generic menu system that
every other heartbeat hooks into. Now updates on entering Game
Center, the door list, a category submenu, and the built-in Number
Guessing game.
Also: the monitor's columns are wider (protocol now fits
petscii40/petscii80, not just telnet/ssh; the activity column
fits a full label like "Away From Keyboard (screensaver)" without
truncating), and "Doing" is relabeled "Action" for clarity. All four
fixes have new regression tests.
v1.0.52 — Live node monitor, and a real fix to the presence-alert gap (August 2026)
New: anetbbs-monitor, a live auto-refreshing terminal node
monitor (Synchronet uMonitor / Mystic nodespy style) — who's
connected, on what protocol, from where, doing what, with a kick
action, no browser needed. It's a new front end onto the same
NodeActivity data the web admin's NodeSpy panel and the in-BBS Sysop
Tools Node Monitor already use, so a kick from any of the three looks
identical to the other two. See docs/32-node-monitor.md.
Fixed: a real gap in the live "X just logged in/out" presence
alerts (v1.0.48): an existing user's login/logout correctly notified
every other online user, but a brand-new account completing
registration and landing straight in a session never did — the
registration route logs the new user in directly and never had the
alert-recording step the separate login route has. Fixed by adding the
same step there. Covered by a new regression test.
v1.0.51 — CI lint fix (August 2026)
Removes an unused import flagged by pyflakes in the CI code-scan job. No
functional change.
v1.0.50 — Postcard/ANSI Editor save fix (August 2026)
Fixes a real bug from v1.0.49: the grid-editor widget shared by
/postcards and the admin ANSI Editor POSTed saves with no CSRF token,
so every save failed with 400 Bad Request under real CSRF protection
(the test suite's own config disables CSRF, so this was never caught).
Fixed by attaching the page's existing CSRF token to the save request —
also fixes the same, previously-unnoticed bug in the admin ANSI Editor.
Also fixes an incorrect GitHub link in the /watch and Postcards page
footers, and a test asserting an exact tools-menu count that the new
Postcards entry had pushed past.
v1.0.49 — Watch It Live, Postcards, guest play, auto-social-posting (August 2026)
Four features aimed at giving ANetBBS something new to show off and
lowering the friction between a social-media link and someone actually
trying the BBS — see docs/29-watch-live.md, docs/30-postcards.md,
docs/31-social-posting.md, and the new section in
docs/24-game-center.md for full details.
- Watch It Live (
/watch, off by default): a public, no-login page
showing real-time who's-online activity, styled as a retro terminal
display, meant to be shared or embedded off-site. - Postcards (
/postcards): any logged-in user can compose retro
CP437 art and get a public share link plus a downloadable PNG, built
on the same grid editor as the admin ANSI Editor but kept in its own
table so user postcards never clutter that admin tool. - Guest play: 13 stateless web arcade games (Snake, Tetris, 2048,
Minesweeper, and more) can now be played with no account — a social
click leads straight to real gameplay instead of a login wall. Never
enabled on the wallet/casino games or anything with a persistent save. - Auto-social-posting queue (
/admin/social/, off by default): a
new #1 high score or a round-number BBS milestone queues a draft
Bluesky/Mastodon post — with a rendered image and editable caption —
for a sysop to review and approve. Nothing posts automatically.
v1.0.48 — Live "X just logged in/out" presence alerts (August 2026)
Classic multi-node BBS behavior: every other currently-online user now
sees a real-time alert when someone logs in or logs out, wherever
they currently are on the BBS (a menu, a board, chat) — not just
inside a dedicated chat room. Works across every front-end and every
combination of them: a telnet login is seen live by users on SSH,
rlogin, or the web, and a web login is seen live by every terminal
user, and vice versa for logouts. Nobody is alerted about their own
login or logout.
Terminal and web run in separate processes in a real deployment, so
this required a small real-time relay between them rather than
piggybacking on a single in-process mechanism — see
models.PresenceEvent's docstring for the design.
Every part of this has a dedicated regression test, including the
actual production watchdog code that prints the alert into an active
terminal session (not a re-implementation of its logic). Full test
suite green.
v1.0.47 — MSP/federation registry security audit; three new themes (August 2026)
A full security and hardening pass over the MSP inter-BBS instant
messaging subsystem and the federation registry (never previously
targeted by a dedicated audit): tightened authentication on the
registry's hub-side API, closed several outbound and inbound
network-request validation gaps, added rate limiting and connection
bounds to the inbound MSP listener, and added a scheduled cleanup job
for stale registry entries. The federation registry's join-request
notification now also emails the hub sysop directly (previously
in-app only), and the Inter-BBS Instant Messages inbox's unread-row
styling is now theme-aware instead of a fixed color pair that could
clash with some themes.
Also: three new site themes — Graphite Teal and Ivory Editorial
(a dark and a light professional option, each with its own type
pairing), and Retro Web '99 (a tiled-background, beveled,
Windows-95-era pastiche, for fun). Also fixed: a pre-existing bug on
the homepage where the Recent Posts and Message Boards lists had a
hardcoded color style that ignored the active theme entirely, and a
low-contrast admin dropdown menu specific to Retro Web '99.
Every fix in this pass has a dedicated regression test. Full test
suite green.
v1.0.46 — ANetBBS Pulse: a read-only mobile status dashboard (August 2026)
A new admin-only status dashboard at /admin/pulse/, built for a phone:
live callers (terminal + web), per-service health with CPU/RAM pulled
from the existing metrics sampler, disk and host uptime, and 24-hour
activity totals, auto-refreshing every 15 seconds. Installable to a
phone's home screen as a standalone app on both Android and iOS.
Read-only by design. No service-control actions, no shell, no arbitrary
log access, and no unauthenticated status endpoint — every route
requires an authenticated admin account through the same gate already
used across the rest of the admin surface. API responses are marked
private, no-store; caller IP and peer addresses are never included in
the payload. The installable service worker caches only the dashboard's
own static assets and explicitly refuses to cache anything under
/admin/. Each data section degrades independently on a database
hiccup instead of failing the whole dashboard.
v1.0.45 — Real BinkP outbound bundle compression and WaZOO FREQ support; a full anetbbs-cfg audit (August 2026)
Closes the two documented "Known BinkP limitations" from docs/06-echomail.md.
Outbound bundle compression — outbound .pkt bundles can now be
sent as real ZIP-compressed ArcMail bundles, per-hub/per-node, off by
default. Turn it on from Admin → Echomail / Hub Management, from
anetbbs-cfg, or remotely as a downstream node's own sysop via the
standard AreaFix %COMPRESS GZIP / %COMPRESS OFF command (previously
parsed but a documented no-op). Bundle naming verified against the
real, published FTS-0006 "WaZOO Filename Conventions" document and
Synchronet's own reference docs — day-of-week extension (.Mo0, etc.),
never .zip.
WaZOO file requests (FREQ) — ANetBBS can now both answer a peer's
FREQ and send its own, following the real FTS-0006 convention. This
turned out to be a different, older mechanism than BinkP's M_GET
command (which is spec'd only for resuming an in-progress transfer,
never for requesting an arbitrary file) — verified against the
published FTS-0006 document rather than assumed. Answering is opt-in
per file area (Admin → File Areas → "Allow FREQ", optional password);
sending is queued from a new Admin → Echomail → WaZOO FREQ page.
Matched files are delivered via the existing TIC-push queue on the
requester's next poll, not synchronously within the same session — a
deliberate scope choice (see anetbbs/echomail/freq.py's docstring),
not full same-session Multiple-Batch-mode.
A full audit of anetbbs-cfg (the SSH/console terminal config
tool), requested directly, plus its first dedicated documentation page
(docs/28-anetbbs-cfg.md, all 16 sections covered in detail). Found
and fixed real gaps accumulated across several releases: the System
section's .env editor was missing MSP, MRC bridge, the file-upload
moderation queue, casino starting balances, the wiki edit gate, and QWK
hub identity fields entirely; File Areas was missing the new FREQ
toggle. Also found and fixed a real bug reaching beyond anetbbs-cfg
itself: builtin_python (the game type ANetCRAFT actually uses) was
missing from both anetbbs-cfg's and the web admin's own game-type
choice list, meaning no admin surface at all could create a new
builtin_python game through its form.
v1.0.44 — Full security/dependency/docs audit: two real path-traversal bugs fixed, dependency floors brought current, a dead config setting wired up (August 2026)
A full sweep across security, dependency freshness, and documentation accuracy, requested directly. Three parallel reviews (dependency/CVE audit against every pinned floor in requirements.txt, a code security review of everything shipped since the last audit plus a fresh broad sweep, and a docs-freshness sweep across the rest of docs/), with every finding independently re-verified before being acted on — CVE claims checked live against OSV.dev, code findings checked by reading the actual source, before any fix was written.
Security fixes:
- Two real path-traversal bugs, both a missing-path-separator bug in a directory-confinement check (str(path).startswith(str(root)) instead of startswith(str(root) + os.sep)) — a sibling directory whose name happens to start with the same prefix (e.g. a gallery at .../nasa and an unrelated .../nasa-secret) passed the check. anetbbs/web/gallery.py's image route was reachable by any logged-in user for .zip-gallery entries (the one file type that bypasses Werkzeug's own separately-safe send_from_directory); anetbbs/web/gallery_admin.py's file-delete route had the identical bug, admin-gated. anetbbs/web/file_areas.py's equivalent route already did this correctly, confirming these were a regression rather than an accepted pattern. Both fixed to match the correct existing pattern; regression tests confirm both are exploitable pre-fix and blocked post-fix.
- Ebook reader's text-fetch shelled out to curl with an unsanitized third-party-API-supplied URL (anetbbs/web/ebooks.py) — no scheme check, no private-address check, and no -- end-of-options guard against an option-like URL. Now validated the same way every other attacker-influenceable fetch target in this app is (core/net_safety.resolve_safe_destination, the same helper the RSS poller's SSRF guard already used), plus a -- separator.
- No rate limiting on netmail's "Crash" immediate dial-out (anetbbs/web/netmail.py) — ticking Crash (or replying to a crash-delivered netmail) spawns a background thread that dials out over BinkP immediately, bypassing the normal poll schedule; unlike every other route that can trigger repeated outbound network activity, compose() had no rate limit at all. Now capped at 20/5min per user, matching the established pattern already used elsewhere (file uploads).
Dependency floors brought current in requirements.txt — several had drifted stale since the last audit, including two that were themselves inside a newly-disclosed vulnerable range: cryptography (44.0.1 → 48.0.1, was in-range for CVE-2026-69247) and urllib3 (2.2.2 → 2.7.0, was in-range for CVE-2026-44431). Also bumped: Flask (3.1.3, CVE-2025-47278), aiohttp (3.14.3, CVE-2026-69243/69244), Pillow (12.3.0, ~2 major versions stale, CVE-2026-59199/55798), requests (2.33.0, CVE-2026-25645). Werkzeug gained its own explicit CVE-floor comment and version pin (3.1.6) for the first time — it previously had none at all, sitting directly under a comment that actually covered a different package (Jinja2).
A real, previously-silent config gap fixed: FILE_MOD_QUEUE_ENABLED (upload moderation queue) was documented in docs/07-file-areas.md and docs/11-spam-control.md as a working .env setting, but anetbbs/config.py never defined it — setting it and restarting did nothing. Wired into Config, admin.py's live-editable settings list, and both .env.example files. Fixing this surfaced a second, related bug: the setting takes effect without a restart, meaning the admin Settings page can write the raw string 'false' into the live config — and bool('false') is True in Python. Both read sites in file_areas.py now parse via str(...).lower() == 'true' instead.
Documentation fixes: README's Game Center list was missing ANetDarkForces (a real, shipped 21st game) and gave the wrong path for the terminal gallery viewer (claimed /home/<user>/, actually $INSTALL_DIR/); docs/17-development.md's door-types table was missing builtin_python (the exact precedent doc readers need for a from-scratch bundled Python door); docs/SECURITY.md dropped a stale "this is the alpha" framing left over from before the project reached its current stable v1.0.x line.
v1.0.43 — Third-party license audit: accurate, complete attribution for the Synchronet compatibility layer and LORD (August 2026)
A full audit of every third-party file bundled with ANetBBS's Synchronet door-game support (anetbbs/games/sbbs_stubs/, anetbbs/games/sbbs_reference/, and the pre-installed LORD door), and a rewritten NOTICE file reflecting exactly what was found. No code changes — this is a licensing/attribution correctness pass, prompted by a closer read of Synchronet's own published copyright terms (GPLv2, with named LGPL exceptions that don't apply here).
- GPLv2 attribution corrected and completed. 8 files already carried an intact Synchronet GPLv2 header; roughly 60 more carry a Synchronet CVS revision tag proving the same origin but had lost their license text somewhere along the way — these are now explicitly documented as GPLv2 too, rather than left unaddressed.
cardlib.js's own separate third-party GPLv2 grant (Randolph E. Sommerfeld) is called out distinctly. The actual GPLv2 license text is now bundled atanetbbs/games/sbbs_stubs/LICENSE-GPL2— previously referenced by nothing in the repo. - Every other identifiable license correctly attributed: MIT (
string.js,jpath.js,qrcode.js), Creative Commons Attribution 3.0 (sha1.js), Creative Commons Attribution-ShareAlike 4.0 (openweathermap.js), and Public Domain (json2.js, embedded insbbs_reference/synchronet-json.js). - The remainder — files with a named community contributor but no formal license statement, and files with no header at all — are now honestly documented as such, rather than silently uncategorized, with an invitation for any author to get in touch.
- LORD's own provenance is documented for the first time: original design by Seth Able Robinson, JavaScript port by Stephen Hurd ("Deuce"), per an in-game credits screen — informally licensed per decades of BBS door-game community practice, not under any OSI or GPL license as bundled. Also flags that
pickle/LICENSE(one of LORD's optional add-ons) is an unfilled GPLv3 template, not a completed license grant. README.mdandLICENSEnow both point toNOTICEfor the full breakdown.