ANetBBS Changelog

Current release: v1.0.68 (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.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-monitor opened 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 in core/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 in NodeActivity.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 at anetbbs/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 in sbbs_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.md and LICENSE now both point to NOTICE for the full breakdown.

v1.0.42 — A genuine no-root install path, documented and verified end to end (August 2026)

New: a fully documented, verified no-root installation path (docs/01b-no-root-install.md) for anyone without sudo/root access. The capability mostly already existed in anetbbs-install but was undocumented and had one real bug — its final instructions always said sudo systemctl start ... even after declining systemd entirely. Added a third, genuinely rootless service option (systemd user units, persisting via loginctl enable-linger), fixed the final instructions to match whichever choice was made, and fixed a real live bug found while proving this end to end: MSP/SYSTAT default to enabled and were never addressed by the wizard, so every install logged a permission-denied bind failure on every boot — now correctly disabled by default. Verified with a real install run as an ordinary non-root user, followed by actually booting the result and confirming clean HTTP 200s with zero permission errors. 22 new tests.

v1.0.41 — Fixed a real bug where the MRC bridge always showed blank BBS info to the wider network; manual install docs now cover MRC setup (August 2026)

Fixed a real bug where every BBS running the MRC bridge showed blank telnet/SSH/website/sysop/description info to the wider MRC network — the default outcome for every install, not an edge case. install.sh/update.sh wrote the wrong config key names and never wrote telnet/SSH contact info at all; fixed in both, with real telnet/SSH strings now derived at install time. Existing installs need their mrc/bridge/config.json corrected by hand (neither script overwrites an existing one) — see docs/INSTALL.md's troubleshooting section. 12 new tests.

The manual install guide (docs/INSTALL.md) now covers MRC bridge setup, the one real gap found in a full audit of the doc against current code (everything else already checked out).

v1.0.40 — Fixed a live BinkP process-corruption bug; hub node polling and message flavor (Crash/Hold) now do what they say (August 2026)

Fixed a real production incident where BinkP delivery silently stopped working after running clean for days. The in-app notification helper's live-toast push imported web_app on the fly to reach its SocketIO instance — safe from the real web process (already running under eventlet from the start), but anetbbs-binkp.service is a plain asyncio process that was never meant to load eventlet at all. The first time an inbound BinkP session delivered netmail to a real local user, that import triggered eventlet.monkey_patch() mid-process, silently corrupting already-created threading primitives and breaking the SQLAlchemy connection pool for the rest of that process's life (RuntimeError: cannot notify on un-acquired lock on every DB write afterward) — so outbound TIC/echomail delivery on later sessions from that same process quietly stopped working, looking like a routing problem rather than what it actually was. Fixed by checking current_app.extensions instead of importing web_app fresh — the same safe pattern already used elsewhere in the codebase for this exact class of bug. Reproduced the production traceback exactly by reverting the fix locally before confirming it, and added a regression test that does the same.

Hub-initiated polling of downstream BinkP nodes can now run on a real schedule, not just manually. Each node can now be given its own auto-poll interval on its admin page; leaving it blank keeps the node exactly as it works today (manual/poll-in-only).

Crash and Hold now actually change delivery timing, not just a cosmetic packet flag. Crash now fires an immediate out-of-schedule delivery attempt the moment a netmail or file-echo item is queued; Hold now actually excludes an item from our own outbound dial-out, shipping only once that peer polls in to us. File-echo/TIC distribution gets these flags for the first time, exposed as Crash/Hold checkboxes on file-area uploads.

New: inline in-place animation for sysop-defined ANSI/CP437 screens. Any screen shown at login/logoff/etc. can now embed a short looping animation directly in its body (@ANIMSTART@...@FRAME@...@ANIMEND@ markers, alongside the existing @PAUSE@ pagination convention) instead of only ever being static art. No code changes needed to use it — drop an animated screen into data/mods/text/ the same way any other custom screen override already works. Documented in docs/04-ansi-screens.md and the in-app wiki.

Fixed a real live bug where a SAUCE-tagged .ans file showed its own metadata as literal garbage text on screen — including the main menu's own file-based art override, and every welcome/goodbye/newuser/custom screen. SAUCE stripping previously only existed inline inside the ANSI Editor's import route; now a single shared sauce.strip() is used everywhere a raw .ans/.asc file is read from disk.

Over 2,780 tests, all green.

v1.0.39 — Second, deeper security and performance audit pass, including docs (August 2026)

A follow-up audit was carried out across the whole codebase, documentation, and deployment tooling — broader in scope than v1.0.38's pass, this round also covered the wiki feature and every doc file, not just application code and scripts. Additional real issues turned up and were fixed, again addressed in strict severity order: further hardening of network-facing protocol handlers, closing a couple more resource-leak and unbounded-growth points in long-running background processes, tightening several more file-permission and credential-handling spots in the install/update/container tooling, adding a few more missing rate limits and size caps, and correcting some troubleshooting documentation that could otherwise lead a sysop toward a weaker configuration than intended. New regression tests were added throughout for every fix — the full suite now sits at over 2,750 tests, all green. As with the previous round, this entry intentionally omits specifics in the interest of responsible disclosure; sysops running an older release should update at their earliest convenience.