Skip to content

PacketBBS: fail closed when the linked web session is revoked - #504

Open
SkrawlCO wants to merge 1 commit into
awehttam:claudesbbsfrom
SkrawlCO:upstream/up-019-packetbbs-auth-lifecycle
Open

SkrawlCO wants to merge 1 commit into
awehttam:claudesbbsfrom
SkrawlCO:upstream/up-019-packetbbs-auth-lifecycle

Conversation

@SkrawlCO

@SkrawlCO SkrawlCO commented Oct 5, 2026

Copy link
Copy Markdown

TITLE: PacketBBS: fail closed when the linked web session is revoked; reset state across auth transitions

Problem

A PacketBBS identity lives in packet_bbs_sessions.user_id and is linked to an online session through bbs_session_id → user_sessions.

  • handleCommand() trusts user_id once it is set. If the linked user_sessions row is revoked (logout, revoke-all, an administrator action, expiry, or account deactivation), the radio node stays authenticated, and the next netmail, chat or post command still executes. Bridge polling likewise keeps delivering queued private messages.
  • Authentication expiry deletes sender rows rather than resetting them, which couples idle logout with sender-row retention.
  • Pending private chat notifications survive QUIT, LOGIN and expiry, so they can be delivered to the next identity on the node.
  • Sessions created before bridge binding existed have no bridge binding.

Impact

Revoking a user's session does not stop that user's radio node. Private messages can reach a node after its user logged out or was revoked.

Repair

  • Every authenticated command and every bridge poll requires a linked online session that Auth::validateSession() still accepts. If it doesn't, the identity is reset as on idle expiry: identity, link, context, drafts and pending notifications are cleared. The command answers Session expired. LOGIN again. without executing, a poll delivers nothing, and no replacement online session is created.
  • Idle expiry resets the identity in one SQL statement under row locks and keeps the sender row and its bridge binding. Stale sender rows are cleaned up separately after PACKETBBS_SESSION_RETENTION_SECONDS (default 86400; documented in .env.example).
  • QUIT, LOGIN and expiry discard unsent private chat notifications from the previous authentication.
  • A legacy session with no bridge binding is bound on its first authorized access.
  • docs/PacketBBS.md documents the lifecycle.

Proof

tests/Integration/PacketBbsSessionLifecycleTest.php (21 tests) runs against an isolated PostgreSQL database, using session-local temp tables only. It is opt-in through PACKETBBS_TEST_DSN and skips otherwise. It covers:

  • valid linked sessions working and refreshing activity;
  • revoked, revoke-all, missing and expired links failing closed before /SEND runs;
  • polling delivering nothing to a revoked identity;
  • expiry versus retention;
  • legacy bridge binding;
  • queue discard across QUIT, LOGIN and expiry.

The existing PacketBbsTextRendererTest and the rest of tests/Unit are unchanged. (Proof against a real PostgreSQL instance is recorded separately.)

A radio node stayed authenticated after its linked user_sessions row was
revoked (logout, revoke-all, admin action, expiry), so commands and
bridge polls kept running and delivering private messages; pending chat
notifications also survived authentication changes, and idle expiry was
coupled to sender-row retention.

Validate the linked session on every command and poll and reset the
identity when it is gone; reset (not delete) on idle expiry with separate
row retention; discard pending notifications across QUIT, LOGIN and
expiry; bind legacy sessions to their bridge on first authorized access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant