Documentation
Getting Started
Daily Operations
Networks & Mail
Look & Feel
Doors & Games
Web Features
Development
Reference
Other

ANetBBS Changelog

Current release: v1.1.53 (October 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.1.53 — BinkP hub-recognition fix for FTN point notation (October 2026)

  • Fixed a bug in the inbound BinkP listener's peer-recognition logic:
    an incoming caller's announced address was matched against a
    network's configured hub address using a plain string comparison,
    with no awareness that FTN point notation allows the same address to
    be written either with an explicit point (e.g. .0) or with the
    point omitted. A hub announcing one form against a hub address
    configured in the other form was treated as a completely
    unrecognized caller — its echomail silently dropped on every
    connection attempt, even though the password would have verified
    correctly. Both forms are now recognized as equivalent in both
    directions. A related, non-blocking false-positive warning in the
    outbound client's own address cross-check is fixed the same way.

v1.1.52 — fsxNet Last Caller timestamp fix (October 2026)

  • The new pipe-delimited Last Caller wire format introduced in v1.1.51
    carries a real timestamp from the originating BBS. Inbound posts in
    this format now use that timestamp for the row's own arrival time
    instead of the moment it was locally imported, so a backlog of
    historical posts imported together no longer all display as having
    arrived at the same instant. The original fixed-field format is
    unaffected, since its own date/time fields are untrusted free text
    by design.

v1.1.51 — fsxNet IBLC wire-format and sync fixes (October 2026)

  • Fixed inbound fsxNet Last Caller (IBLC) parsing to recognize a second
    real wire variant seen in production — a pipe-delimited header/data
    format framed with bare BEGIN/END markers — alongside the
    original fixed-field format. Posts in this variant were previously
    classified as unrecognized and never imported.
  • Fixed the inbound sync job to permanently record a message as
    unrecognized once classified that way, instead of re-scanning the
    same backlog of genuinely unrelated traffic on every run. A message
    matching a known IBOL/IBLC shape whose feature toggle is simply
    turned off is deliberately excluded from this — re-enabling the
    feature later still picks it up normally.

v1.1.50 — Submenu renumbering fix for Door Games and File Areas (October 2026)

  • Fixed a bug in the admin "Renumber" button and drag-and-drop
    reordering for both Door Games and File Areas: games/areas inside a
    submenu category were being assigned a gapped 10/20/30/... number
    sequence instead of a clean 1/2/3/... one, contradicting the
    automatic every-boot numbering behavior those submenus already have.
    A sysop editing a submenu entry's number to 1 and clicking Renumber
    would see it immediately revert to 10. Both admin tools now match
    the clean-numbering behavior submenu categories are supposed to have;
    top-level (non-submenu) numbering is unaffected.

v1.1.49 — Registration controls, docs access toggle, poll cleanup, and RSS/Who's Online settings (October 2026)

  • New account registration can now be closed entirely, or gated behind
    a shared invite code — both configurable at Admin → Settings,
    defaulting to the existing open-registration behavior. Enforced
    consistently across the web registration form and the terminal
    (telnet/SSH/rlogin/PETSCII) registration flow.
  • The built-in docs browser — and the new-sysop tutorial walkthrough,
    which shares the same toggle — can now be restricted to admin
    accounts only, instead of being unconditionally public to anyone
    with no login.
  • Admins can now delete a poll outright, not just close it.
  • Who's Online's IP display to admins now has a real configuration
    toggle, rather than being permanently on with no way to turn it off.
  • RSS feed URLs are now validated immediately when a feed is saved,
    with a clear error message for a missing or unsupported URL scheme,
    instead of only surfacing as a silent failure at the next poll.

See 11-spam-control.md,
SECURITY.md, and 16-rss-reader.md
for details.

v1.1.48 — Native MUD engine, door-games admin fixes, game-server mode, MUD telnet protocols, and DoorParty federation (October 2026)

The Shattered Realm — a native, built-in MUD (Game Center → Door
Games): a full multiplayer text MUD with no external door or dropfile,
one persistent character per account. Four zones (Village, Forest,
Dungeon, and the high-level Ruined Citadel), movement/combat/inventory/
shops/a healer, live chat with everyone else exploring, and real
leveling. Shipping with its first release:

  • Four character classes (Warrior, Mage, Rogue, Cleric), chosen
    once at character creation, each with its own stat growth and combat
    ability (Power Attack, Fireball, Backstab, Heal).
  • Quests: an admin-editable catalog (giver room, target monster,
    kill count, reward); accepting tracks kills anywhere in the world,
    turning in at the giver hands over the reward. A working example
    quest, "Cleanse the Citadel," ships with the base world.
  • Class-restricted signature weapons, mana that regenerates on
    movement and fully refills at the healer, and a full admin UI for
    building rooms, items, monsters, and quests.
  • Negotiates real MUD telnet protocol extensions (MSSP, MCCP2, GMCP)
    when reached through Game Center, for real MUD clients and
    server-listing crawlers.
  • Fixed a chain of bugs that together blocked playing it at all: the
    web UI always reported "you need to be logged in" even for a
    genuinely logged-in user; once that was fixed, actually loading or
    creating a character crashed outright on the web (missing database
    access) and, separately, the same crash over telnet/SSH; and typing
    a character name or class choice showed nothing at all on screen
    (no local echo) in both cases. Class, room, and shop descriptions
    also no longer break words apart at the right edge of the screen,
    and the shop's item list is now paginated instead of running off
    the top of the screen.

Door Games admin fixes: the number a player types to pick a door
in the Door Games menu no longer shifts when an unrelated door is
added, removed, or reordered elsewhere in the list — previously every
door's number was recomputed from scratch on each redraw, so changing
one door silently renumbered every door listed after it. Reordering is
now drag-and-drop in the web admin — for both individual games and
their categories, with a category's on-screen position now actually
following where it's dragged to, not just its displayed number — (with
a one-click "renumber this list cleanly" option, scoped to just one
category if needed) and up/down keys in the anetbbs-cfg terminal
tool, both grouped by category with collapsible sections. Every
category's own submenu page now always numbers its doors starting at
1 with no gaps, automatically on every restart, no admin action
needed — top-level (not-in-a-submenu) door numbers still only change
when a genuine duplicate is found, since those can be hardcoded into
the main Door Games screen's own art. Categories themselves always number cleanly
starting at 1, same automatic restart-time fix.

Standalone game-server deployment mode: a new install-wide option
(GAMESERVER_MODE_ENABLED, plus an optional
GAMESERVER_DEFAULT_DOOR_SLUG) drops every connection straight into
the door menu — or straight into one specific door — instead of the
normal BBS main menu, for running a dedicated, game-only install.
Works over telnet, SSH, and rlogin.

DoorParty door-network federation: a new admin page (Admin → Door
Networks) to bulk-import DoorParty's catalog of shared multiplayer
doors (LORD, TradeWars 2002, Usurper, and more) as ordinary local
games, using the existing rlogin door-game bridge. Requires running
the separate, independent doorparty-connector tool alongside
ANetBBS; see the updated docs for setup.

File area categories and submenu sections: file areas can now be
grouped the same way door games already are — a new Admin → File Areas
→ Categories page, drag-and-drop reordering (for both individual areas
and their categories) in the web admin, and the same automatic
numbering self-heal. A category flipped to "submenu section" collapses
to one line in the area list (terminal and web); picking it opens a
second screen listing just that category's own areas. File areas had
no grouping concept at all before this.

docs/14-door-games.md, docs/07-file-areas.md, docs/28-anetbbs-cfg.md,
and new docs/40-mud.md cover all of the above.

v1.1.47 — Five new web themes (October 2026)

Five new full web-UI themes, each built as a distinct visual world
rather than another palette swap on the existing layout, using the
same --theme-stylesheet override mechanism Command Deck/Hackers/
Graphite Teal/Ivory Editorial/Retro Web already established (a
dedicated CSS file per theme restyling the navbar, cards, buttons,
forms, tables, badges, alerts, footer, and code blocks):

  • Ceefax Teletext — 1980s broadcast videotext: flat primary-colour
    blocks, chunky VT323 pixel type, hard right angles everywhere, no
    gradients.
  • Brass & Oscilloscope — a ham-radio control room: brushed walnut
    panels, brass rivets on the navbar and cards, oscilloscope-green LCD
    readouts, Oswald + JetBrains Mono.
  • Mahogany Study — a gentleman's reading room: deep mahogany,
    brass nameplates, leather-bound cards, Playfair Display + Lora, with
    a small illuminated drop-cap on each card's opening line.
  • Cyanotype Blueprint — an engineering schematic: Prussian-blue
    grid paper, white/cyan line-art only, dashed part boxes with corner
    crosshairs, Big Shoulders Display + Space Mono.
  • Riso Pop-Art Arcade — a pinball backglass poster: flat saturated
    red/blue/yellow on black ink, thick comic outlines with hard offset
    shadows, Anton + Archivo.

All five pass WCAG AA contrast on body text (checked programmatically,
matching this project's own stated bar for every built-in theme) and
seed automatically on next boot — no migration needed. Selectable
immediately from Profile → Themes. docs/08-themes.md and
docs/00-overview.md updated (13 → 18 built-in themes).

v1.1.46 — Fix: username lookups were case-sensitive outside of login (October 2026)

Logging in (both web and terminal) has always matched a username
case-insensitively, via web/auth.py's login() and
core/user_manager.py's authenticate(), both using the
func.lower(User.username) == value.lower() idiom. Several other
places that look up a user by a typed-in username never adopted that
same convention and did a plain exact-match query instead — reported
live: sending a private message to a recipient whose account used mixed
case (e.g. "StingRay") failed with "user not found" when the sender
typed it in lowercase ("stingray").

Found and fixed at every site with the same shape, all now using the
identical case-insensitive pattern login already established:

  • features/bbs_ui.py's _send_pm() — terminal "Send Private Message"
  • web/pm.py's compose() — web PM compose
  • web/profile.py's view_user() — /profile/<username>
  • web/gemini.py's view()/raw_gemtext() — /gemini/<username>[.gmi]
  • core/finger_server.py's _user_info() — finger protocol (port 79)
  • web/auth.py's forgot_password() — username-or-email identifier
  • web/admin.py's add/edit-user forms — duplicate-username check

Password matching is untouched and intentionally still exact-case —
only the username lookup itself was ever the bug. Covered by a new
regression test file exercising all seven sites.

v1.1.45 — Fix: Min Read Level 0 (public access) silently reset to 10 (October 2026)

Setting a board's Min Read Level to 0 (public/everyone) in the Web Admin
UI silently saved as 10 (registered) instead, both when creating and
editing a board — reported by ElectroDrome (bbs.electrodrome.net,
#30). Root cause:
admin.py's add_board()/edit_board() used form.min_access_level.data or 10 as a "default if the field is blank" shortcut; WTForms'
IntegerField coerces the submitted value to a real Python int before
that line runs, and Python's or treats the integer 0 as falsy, so a
deliberately-entered 0 was indistinguishable from an empty field. Fixed
by checking for None explicitly, matching the (already-correct) pattern
used two lines away to populate the edit form in the first place.

A sweep of the rest of the admin/access-level code for the same shape
found one more reachable instance of the save-path bug (the per-user
access level field in the sysop's user-management page) and two read-side
instances of a related but more consequential variant: echomail.py's
area-visibility check and games.py's game-visibility check both computed
a user's effective access level with the same or 10 shortcut, meaning a
user account genuinely set to access level 0 was treated as level 10 — a
real level, not a blank field — for every area/game visibility decision,
rather than their actual, lower level. All four fixed and covered by a new
regression test file; none were reachable by an ordinary user doing
ordinary things.

v1.1.44 — CI-only test flake fix (October 2026)

Three echomail/BinkP test fixtures (test_poller_poll_node_flushes_netmail.py,
test_hatch_delivery_to_downstream_nodes.py, test_join_request_resend_email.py)
generated fake FTN node addresses from a single-letter suffix using Python's
built-in hash(). That hash is randomized per interpreter process, so under
some random seeds two different fixtures' suffixes hashed to the same address
mod N and collided against a real database uniqueness constraint — a failure
that depended entirely on which seed a given CI run happened to start with,
and never reproduced locally under a different seed. Replaced with a
deterministic character-sum calculation in all three files, verified stable
across every tested seed including the two confirmed to collide under the
old code. Test-only change; no application code affected.

v1.1.43 — Second full audit pass: access-control, input-handling, and infrastructure hardening, plus a docs/wiki follow-up (October 2026)

A second self-directed audit pass covering security, dependency/
infrastructure currency, and documentation accuracy end to end. Generic
summary below, deliberately not itemized by specific issue, per this
project's standing policy for anything security-adjacent.

Hardening: a terminal menu action and a database-driven menu's
art-loading path each had a missing check closed — one around who is
allowed to reach a given built-in program via a custom menu, the other
around how a sysop-set menu name resolves to a file on disk. A couple
of places where one user's own text could reach another user's
terminal screen unfiltered were tightened to match protections already
applied elsewhere in the same code. Two authentication-adjacent
endpoints picked up a timing-consistency fix already applied to their
sibling endpoints. The InterBBS mail system's file-request handling and
the chat bridge's WebSocket listener each picked up a cap an equivalent,
already-protected code path already had. None of these were reachable
by an ordinary user doing ordinary things — fixed anyway, each with its
own regression test.

Infrastructure: every generated systemd service unit picked up
additional sandboxing directives, scoped individually per unit's actual
needs rather than applied as a blanket change; CI workflow actions were
brought current ahead of a since-passed platform deprecation deadline,
with automated monitoring added so this doesn't recur silently; the
containerized build's base image moved off an end-of-life support
track; and the automated dependency-update bot gained a second coverage
lane it was missing.

Documentation and wiki: a built-in wiki page overstated how many
admin-review notification types exist and named one that doesn't
actually have a settings toggle; another understated the current
built-in game count by one; and a stale code comment describing a
security header as not-yet-implemented was corrected to describe what
has actually shipped since.

v1.1.42 — MRC multi-part message spacing fix (October 2026)

Widened the delay between pieces of a split multi-part MRC chat message
from 0.6 to 0.75 seconds, across the bridge and every client (terminal
and web). The shorter spacing could trip the hub's flood control on
longer messages, causing pieces to be dropped; the wider spacing matches
what the external umrc-client already uses and clears the issue.
Contributed by winzlocom.

v1.1.41 — Full audit pass: hardening, dependency updates, and a documentation/wiki accuracy sweep (October 2026)

A broad self-directed audit covering security, dependency currency,
and documentation accuracy end to end — every numbered doc, the
built-in wiki, and the top-level project files. Generic summary below,
deliberately not itemized by specific issue, per this project's
standing policy for anything security-adjacent.

Hardening: a sweep of every connection-handling code path across
the terminal dial-out gateway, external door-game bridges, file
transfer, and the MRC bridge closed several gaps in timeout handling
and input bounds that could leave a session or service hung or, in
one case, a data file corrupted under concurrent writes. A couple of
sysop-only code paths that build file paths from configurable text
fields were also tightened as defense in depth. None of these were
reachable by an ordinary user doing ordinary things — fixed anyway,
each with its own regression test.

Dependencies: several package floors in requirements.txt were
bumped to current patched versions, the base image used for one
optional build stage was updated off an end-of-life release, a pair
of long-dead, unused dependencies left over from a removed feature
were deleted outright, and packaging metadata now states the real
minimum Python version this project has always required.

Documentation and wiki: every file under docs/, the BBS's own
built-in wiki content, and README.md were checked claim-by-claim
against the current codebase — feature counts, config defaults, file
paths, and cross-references that had quietly drifted out of date as
the project grew (achievement badges, scheduled-event handlers, theme
counts, menu action types, and a handful of others) are now accurate
again, along with a changelog entry from a few releases back that had
gotten merged into the wrong section.

Also fixed: a web settings-save button (IRC client configuration)
that silently failed for every user — unrelated to the audit's
security scope, just found along the way.

v1.1.40 — NodeSpy live screen mirror: same-day fixes from real live testing (October 2026)

Three real bugs found by actually using the brand-new NodeSpy live
screen mirror (v1.1.39) on a live BBS, fixed the same day:

  • The mirror could get stuck on an old screen indefinitely. A
    screen is normally drawn as a burst of several write() calls in a
    row (clear, banner, content, prompt) with essentially no delay
    between them — all well inside the mirror's own DB-write throttle.
    Only the first call in a burst that happened to clear the throttle
    actually persisted; the rest of that same burst, often the most
    complete version of the screen, got throttled away. If the NEXT
    screen's burst also started soon enough afterward, its calls could
    ALSO all land inside the throttle window — leaving the mirror frozen
    on a screen the user had already moved past, with nothing left to
    trigger a re-check once the session settled down waiting for the
    next keypress. Fixed by forcing an immediate, unthrottled flush of
    the current screen right before the session blocks waiting for
    input — the moment a screen has actually settled and the throttle
    no longer needs to protect anything.
  • A sysop-authored custom ANSI menu screen never reached the mirror
    at all
    — a real, separate bypass in the main menu renderer (and
    the shared helper several other built-in screens use) wrote
    directly to the raw connection to preserve exact CP437 byte
    fidelity, skipping the normal code path the mirror hooks into
    entirely. Since most real BBS main menus have custom ANSI art, this
    made the previous bug worse in practice — the mirror could stay
    stuck through an entire menu screen with no trace of it ever having
    been shown. Both bypasses now feed the mirror too.
  • Box-drawing and block characters rendered wrong (misaligned,
    wrong glyphs) in the web mirror
    , traced to a decode mismatch:
    sysop-authored ANSI art is stored as raw CP437 bytes, which need
    CP437 decoding to show their real box-drawing glyphs in a browser;
    two of the mirror's capture points were using the wrong decode,
    producing unrelated accented-letter mojibake instead of lines and
    corners.

Also made the mirror itself easier to read — larger text, and the
terminal view now sizes to its actual content instead of floating in
empty space to one side.

v1.1.39 — SSH hardening, and a live NodeSpy screen mirror (October 2026)

The SSH server now generates and offers an Ed25519 host key alongside
its existing RSA-2048 one, rather than RSA alone. Both are offered
together — nothing about the existing RSA key changes, so already-saved
known_hosts entries keep working — but a modern SSH client that
prefers Ed25519 (smaller, faster, no SHA-1-era baggage) now gets it
automatically. An already-running install picks up the new key the
next time its SSH service restarts, the same self-healing pattern new
database columns already use here — no sysop action needed beyond a
normal update/restart. Also bumps the SSH library (asyncssh)
dependency floor to pick up several upstream fixes released since the
previous floor, none of which were actually reachable through how
this app uses the library, but cost nothing to clear anyway.

The admin NodeSpy panel's per-node detail view now shows a genuinely
live screen mirror — the actual rendered content of a user's terminal,
with real ANSI color, refreshing about once a second — rather than
just a "what page/action are they in" label. This closes a long-
standing gap: the underlying database column for this existed and the
per-node page already had a spot for it, but nothing ever populated
it. Admin-only, same permission gate as the rest of the Control
Center; the public "Watch It Live" page does not and will not show
this, for the privacy reasons already documented there.