Quick security audit checklist covering authentication, function exposure, argument validation, row-level access control, and environment variable handling
npx skills add https://github.com/waynesutton/convexskills --skill convex-security-check
A quick security audit checklist for Convex applications covering authentication, function exposure, argument validation, row-level access control, and environment variable handling.
Before implementing, do not assume; fetch the latest documentation:
Use this checklist to quickly audit your Convex application's security:
ctx.auth.getUserIdentity()query, mutation, action) reviewedinternalQuery, internalMutation, internalActionargs validatorsreturns validatorsv.any() used for sensitive data// convex/auth.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { ConvexError } from "convex/values";
// Helper to require authentication
async function requireAuth(ctx: QueryCtx | MutationCtx) {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new ConvexError("Authentication required");
}
return identity;
}
// Secure query pattern
export const getMyProfile = query({
args: {},
returns: v.union(v.object({
_id: v.id("users"),
name: v.string(),
email: v.string(),
}), v.null()),
handler: async (ctx) => {
const identity = await requireAuth(ctx);
return await ctx.db
.query("users")
.withIndex("by_tokenIdentifier", (q) =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
},
});
// PUBLIC - Exposed to clients (review carefully!)
export const listPublicPosts = query({
args: {},
returns: v.array(v.object({ /* ... */ })),
handler: async (ctx) => {
// Anyone can call this - intentionally public
return await ctx.db
.query("posts")
.withIndex("by_public", (q) => q.eq("isPublic", true))
.collect();
},
});
// INTERNAL - Only callable from other Convex functions
export const _updateUserCredits = internalMutation({
args: { userId: v.id("users"), amount: v.number() },
returns: v.null(),
handler: async (ctx, args) => {
// This cannot be called directly from clients
await ctx.db.patch(args.userId, {
credits: args.amount,
});
return null;
},
});
// GOOD: Strict validation
export const createPost = mutation({
args: {
title: v.string(),
content: v.string(),
category: v.union(
v.literal("tech"),
v.literal("news"),
v.literal("other")
),
},
returns: v.id("posts"),
handler: async (ctx, args) => {
const identity = await requireAuth(ctx);
return await ctx.db.insert("posts", {
...args,
authorId: identity.tokenIdentifier,
});
},
});
// BAD: Weak validation
export const createPostUnsafe = mutation({
args: {
data: v.any(), // DANGEROUS: Allows any data
},
returns: v.id("posts"),
handler: async (ctx, args) => {
return await ctx.db.insert("posts", args.data);
},
});
// Verify ownership before update
export const updateTask = mutation({
args: {
taskId: v.id("tasks"),
title: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
const identity = await requireAuth(ctx);
const task = await ctx.db.get(args.taskId);
// Check ownership
if (!task || task.userId !== identity.tokenIdentifier) {
throw new ConvexError("Not authorized to update this task");
}
await ctx.db.patch(args.taskId, { title: args.title });
return null;
},
});
// Verify ownership before delete
export const deleteTask = mutation({
args: { taskId: v.id("tasks") },
returns: v.null(),
handler: async (ctx, args) => {
const identity = await requireAuth(ctx);
const task = await ctx.db.get(args.taskId);
if (!task || task.userId !== identity.tokenIdentifier) {
throw new ConvexError("Not authorized to delete this task");
}
await ctx.db.delete(args.taskId);
return null;
},
});
// convex/actions.ts
"use node";
import { action } from "./_generated/server";
import { v } from "convex/values";
export const sendEmail = action({
args: {
to: v.string(),
subject: v.string(),
body: v.string(),
},
returns: v.object({ success: v.boolean() }),
handler: async (ctx, args) => {
// Access API key from environment
const apiKey = process.env.RESEND_API_KEY;
if (!apiKey) {
throw new Error("RESEND_API_KEY not configured");
}
const response = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "[email protected]",
to: args.to,
subject: args.subject,
html: args.body,
}),
});
return { success: response.ok };
},
});
// convex/secure.ts
import { query, mutation, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { ConvexError } from "convex/values";
// Authentication helper
async function getAuthenticatedUser(ctx: QueryCtx | MutationCtx) {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new ConvexError({
code: "UNAUTHENTICATED",
message: "You must be logged in",
});
}
const user = await ctx.db
.query("users")
.withIndex("by_tokenIdentifier", (q) =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (!user) {
throw new ConvexError({
code: "USER_NOT_FOUND",
message: "User profile not found",
});
}
return user;
}
// Check admin role
async function requireAdmin(ctx: QueryCtx | MutationCtx) {
const user = await getAuthenticatedUser(ctx);
if (user.role !== "admin") {
throw new ConvexError({
code: "FORBIDDEN",
message: "Admin access required",
});
}
return user;
}
// Public: List own tasks
export const listMyTasks = query({
args: {},
returns: v.array(v.object({
_id: v.id("tasks"),
title: v.string(),
completed: v.boolean(),
})),
handler: async (ctx) => {
const user = await getAuthenticatedUser(ctx);
return await ctx.db
.query("tasks")
.withIndex("by_user", (q) => q.eq("userId", user._id))
.collect();
},
});
// Admin only: List all users
export const listAllUsers = query({
args: {},
returns: v.array(v.object({
_id: v.id("users"),
name: v.string(),
role: v.string(),
})),
handler: async (ctx) => {
await requireAdmin(ctx);
return await ctx.db.query("users").collect();
},
});
// Internal: Update user role (never exposed)
export const _setUserRole = internalMutation({
args: {
userId: v.id("users"),
role: v.union(v.literal("user"), v.literal("admin")),
},
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.patch(args.userId, { role: args.role });
return null;
},
});
npx convex deploy unless explicitly instructedExpert in secure backend coding practices specializing in input validation, authentication, and API security. Use PROACTIVELY for backend security implementations or security code reviews.
This skill should be used when the user asks to "perform cloud penetration testing", "assess Azure or AWS or GCP security", "enumerate cloud resources", "exploit cloud misconfigurations", "test O365 security", "extract secrets from cloud environments", or "audit cloud infrastructure". It provides comprehensive techniques for security assessment across major cloud platforms.
You are a dependency security expert specializing in vulnerability scanning, license compliance, and supply chain security. Analyze project dependencies for known vulnerabilities, licensing issues, outdated packages, and provide actionable remediation strategies.
Comprehensive Flow Nexus platform management - authentication, sandboxes, app deployment, payments, and challenges
This skill should be used when the user asks to "escalate privileges on Linux", "find privesc vectors on Linux systems", "exploit sudo misconfigurations", "abuse SUID binaries", "exploit cron jobs for root access", "enumerate Linux systems for privilege escalation", or "gain root access from low-privilege shell". It provides comprehensive techniques for identifying and exploiting privilege escalation paths on Linux systems.
Expert malware analyst specializing in defensive malware research, threat intelligence, and incident response. Masters sandbox analysis, behavioral analysis, and malware family identification. Handles static/dynamic analysis, unpacking, and IOC extraction. Use PROACTIVELY for malware triage, threat hunting, incident response, or security research.
This skill should be used when the user asks to "use Metasploit for penetration testing", "exploit vulnerabilities with msfconsole", "create payloads with msfvenom", "perform post-exploitation", "use auxiliary modules for scanning", or "develop custom exploits". It provides comprehensive guidance for leveraging the Metasploit Framework in security assessments.
Expert in secure mobile coding practices specializing in input validation, WebView security, and mobile-specific security patterns. Use PROACTIVELY for mobile security implementations or mobile security code reviews.
Take waynesutton/convex-security-check from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.