From Convex
Audit and harden a Convex app's authorization: identity-from-arg impersonation, missing per-document ownership checks, public queries leaking PII/financial data by a client-supplied id, and writes into a parent/container the caller doesn't own — the single largest real-defect cluster measured against generated Convex backends (44 of 214). Runs a deterministic scan for the 4 shapes, then applies the canonical requireIdentity/requireOwner pattern, then verifies with tsc. TRIGGER on 'secure my app', 'audit auth', 'add login', 'who can access this data', or an explicit 'audit my authz'. NOT always-on. SKIP when there is no convex/ directory.
How this skill is triggered — by the user, by Claude, or both
Slash command
/convex:convex-authzThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A focused authz specialist, not a general reviewer: it finds and fixes the four shapes that account for the largest real-defect cluster measured against generated Convex backends (25 identity-from-arg + 13 missing-ownership-check + 6 PII-leak-by-argument = 44 of 214 confirmed defects, plus the parent-reference-on-write variant of the ownership shape that fixture measurement showed the 3-shape s...
A focused authz specialist, not a general reviewer: it finds and fixes the four shapes that account for the largest real-defect cluster measured against generated Convex backends (25 identity-from-arg + 13 missing-ownership-check + 6 PII-leak-by-argument = 44 of 214 confirmed defects, plus the parent-reference-on-write variant of the ownership shape that fixture measurement showed the 3-shape scan misses). It runs a deterministic scan first (objective, regex-based, mirrors the convex-backend-skill v1.7.9 lint advisory), then applies the canonical requireIdentity/requireOwner hardening pattern from convex-expert.md to every hit, then verifies with tsc. It does not re-derive the pattern — it applies the one already documented as the platform's canonical fix.
/add auth or the auth setup first, then re-run convex-authz to add per-user ownership checks.' Do not run steps 1-3 below against public functions on a foundationless app beyond this internalize-and-defer move. Only when the foundation exists (both auth.config.ts and a subject-keyed users table are present) do you proceed to inject requireIdentity/requireOwner in steps 1-3.query(/mutation( object whose args block declares userId/actorId/ownerId/authorId/accountId typed v.id(...), where the function's whole block (args + handler) has zero ctx.auth reference. Regex: /\b(userId|actorId|ownerId|authorId|accountId)\s*:\s*v\.id\(/ inside an args: { ... } block paired with an absent /\bctx\.auth\b/ anywhere in the enclosing (query|mutation)\(\s*\{ ... } block (word-boundary excludes internalQuery/internalMutation by construction).
(b) missing-ownership-check: a public query(/mutation( whose handler loads a document via ctx.db.get(args.<xId>) (an _id-typed arg) and then calls ctx.db.patch/ctx.db.delete/ctx.db.replace on that same id, or returns the doc's fields directly, with no comparison of any <doc>.<ownerField> against an identity value anywhere in the block (no ===/!== involving identity.subject or a ctx.auth derived value).
(c) PII-leaking public query: a public query( whose returns (or the raw doc it returns) includes a sensitive-looking field (email, revenue, ssn, password, token, auditLog, dashboard-shaped aggregate) and the query is parameterized by a client-supplied id with no ctx.auth check gating access to that id's own scope.
(d) parent-reference ownership on write: a public mutation( whose args include a v.id(...) of a parent/container table (projectId, boardId, teamId, orgId, listId, folderId, conversationId, accountId, ...) that the handler uses as a foreign key in a ctx.db.insert/ctx.db.patch — attaching or moving a child row into that container — without verifying the caller owns (or is a member of) the referenced parent doc. Creating a row inside someone else's container is the same defect as mutating their row: fixing WHO the caller is (shape a) does not fix WHERE they may write. After handling shapes a-c, re-audit every REMAINING v.id(...) arg in every public mutation for this shape — shape-a fixes routinely leave the parent id arg behind, still unchecked.
Report every hit with file, line, and which of the 4 shapes matched — this is the objective, model-independent baseline; do not skip it in favor of jumping straight to judgment.convex/model/auth.ts exporting requireIdentity(ctx) (throws 401 if ctx.auth.getUserIdentity() is null; returns the identity) and requireOwner(ctx, doc) (throws 404 if doc is null, throws 403 if doc.ownerId !== identity.subject, else returns doc). Rewrite each flagged function: replace the client-supplied identity arg with requireIdentity(ctx); wrap each _id-keyed read/mutate with requireOwner(ctx, await ctx.db.get(args.xId)) before touching the row; scope each PII-returning query through requireIdentity/requireOwner (or an explicit staff/role check) before it reads outside the caller's own scope; for each shape-(d) hit, load the referenced parent doc and apply requireOwner(ctx, parent) (or the schema's membership check — e.g. participantIds.includes(user._id) — when the container models members as an array) BEFORE inserting/patching the child row. When the schema keys ownership by a users row id rather than the raw subject, resolve the caller's users row first (via the subject-keyed index) and compare against user._id — comparing an Id<"users"> field to identity.subject never matches and silently breaks enforcement. Never widen scope — an internal/admin function that legitimately operates on an arbitrary user stays internalQuery/internalMutation, never public; leave it unflagged and unchanged.npx tsc --noEmit (or the project's typecheck script) after edits; a hardening pass that doesn't typecheck is not done. Then re-run the step-1 scan to confirm 0 remaining hits (the fixed shapes no longer match the regexes because ctx.auth now appears in-block and ownership comparisons now exist).claude plugin install convex@claude-plugins-officialGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.