Passkeys не чинят сессию сами по себе
Синтетический research-shot: схема проблемы, контроля, evidence и ретеста в фирменной лабораторной стилистике Virusologia.
Коротко: Продукт кажется современным, когда на логине есть passkeys. Но зрелая проверка смотрит дальше: как меняется session id после step-up, какие recovery flows сильнее или слабее WebAuthn и может ли stolen session обойти весь новый красивый UX.

Проблема

Команды часто внедряют passkeys как новый login method, но оставляют старую recovery модель, старую session cookie и прежнюю логику повышенных действий. В результате сложный фактор защищает только вход, а не аккаунт в целом.

Особенно неприятны случаи, когда пользователь прошёл WebAuthn, но сессия не ротируется, privileged action не требует fresh verification, а account recovery или email change остаются заметно слабее основной аутентификации.

Решение

Нужно смотреть на WebAuthn как на часть identity boundary: регистрация, login, step-up, session rotation, sensitive action, factor reset, recovery, device loss и audit trail.

Хорошая defensive-практика - отделять presence от higher assurance, делать session rotation после сильного подтверждения и помечать операции, для которых нужен fresh WebAuthn или другой trusted step-up.

Что проверить руками

  • Проверить, меняется ли session id после passkey login и после step-up.
  • Сравнить силу primary login и recovery flow: email reset, support override, backup code, device replacement.
  • Проверить, требуется ли fresh verification для email change, payout change, API token creation и password reset.
  • Посмотреть audit trail: видно ли регистрацию нового passkey, удаление старого фактора и fallback login.

Типичные ошибки

  • Считать, что WebAuthn автоматически повышает гарантию для всех сессий.
  • Не разделять login success и privileged action assurance.
  • Не тестировать потерю устройства и жизненный цикл recovery отдельно от happy-path логина.

Defensive checklist

  • Session rotation есть после сильного входа.
  • Sensitive actions требуют fresh verification или step-up.
  • Recovery не слабее primary assurance.
  • Audit logs покрывают factor registration, revocation и recovery events.

Безопасный пример кода

Session rotation после WebAuthn step-up. Пример рассчитан на owned/lab-среду и показывает инженерную логику проверки, а не эксплуатационную цепочку.

def complete_webauthn_stepup(session: dict, user_id: str, assurance: str) -> dict:
    fresh = {
        "user_id": user_id,
        "session_id": issue_new_session_id(),
        "assurance": assurance,
        "stepup_at": now_iso(),
        "csrf_token": issue_csrf_token(),
    }
    revoke_session(session["session_id"])
    persist_session(fresh)
    return fresh


def privileged_action_allowed(session: dict, max_age_seconds: int = 900) -> bool:
    if session.get("assurance") not in {"webauthn", "mfa"}:
        return False
    return age_seconds(session.get("stepup_at")) <= max_age_seconds

Как это должно попасть в отчёт

  • Identity boundary: login, step-up, recovery, factor lifecycle.
  • Evidence: cookie/session behavior, action gating, audit logs, fallback logic.
  • Retest: stolen old session и weak recovery path больше не дают privileged access.
Этическая рамка: материал предназначен для defensive security, исследования собственных систем, controlled labs и подготовки remediation. Здесь нет вредоносных payload-цепочек, persistence-инструкций и шагов для несанкционированного доступа.