Skip to content

Unauthenticated Cross-User AI Metadata Disclosure via Missing Access Check on /api/video/ai #1981

Description

@geo-chen

reported on 4 June 2026 https://github.com/CapSoftware/Cap/security/advisories/GHSA-5m2p-v3wg-3gm6 - no response:

Summary

The GET /api/video/ai endpoint retrieves AI-generated metadata (title, summary, chapters) for any video by ID without verifying whether the requesting user has permission to access that video. Any authenticated user can supply an arbitrary video ID and read the AI-generated content of another user's private recordings.

Details

The handler at apps/web/app/api/video/ai/route.ts (GET, lines 13-169) fetches the target video directly from the database using only the caller's authenticated session and the videoId query parameter:

const result = await db()
  .select()
  .from(videos)
  .where(eq(videos.id, videoId));

No ownership check and no call to the VideosPolicy canView policy are made before returning the video's metadata fields. The response body includes:

  • metadata.aiTitle
  • metadata.summary
  • metadata.chapters
  • metadata.aiGenerationStatus

The correct access-guarded path used by every other video-reading endpoint is getByIdForViewing, which runs the full canView policy (owner check, org/space membership, public flag, password, email restriction). This endpoint bypasses all of that.

PoC

Preconditions: User A has a private video (public=false) with AI metadata populated. User B has no membership in User A's org or space and cannot view the video at /s/{videoId}.

  1. Authenticate as User B and obtain a session token (cookie next-auth.session-token).

  2. Note the video ID of User A's private recording (vid001vidtest00 in the test below). In practice an attacker would enumerate IDs or obtain one via a notification link or a shared URL they intercept.

  3. Issue:

curl -s "http://<host>/api/video/ai?videoId=vid001vidtest00" \
  -H "Cookie: next-auth.session-token=<User B token>"
  1. Response (200 OK):
{
  "processing": false,
  "title": "Secret Report Title",
  "summary": "This is a confidential summary",
  "chapters": [],
  "aiGenerationStatus": "COMPLETE"
}

User B receives the AI-generated title and summary of a video they cannot open. The /s/vid001vidtest00 share page correctly shows "This video is private" to User B, confirming there is no legitimate access path.

The same endpoint will also attempt to enqueue AI generation on behalf of the requester when aiGenerationStatus is SKIPPED and transcriptionStatus is COMPLETE, consuming the video owner's AI credits without their knowledge.

Impact

Any authenticated user can read the AI-generated title, summary, and chapter list of any other user's private video recordings. These fields are derived from the audio transcript and can contain sensitive, confidential, or personally identifying information. The bug affects all self-hosted Cap deployments running the current codebase.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    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