Skip to content
Backend
Skill

/convex-security-audit

Deep security review of a Convex app: authorization model, data access paths per table, HTTP action exposure, rate limiting, file storage access, scheduled function trust, and a written findings report. Use before launch, after an incident, or when the user asks for a full audit

BOOST
From plugin
builder-skills
40619 skills
Install
$ npx -y skills add waynesutton/builder-skills --skill convex-security-audit --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/convex-security-audit

Context preview

The summary Claude sees to decide when to auto-load this skill.

Deep security review of a Convex app: authorization model, data access paths per table, HTTP action exposure, rate limiting, file storage access, scheduled function trust, and a written findings report. Use before launch, after an incident, or when the user asks for a full audit

SKILL.md

convex-security-audit.SKILL.md
name: convex-security-audit
description: Deep security review of a Convex app: authorization model, data access paths per table, HTTP action exposure, rate limiting, file storage access, scheduled function trust, and a written findings report. Use before launch, after an incident, or when the user asks for a full audit rather than a quick check.

Convex security audit

Produces a written findings report covering every public function, every table, and every entry point (HTTP routes, file storage, scheduler). The one rule: every exported `query`, `mutation`, and `action` is a public endpoint anyone on the internet can call with any arguments, so each one must prove who the caller is and what they may touch.

For a ten minute pass, use convex-security-check instead. This skill is the slow, complete version.

When to reach for this

  • Before a production launch or a major release
  • After an incident, a leaked key, or suspicious data access
  • When the codebase has grown past a handful of public functions and nobody has mapped who can call what
  • When adding multi tenant, admin, or billing features
  • When the user asks for "a full audit", "security review", or "check the whole backend"

Reference files

  • [references/authorization-patterns.md](references/authorization-patterns.md): open when fixing findings, it has the `getCurrentUser` helper, `customQuery` and `customMutation` wrappers for authed, admin, and tenant scoped access, ownership checks through indexes, and role checks.
  • [references/attack-surface.md](references/attack-surface.md): open during steps 4 through 7, it covers HTTP actions, storage URL leakage, scheduler trust, rate limiting with `@convex-dev/rate-limiter`, secrets in env, and client supplied IDs.
  • [references/audit-report-template.md](references/audit-report-template.md): open at step 8, it is the findings template and the per table data access matrix to fill in.

Audit procedure

Work through the steps in order. Record every finding as you go. Do not fix anything until the inventory is complete; fixing early hides the shape of the problem.

1. Inventory public functions

List every exported `query`, `mutation`, and `action`. These are the attack surface.

rg -n "export const \w+ = (query|mutation|action)\(" convex --glob '!**/_generated/**'

For each one write down: file, name, type, whether a client needs to call it, and whether it is costly (sends email, calls an LLM, creates records) and so needs a rate limit. Anything only called by other Convex functions, crons, or the scheduler should be `internalQuery`, `internalMutation`, or `internalAction`. Flag each of those as a finding.

2. Map auth per function

For every public function from step 1, answer: does it call `ctx.auth.getUserIdentity()` (or a helper that does) before reading or writing? Which functions are intentionally anonymous?

rg -l "= (query|mutation|action)\(" convex --glob '!**/_generated/**' \
  | xargs rg -L "getUserIdentity|getCurrentUser|authedQuery|authedMutation"

Files that print here contain public functions with no auth call anywhere in the file. Read each one. Anonymous is fine for a public blog list. It is a critical finding for anything that returns or writes user data.

Check that auth helpers throw or return early on a missing identity, and that role checks read the role from your `users` table, not from client arguments.

3. Map data access per table

For every table in `convex/schema.ts`, list which public functions read it, which write it, and what condition scopes the access (owner ID, org membership, role, public flag). Fill in the data access matrix from the report template.

Look for:

  • Reads with no scoping: `ctx.db.query("table").collect()` inside a public function
  • `ctx.db.get(args.someId)` followed by a return or patch with no ownership comparison
  • Ownership compared against a spoofable value (email, display name) instead of `user._id`
  • `.filter()` used for scoping instead of `.withIndex()`; this usually means the scoping was an afterthought
  • Return validators that leak fields: `v.any()`, or spreading a full document that carries `passwordHash`, `stripeCustomerId`, or internal flags
rg -n "v\.any\(\)" convex --glob '!**/_generated/**'
rg -n "ctx\.db\.(get|patch|delete|replace)\(args\." convex --glob '!**/_generated/**'

4. Review http.ts

Every route in `convex/http.ts` is reachable without a Convex client. For each route check: how is the caller verified (webhook signature, bearer token, `ctx.auth.getUserIdentity()`), is the body validated before use, and does it call only `internal.*` functions with data it has already verified?

rg -n "http\.route|httpAction\(" convex/http.ts

Webhook handlers that skip signature verification are critical. Routes that call `api.*` functions from inside an `httpAction` may be bypassing auth those functions assume they have.

5. Review file storage

Find where upload URLs are generated and where file URLs are returned.

rg -n "storage\.(generateUploadUrl|getUrl|delete)" convex --glob '!**/_generated/**'

`generateUploadUrl` in a public mutation with no auth lets anyone fill your storage. A query that returns `ctx.storage.getUrl(args.fileId)` for any ID the client passes is a data leak; the URL works for anyone who holds it.

6. Review scheduled and internal boundaries

Internal functions skip auth by design. The question is whether anything schedules them with unverified input.

rg -n "(runAfter|runAt|runMutation|runAction|interval|cron)\([^)]*\bapi\." convex --glob '!**/_generated/**'

Scheduling an `api.*` function is a finding: use `internal.*`. Then trace each `internal.*` call site back to the public function or route that triggered it and confirm the caller validated ownership before scheduling.

7. Review env and secrets

rg -n -i "(sk_live|sk_test|whsec_|AKIA[0-9A-Z]{16}|-----BEGIN|api[_-]?key\s*[:=]\
Read more
Ships withbuilder-skills

Agent skills for builders shipping Convex apps. By Wayne Sutton. Seventeen skills.

Get the whole plugin

Other skills on builder-skills.