Skip to content

tech-debt(security): two different validate_session_ownership functions — same name, different signature and semantics #14016

Description

@mrveiss

Found during review of PR #14013 (#14011). Not a live bug — every current caller imports the right one — but it is a name collision on a security-critical helper, which is a poor thing to leave to luck.

The two

module signature semantics
autobot-backend/security/session_ownership.py validate_session_ownership(session_id: str, request: Request) -> Dict the canonical validator; FastAPI-dependency shaped, integrates feature flags, metrics and audit
autobot-backend/api/conversation_files.py validate_session_ownership(request: Request, session_id: str, user_data: Dict) -> bool a different helper: different parameter order, extra required argument, returns a bool

They differ in arity, parameter order, and return type, so a wrong import is not a subtle behaviour change — but the two most plausible mistakes both fail in the wrong direction:

  • used as a FastAPI dependency, the conversation_files one would have FastAPI try to resolve user_data as a query parameter
  • called positionally with the canonical argument order, request and session_id are swapped, so the id being validated is not the id being used

The second is the shape that produces a check that runs, passes, and guards nothing — the exact failure mode #13982 and #14011 were about.

Why it is worth fixing rather than noting

grep -rn "def validate_session_ownership" returns two hits and no indication which is canonical. Someone adding an ownership check to a new endpoint — which is now a recurring task, see #13982, #14011, #14012 — has a 50% chance of importing the wrong one from a bare grep.

This is the same class as the same-name/different-concept enum forks (#13845/#13846): two implementations of one idea that only a reader can tell apart.

Suggested fix

Rename the conversation_files one to what it actually is (it takes pre-fetched user_data and returns a bool — something like _verify_session_owner_for_user, and make it private if it has no external callers), leaving exactly one public validate_session_ownership in the codebase.

Consolidating them is the alternative but likely wrong: they answer different questions and one is dependency-shaped. One canonical name plus one clearly-scoped private helper is the smaller, safer change.

Acceptance criteria

  • grep -rn "def validate_session_ownership" returns exactly one definition
  • Every caller of the renamed helper updated; no behaviour change
  • The canonical validator's docstring states it is the one to use for endpoint ownership checks

Related

Activity

  1. modified the milestones: Backlog, v0.11.0 on Sep 12, 2026
  2. mrveiss commented on Oct 10, 2026

    @mrveiss
    OwnerAuthor

    Decision needed (owner), security. The de-fork stopped before merging, because a merge would widen access. The two validate_session_ownership functions are genuinely different security postures. Decision table (read from code) in short: if the conversation-files routes moved onto the canonical security/session_ownership.py validator, they would ALLOW what they deny today in 7 cases: auth disabled, enforcement disabled, log_only non-owner, flags unavailable (degrades to log_only), user with no role, session shared with user (#689), and owner unreadable. Conversely, the canonical validator denies a global admin outside the session's org, which the file routes allow.

    Options:

    1. Rename apart (committed locally as 7d4c136354 on issue-14016-session-ownership, NOT queued): the file routes' check becomes private _authorize_session_for_user, with one public name and a guard. It widens nothing but contradicts the standing rule "merge, never rename apart".
    2. Real merge, recommended: the canonical validator gains an explicit strict=True posture for the file routes. No flag or fast paths, role required, unreadable owner denies, no shared or org grants, global admin allowed. One function, two named postures, and a sign-off that the strict posture belongs in the canonical module.
    3. Move the strict logic into security/session_ownership.py as a separately named helper. Same behaviour as 1, in one module.

    Separate security finding in the canonical validator (it applies whatever is chosen): SessionOwnerUnreadable is swallowed and treated as "unowned", so the caller is allowed and claims the session. The file routes deny in that case (#14033). That is fail-open on a read error. The docstring calls the legacy-claim fallback deliberate, but an unreadable owner is not an unowned session.

  3. added
    needs-decisionBlocked on an owner decision; options and a recommendation are on the issue
    on Oct 10, 2026
  4. mrveiss commented on Oct 10, 2026

    @mrveiss
    OwnerAuthor

    Correction to the comment above: the fail-open finding (SessionOwnerUnreadable treated as unowned in the canonical validator) is already tracked as #17481. It is not a new finding, and its fix lives there.

  5. mrveiss commented on Oct 10, 2026

    @mrveiss
    OwnerAuthor

    Owner decision (2026-10-10): option 2, a real merge. The canonical validator gains an explicit strict posture for the file routes: no flag or fast paths, role required, an unreadable owner denies, no shared or org grants, global admin allowed. One function with two named postures, plus a guard. Also fix the fail-open: SessionOwnerUnreadable must deny, never count as "unowned" and let the caller claim the session (#14033).

  6. removed
    needs-decisionBlocked on an owner decision; options and a recommendation are on the issue
    on Oct 10, 2026
  7. added a commit that references this issue on Oct 11, 2026
  8. mrveiss commented on Oct 11, 2026

    @mrveiss
    OwnerAuthor

    Closure evidence (merged in merge train #18314, 5efe902; constituent #18258). Verified against main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions