Fix guide · high · convex_function_no_auth

Convex query/mutation returns data without authentication

What this rule means

A Convex function (extracted from your client bundle) returned a successful response when called without an auth header. Anonymous callers can read whatever this function returns.

Why it matters

Convex doesn't have Row-Level Security. The platform's contract is that every function enforces its own access control via auth.getUserIdentity(). Skipping the check on a single function makes that function publicly readable forever — and the function name is in your client bundle.

How to fix it

Add the auth check to the function:

// convex/messages.ts
import { query } from "./_generated/server";
import { v } from "convex/values";

export const list = query({
  args: { conversationId: v.id("conversations") },
  handler: async (ctx, { conversationId }) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Unauthenticated");

    // Verify the user is a member of this conversation:
    const membership = await ctx.db
      .query("conversation_members")
      .withIndex("by_user_conversation", q =>
        q.eq("userId", identity.subject).eq("conversationId", conversationId)
      )
      .unique();
    if (!membership) throw new Error("Forbidden");

    return await ctx.db
      .query("messages")
      .withIndex("by_conversation", q => q.eq("conversationId", conversationId))
      .collect();
  },
});

For mutations: identical pattern. Always derive the user ID from identity.subject rather than trusting a client-supplied authorId.

Full guide: /blog/convex-security.

Did vibecheck flag this on your app?

If you reached this page from a vibecheck inspection report, the redacted match in your scan output is the exact string we found in your bundle. After applying the fix above, run the inspection again — the finding should clear.

Run another inspection