PluginBench
Skill
Pass
Audit score 90

convex-authz

get-convex/agent-skills

Audit and harden Convex app authorization: detect identity spoofing, missing ownership checks, PII leaks, and unsafe parent writes.

What is convex-authz?

Scans Convex backends for four high-impact authorization defects: identity-from-arg impersonation, missing per-document ownership checks, public queries leaking sensitive data by client-supplied ID, and writes into parent containers the caller doesn't own. Applies canonical requireIdentity/requireOwner patterns, verifies with TypeScript, and reports concrete diffs. Use when securing an existing Convex app or auditing authorization logic.

  • Detects identity-from-arg impersonation via regex scan of public queries/mutations with userId/actorId/ownerId args but no ctx.auth checks
  • Finds missing per-document ownership checks where public functions read and mutate docs without comparing doc.ownerId to caller identity
  • Identifies PII-leaking public queries that return sensitive fields (email, revenue, ssn, password, token, auditLog) parameterized by client-supplied ID without auth gating
  • Catches parent-reference ownership violations where mutations insert/patch child rows into parent containers without verifying caller owns the parent
  • Applies canonical requireIdentity and requireOwner helper patterns from convex-expert.md to every hit
  • Verifies hardening with TypeScript typecheck and re-scans to confirm zero remaining hits

How to install convex-authz

npx skills add https://github.com/get-convex/agent-skills --skill convex-authz
Prerequisites
  • A Convex project with a convex/ directory present
  • (Recommended) An auth.config.ts with an auth provider and a users/identities table keyed to auth subject, to enable requireIdentity/requireOwner enforcement; if missing, the skill will convert flagged public functions to internalQuery/internalMutation instead and defer ownership checks until auth setup is complete
Claude Code
Cursor
Windsurf
Cline

How to use convex-authz

  1. 1.Run the skill and trigger it with phrases like 'secure my app', 'audit auth', 'audit authz', or 'who can access this data'
  2. 2.Review the scan report grouped by the four defect shapes (identity-from-arg, missing-ownership-check, PII-leak, parent-reference-on-write), each with file:line and exploitation risk
  3. 3.Examine the concrete diffs showing requireIdentity/requireOwner patterns applied to each flagged function
  4. 4.Run `npx tsc --noEmit` to verify the hardening passes TypeScript checks
  5. 5.Confirm the re-scan shows zero remaining hits in all four shapes

Use cases

Good for
  • Audit a Convex app before production launch to close authorization gaps that could allow data theft or impersonation
  • Harden an existing Convex backend after discovering an authorization defect, using the deterministic scan to find similar patterns
  • Review a Convex mutation that writes child rows into parent containers (projects, boards, teams) to ensure caller ownership of the parent is verified
  • Scan a Convex query returning user-sensitive fields (email, revenue, audit logs) to confirm access is gated by caller identity, not just client-supplied ID
  • Verify that a public query accepting a userId argument validates the caller's identity before returning that user's data
Who it's for
  • Convex backend developers securing an app before launch or after a security review
  • Security-conscious teams auditing authorization patterns in existing Convex codebases
  • Developers implementing multi-tenant or role-based access control in Convex
  • Teams migrating to Convex and need to validate authorization patterns match platform best practices

convex-authz FAQ

What if my Convex app has no auth.config.ts or users table yet?

The skill will not inject requireIdentity/requireOwner (which would be non-functional). Instead, it converts flagged public admin/privileged functions to internalQuery/internalMutation to remove public reachability, then instructs you to run auth setup first and re-run convex-authz to add per-user ownership checks.

Does this skill check all authorization issues or just these four shapes?

It focuses on the four highest-impact defect shapes measured against real Convex backends (44 of 214 confirmed defects). It does not perform a general code review; it applies deterministic, objective regex scans for these specific patterns, then applies the canonical hardening pattern from convex-expert.md.

Can I use my own authorization helper instead of requireIdentity/requireOwner?

The skill applies the canonical requireIdentity/requireOwner pattern from convex-expert.md verbatim. If you have custom helpers, you can manually adapt the diffs, but the skill itself does not invent new patterns.

What happens if a function is already using ctx.auth correctly?

The deterministic scan will not flag it — functions with ctx.auth references in-block are excluded from the identity-from-arg shape, and functions with ownership comparisons are excluded from the missing-ownership-check shape.

Do I need to re-run this skill after making changes?

Yes. After applying the hardening diffs and running `npx tsc --noEmit`, re-run the scan to confirm zero remaining hits in all four shapes. This verifies the fixes are complete and correct.

Full instructions (SKILL.md)

Source of truth, from get-convex/agent-skills.


name: convex-authz description: "Audit and harden a Convex app's authorization: identity-from-arg impersonation, missing per-document ownership checks, public queries leaking data by a client-supplied id, and writes into a parent/container the caller doesn't own. Scans for the 4 shapes, applies requireIdentity/requireOwner, verifies with tsc. TRIGGER on 'secure my app', 'audit auth/authz', 'who can access this data'. SKIP when there is no convex/ directory."

<!-- GENERATED from convex-agents content/capabilities/convex-authz.json — do not edit by hand. -->

Convex Authz Auditor/Hardener

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), 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.

Workflow

  1. MANDATORY FIRST STEP — check the auth foundation exists before injecting any ctx.auth enforcement: (1) is there an auth.config.ts with a provider? (2) is there a users/identities table keyed to the auth subject (tokenIdentifier/identity.subject)? If EITHER is missing, DO NOT add requireIdentity/requireOwner — on a foundationless app ctx.auth.getUserIdentity() always returns null (enforcement is non-functional: every call 401s, or worse, the check is bypassed/miscompared against a non-subject field like an email string) and a reviewer correctly flags that as a NEW authz defect, not a fix. Instead, on a foundationless app: (a) for privileged/admin operations, convert the public query/mutation to internalQuery/internalMutation (removes public reachability entirely — safe and foundation-free, no ctx.auth needed), and (b) tell the user: 'this app has no auth foundation; run /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.
  2. SCAN (deterministic, objective-first): for every convex/**/*.ts file (skip convex/_generated/ and .d.ts), grep for the four shapes: (a) identity-from-arg: a public 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.
  3. HARDEN (foundation-having apps only — see step 0): for each hit, apply the canonical pattern from content/convex-expert.md verbatim — do not invent a new helper. Add (if absent) 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.
  4. VERIFY: run 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).
  5. Report findings grouped by the 4 rule shapes with file:line, explain why each is exploitable (who could impersonate whom / read whose data), and show the concrete diff applied (or, on a foundationless app, the internalize-and-defer diff plus the auth-setup nudge) — never just describe the fix in prose.

Rules

  • MANDATORY FIRST STEP: before injecting requireIdentity/requireOwner, verify the auth foundation exists — an auth.config.ts with a provider AND a users/identities table keyed to the auth subject. If either is missing, do not add ctx.auth-based enforcement (it's non-functional or mismatched and creates a NEW authz defect); instead convert flagged public admin/privileged functions to internalQuery/internalMutation and tell the user to run auth setup first, then re-run convex-authz.
  • Scan objectively before judging — run the 4 deterministic greps first; don't skip straight to LLM judgment, and don't let a clean scan stop you from still eyeballing internal/admin exemptions.
  • Identity always comes from ctx.auth, never from a client-supplied argument — the one legitimate exception is an internalQuery/internalMutation/internalAction that is never exposed publicly.
  • Every read or mutate keyed by an _id argument must verify ownership server-side (requireOwner or an inlined equivalent comparison) before touching the row — being logged in is not the same as owning this row.
  • Any v.id(...) argument a public mutation uses as a foreign key when inserting or moving a row must have the referenced parent's ownership (or membership) verified against the caller first — creating a child row inside someone else's project/board/account is the same defect as mutating their row, and it survives an identity-from-arg fix unless checked separately.
  • Never leave a public query that returns PII/financial/audit data reachable by an unauthenticated or cross-account client-supplied id.
  • Reuse requireIdentity/requireOwner from content/convex-expert.md verbatim — do not fork a parallel helper or invent new error semantics.
  • Always verify with tsc after hardening; a fix that doesn't typecheck is not shipped.
  • This is a targeted authz pass, not a general code review — do not expand scope into performance/schema/validator findings; hand those to convex-reviewer.
  • SKIP entirely when there is no convex/ directory in the project.