Skip to content

fix: enforce authentication on unauthenticated endpoints and harden auth_must#1294

Open
eren-karakus0 wants to merge 2 commits intoeigent-ai:mainfrom
eren-karakus0:fix/security-hardening-auth-race-conditions-memory-leaks
Open

fix: enforce authentication on unauthenticated endpoints and harden auth_must#1294
eren-karakus0 wants to merge 2 commits intoeigent-ai:mainfrom
eren-karakus0:fix/security-hardening-auth-race-conditions-memory-leaks

Conversation

@eren-karakus0
Copy link

Related Issue

Closes #1293

Description

This PR fixes 4 authentication and authorization vulnerabilities in the server layer, discovered during a systematic audit of endpoint security.

1. CRITICAL — Unauthenticated Snapshot Listing (server/app/controller/chat/snapshot_controller.py)

Before: GET /chat/snapshots had no auth_must dependency. Any unauthenticated request could list ALL snapshots across ALL users, leaking api_task_id, image_path, and browser_url data.

After: Added auth: Auth = Depends(auth_must) and a ChatSnapshot.user_id == user_id filter. Users can now only list their own snapshots. This aligns with the existing behavior of all other snapshot CRUD endpoints.

2. HIGH — Snapshot IDOR in Get Endpoint (server/app/controller/chat/snapshot_controller.py)

Before: GET /chat/snapshots/{snapshot_id} required authentication but did NOT verify that the snapshot belonged to the authenticated user. Any authenticated user could read any other user's snapshots by iterating IDs.

After: Added an ownership check: if snapshot.user_id != user_id: raise HTTPException(403). This is consistent with the update and delete endpoints which already had this check.

3. HIGH — Unauthenticated Share Link Creation (server/app/controller/chat/share_controller.py)

Before: POST /chat/share had no auth dependency. Anyone could generate share tokens for arbitrary task_id values without authentication.

After: Added auth: Auth = Depends(auth_must) and included user_id in audit logs. Read endpoints (GET /share/info/{token}, GET /share/playback/{token}) intentionally remain public since they use the share token itself for authorization.

4. HIGH — auth_must Silent Bypass (server/app/component/auth.py)

Before: auth_must depended on oauth2_scheme which has auto_error=False. When no token was provided, the scheme returned None instead of raising 401. auth_must then passed None to jwt.decode(), which threw an opaque InvalidTokenError ("Could not validate credentials").

After: Added an explicit None check at the top of auth_must with a clear "Authentication required" error message. The type annotation was also updated to str | None to accurately reflect what oauth2_scheme returns with auto_error=False.

Testing Evidence (REQUIRED)

  • I have included human-verified testing evidence in this PR.
  • This PR includes frontend/UI changes, and I attached screenshot(s) or screen recording(s).
  • No frontend/UI changes in this PR.

Tests added (server/tests/test_auth.py):

  1. TestAuthMustNoneTokenHandling (3 tests):

    • Verifies auth_must accepts None type annotation
    • Verifies auth_must raises TokenException on None token
    • Verifies jwt.decode is never called with None input
  2. TestSnapshotEndpointAuthRequirements (5 tests):

    • Verifies ALL 5 snapshot CRUD endpoints (list, get, create, update, delete) have auth parameter
    • Prevents future regressions where auth is accidentally removed
  3. TestShareLinkAuthRequirement (2 tests):

    • Verifies create_share_link requires auth parameter
    • Verifies read endpoints (get_share_info, share_playback) correctly use token-based auth (not user auth)

What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

Contribution Guidelines Acknowledgement

…uth_must

- snapshot listing (GET /snapshots): add missing auth_must dependency and
  filter results by authenticated user's ID to prevent cross-user data leak
- snapshot get (GET /snapshots/{id}): add ownership check to prevent IDOR —
  authenticated users could previously read any user's snapshots by ID
- share link creation (POST /share): add auth_must dependency to prevent
  unauthenticated users from generating share tokens for arbitrary task IDs
- auth_must: add explicit None check before JWT decode — oauth2_scheme uses
  auto_error=False which returns None instead of 401 when no token is provided,
  causing auth_must to pass None to jwt.decode() and produce an opaque error
Copy link
Collaborator

@bytecii bytecii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general LGTM, left some comments.


def test_create_share_link_requires_auth_dependency(self):
"""POST /share must include auth_must as a dependency."""
from app.controller.chat.share_controller import create_share_link
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move these imports at the top

assert "auth" in param_names


class TestShareLinkAuthRequirement:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the class and just use def test_xxx

- Move share_controller imports to module top level
- Replace TestShareLinkAuthRequirement class with plain test functions
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.

Security: Unauthenticated snapshot listing, snapshot IDOR, unauthenticated share creation, auth_must bypass

2 participants

Comments