Skip to content

Bug: GROUP_INVITE expiration not enforced (invites never expire) #285

Description

@QuickMythril

Bug: GROUP_INVITE expiration not enforced (invites never expire)

Summary

Closed-group invites (GROUP_INVITE) include a TTL/expiration parameter, but expiration is not enforced anywhere in Qortal Core. As a result, any invite can be accepted indefinitely, even long after its intended expiry, allowing late/unwanted joins to closed groups.

Impact / Why this matters

  • Closed-groups effectively have permanent invites unless admins manually cancel them or kick members after they join.
  • Admins who issue short-lived invites (e.g. “valid for 1 day”) cannot rely on TTL for access control.
  • Users can accept an old invite months later and become a member, which is surprising and can be a privacy/security concern for groups that assume invite expiry works.
  • Current workaround is manual: kick members after they join, or preemptively cancel invites.

Expected behavior

For closed groups, once an invite’s TTL has passed:

  • The invite should be treated as invalid/expired.
  • A JOIN_GROUP should not be auto-approved by an expired invite (ideally it becomes a join request again, requiring a fresh invite/approval).

Actual behavior

  • Issued invites can be accepted at any time, regardless of TTL/expiration.
  • JOIN_GROUP finds an invite record and finalizes membership even if the invite’s expires_when is in the past.

Technical cause (current implementation)

  • Invite TTL is stored and expires_when is computed when saving GROUP_INVITE (expiry persisted in GroupInvites.expires_when).

  • However:

    • Join processing does not check expires_when at all; it only checks whether an invite exists and then uses it to approve membership.
    • Invite retrieval APIs / repository queries also do not filter expired invites; they return all invites (expired or not), and the join path treats any returned invite as valid.

In short: expiration is data-only right now; it is never consulted in validation/finalization logic.

Reproduction (simple)

  1. Create a closed group.
  2. Admin sends GROUP_INVITE to address X with a short TTL (e.g. 60 seconds).
  3. Wait until TTL has passed.
  4. Address X sends JOIN_GROUP.
  5. Observe: X is still added as a member (invite behaves as non-expiring).

Proposed fix (high level)

  1. Consensus-enforced expiry on membership finalization (behind a feature trigger):

    • After the trigger height, treat an invite as valid only if expiry == null/0 (never) OR finalization-time <= expires_when.
    • If expired, ignore invite and handle JOIN_GROUP as a join request (preferred) or reject (more disruptive).
    • Must be feature-triggered because it changes membership outcomes (consensus).
  2. API filtering for invites:

    • Default invite-list endpoints should omit expired invites.
    • Add optional flag (e.g. includeExpired=true) to show them for debugging/UI needs.
  3. Tests:

    • Add unit/integration tests covering:

      • pre-trigger vs post-trigger behavior
      • invite-first and join-first ordering
      • expired invite does not grant membership
      • API does not return expired invites by default

Notes / Related

  • There is a manual cancellation tx (CANCEL_GROUP_INVITE), but that’s not a substitute for TTL enforcement.
  • Prefer filtering over DB pruning for expired invites (avoid unnecessary state mutation; keep consensus logic deterministic).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions