Skip to content

security(backend-auth): password-epoch revocation check fails open when Redis errors (backend equivalent of #16387) #16411

Description

@mrveiss

Problem

#16387 made the SLM's password-epoch revocation check fail CLOSED in
autobot-slm-backend/services/auth.py (decode_token_async). That check
(is_token_revoked_by_password_change / get_password_epoch, in the shared
autobot_shared/user_management/password_epoch.py) has a second, live
caller
in autobot-backend/auth_middleware.py's get_current_user
(around the #12924 block), with no exception handling of its own around
the call.

#16387's fix deliberately did NOT touch password_epoch.py's internals,
because doing so would silently change this backend call site's behaviour
too (get_current_user has no try/except there today, so it inherits
whatever get_password_epoch does on a Redis error) — out of scope for an
SLM-only issue, per that issue's own instructions.

Today, get_password_epoch still catches Exception around its Redis call
and returns None (→ "not revoked") on failure — i.e. it fails open for
this backend caller. A revoked token (password changed after a leak) is
still honoured by the backend while Redis is erroring.

Decision needed (owner)

Whether the backend's get_current_user should adopt the same fail-closed
policy #16387 chose for the SLM (a Redis outage denies backend login too),
or a different one — and if fail-closed, how to implement it without
changing password_epoch.py's shared contract in a way that also needs SLM
follow-up (or, if the module's contract does change, updating both callers
in the same PR).

Acceptance criteria

  • The chosen behaviour is implemented for autobot-backend/auth_middleware.py's use of is_token_revoked_by_password_change, with a test for a Redis error.
  • The decision and rationale are recorded in docs/developer/THREAT_MODEL.md §4, next to #16387's entry.

Refs #16387, #12924

Activity

  1. mrveiss commented on Sep 11, 2026

    @mrveiss
    OwnerAuthor

    Decision needed (owner). This is the backend counterpart of #16387, where the SLM now fails closed.

    autobot_shared/user_management/password_epoch.py is shared, and the backend's auth_middleware get_current_user calls it with no exception handling of its own. The #16387 PR therefore leaves this path alone: changing the shared module would silently change backend authentication.

    Option Gain Loss
    1. Fail closed (recommended) A revoked token is never honoured, the same rule as the SLM (#16387) and the same for humans and agents A Redis outage takes backend login down, and the main UI with it
    2. Fail open, with an alert on every fail-open Login survives an outage Revoked tokens work during the outage; the risk is only made visible
    3. Bounded fail-open (tokens issued before the outage, for N minutes) Limits the exposure window The most complex option; revoked tokens still work within the window

    Recommendation: option 1. The backend already depends on Redis for sessions and chat, so an outage degrades it anyway. Honouring a revoked credential is the worse failure, and one rule for both services is simpler to reason about.

  2. added
    needs-decisionBlocked on an owner decision; options and a recommendation are on the issue
    on Sep 11, 2026
  3. mrveiss commented on Sep 12, 2026

    @mrveiss
    OwnerAuthor

    Owner decision, 2026-09-12: fail closed, the same rule as the SLM (#16387, #16413). When the backend's password-epoch revocation check can't run because Redis errors, the token is denied with 401. A Redis outage therefore takes backend login down, and that's the accepted trade-off. The shared autobot_shared/user_management/password_epoch.py helper becomes fail-closed for both callers, and auth_middleware turns that failure into a normal 401.

  4. removed
    needs-decisionBlocked on an owner decision; options and a recommendation are on the issue
    on Sep 12, 2026
  5. added this to the v0.9.0 milestone on Sep 12, 2026
  6. mrveiss commented on Sep 14, 2026

    @mrveiss
    OwnerAuthor

    Closure evidence (merged by #16433, 9caa367)

    AC evidence (origin/main)
    The chosen behaviour is implemented for auth_middleware.py's use of is_token_revoked_by_password_change, with a test for a Redis error autobot-backend/auth_middleware.py:905-906 if user_data.get("auth_method") == "jwt": await reject_if_revoked_by_password_change(user_data). autobot-backend/auth_revocation.py:33-41 catches RevocationCheckUnavailable and raises HTTP_401_UNAUTHORIZED. The shared helper raises on a store error at autobot_shared/user_management/password_epoch.py:153-154. Test: autobot-backend/tests/test_password_revocation_fail_closed_16411.py:47-54 test_a_redis_error_denies_the_token is parametrised with RedisError("down"), ConnectionError, and TimeoutError. Each error is raised by the client that get_async_redis_client returns, and the test asserts status_code == 401. :58 covers a missing client, which also gets a 401. The controls at :67 and :86 admit the token.
    The decision and rationale are recorded in THREAT_MODEL.md §4, next to #16387's entry docs/developer/THREAT_MODEL.md:126-127, below #16387's entry at :120: "The shared password-epoch helper fails CLOSED in both services (#16411, #16422, owner decisions) … an except reading it as "not revoked" is a fail-open."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions