Skip to main content

convex-security-audit

Deep security review patterns for authorization logic, data access boundaries, action isolation, rate limiting, and protecting sensitive operations

Ir para a instalação

Informações da origem

Repositório
waynesutton/convexskills
Última atividade na origem
3 de fevereiro de 2026 às 04:58
Idioma detectado do SKILL.md
inglês
Estrelas
404
Forks
32

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
4 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
convex-security-audit
displayName
Convex Security Audit
description
Deep security review patterns for authorization logic, data access boundaries, action isolation, rate limiting, and protecting sensitive operations
version
1.0.0
author
Convex
tags
["convex","security","audit","authorization","rate-limiting","protection"]
# Convex Security Audit Comprehensive security review patterns for Convex applications including authorization logic, data access boundaries, action isolation, rate limiting, and protecting sensitive operations. ## Documentation Sources Before implementing, do not assume; fetch the latest documentation: - Primary: https://docs.convex.dev/auth/functions-auth - Production Security: https://docs.convex.dev/production - For broader context: https://docs.convex.dev/llms.txt ## Instructions ### Security Audit Areas 1. **Authorization Logic** - Who can do what 2. **Data Access Boundaries** - What data users can see 3. **Action Isolation** - Protecting external API calls 4. **Rate Limiting** - Preventing abuse 5. **Sensitive Operations** - Protecting critical functions ### Authorization Logic Audit #### Role-Based Access Control (RBAC) ```typescript // convex/lib/auth.ts import { QueryCtx, MutationCtx } from "./_generated/server"; import { ConvexError } from "convex/values"; import { Doc } from "./_generated/dataModel"; type UserRole = "user" | "moderator" | "admin" | "superadmin"; const roleHierarchy: Record<UserRole, number> = { user: 0, moderator: 1, admin: 2, superadmin: 3, }; export async function getUser(ctx: QueryCtx | MutationCtx): Promise<Doc<"users"> | null> { const identity = await ctx.auth.getUserIdentity(); if (!identity) return null; return await ctx.db .query("users") .withIndex("by_tokenIdentifier", (q) => q.eq("tokenIdentifier", identity.tokenIdentifier) ) .unique(); } export async function requireRole( ctx: QueryCtx | MutationCtx, minRole: UserRole ): Promise<Doc<"users">> { const user = await getUser(ctx); if (!user) { throw new ConvexError({ code: "UNAUTHENTICATED", message: "Authentication required", }); } const userRoleLevel = roleHierarchy[user.role as UserRole] ?? 0; const requiredLevel = roleHierarchy[minRole]; if (userRoleLevel < requiredLevel) { throw new ConvexError({ code: "FORBIDDEN", message: `Role '${minRole}' or higher required`, }); } return user; } // Permission-based check type Permission = "read:users" | "write:users" | "delete:users" | "admin:system"; const rolePermissions: Record<UserRole, Permission[]> = { user: ["read:users"], moderator: ["read:users", "write:users"], admin: ["read:users", "write:users", "delete:users"], superadmin: ["read:users", "write:users", "delete:users", "admin:system"], }; export async function requirePermission( ctx: QueryCtx | MutationCtx, permission: Permission ): Promise<Doc<"users">> { const user = await getUser(ctx); if (!user) { throw new ConvexError({ code: "UNAUTHENTICATED", message: "Authentication required" }); } const userRole = user.role as UserRole; const permissions = rolePermissions[userRole] ?? []; if (!permissions.includes(permission)) { throw new ConvexError({ code: "FORBIDDEN", message: `Permission '${permission}' required`, }); } return user; } ``` ### Data Access Boundaries Audit ```typescript // convex/data.ts import { query, mutation } from "./_generated/server"; import { v } from "convex/values"; import { getUser, requireRole } from "./lib/auth"; import { ConvexError } from "convex/values"; // Audit: Users can only see their own data export const getMyData = query({ args: {}, returns: v.array(v.object({ _id: v.id("userData"), content: v.string(), })), handler: async (ctx) => { const user = await getUser(ctx); if (!user) return []; // SECURITY: Filter by userId return await ctx.db .query("userData") .withIndex("by_user", (q) => q.eq("userId", user._id)) .collect(); }, }); // Audit: Verify ownership before returning sensitive data export const getSensitiveItem = query({ args: { itemId: v.id("sensitiveItems") }, returns: v.union(v.object({ _id: v.id("sensitiveItems"), secret: v.string(), }), v.null()), handler: async (ctx, args) => { const user = await getUser(ctx); if (!user) return null; const item = await ctx.db.get(args.itemId); // SECURITY: Verify ownership if (!item || item.ownerId !== user._id) { return null; // Don't reveal if item exists } return item; }, }); // Audit: Shared resources with access list export const getSharedDocument = query({ args: { docId: v.id("documents") }, returns: v.union(v.object({ _id: v.id("documents"), content: v.string(), accessLevel: v.string(), }), v.null()), handler: async (ctx, args) => { const user = await getUser(ctx); const doc = await ctx.db.get(args.docId); if (!doc) return null; // Public documents if (doc.visibility === "public") { return { ...doc, accessLevel: "public" }; } // Must be authenticated for non-public if (!user) return null; // Owner has full access if (doc.ownerId === user._id) { return { ...doc, accessLevel: "owner" }; } // Check shared access const access = await ctx.db .query("documentAccess") .withIndex("by_doc_and_user", (q) => q.eq("documentId", args.docId).eq("userId", user._id) ) .unique(); if (!access) return null; return { ...doc, accessLevel: access.level }; }, }); ``` ### Action Isolation Audit ```typescript // convex/actions.ts "use node"; import { action, internalAction } from "./_generated/server"; import { v } from "convex/values"; import { api, internal } from "./_generated/api"; import { ConvexError } from "convex/values"; // SECURITY: Never expose API keys in responses export const callExternalAPI = action({ args: { query: v.string() }, returns: v.object({ result: v.string() }), handler: async (ctx, args) => { // Verify user is authenticated const identity = await ctx.auth.getUserIdentity(); if (!identity) { throw new ConvexError("Authentication required"); } // Get API key from environment (not hardcoded) const apiKey = process.env.EXTERNAL_API_KEY; if (!apiKey) { throw new Error("API key not configured"); } // Log usage for audit trail await ctx.runMutation(internal.audit.logAPICall, { userId: identity.tokenIdentifier, endpoint: "external-api", timestamp: Date.now(), }); const response = await fetch("https://api.example.com/query", { method: "POST", headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ query: args.query }), }); if (!response.ok) { // Don't expose external API error details throw new ConvexError("External service unavailable"); } const data = await response.json(); // Sanitize response before returning return { result: sanitizeResponse(data) }; }, }); // Internal action - not exposed to clients export const _processPayment = internalAction({ args: { userId: v.id("users"), amount: v.number(), paymentMethodId: v.string(), }, returns: v.object({ success: v.boolean(), transactionId: v.optional(v.string()) }), handler: async (ctx, args) => { const stripeKey = process.env.STRIPE_SECRET_KEY; // Process payment with Stripe // This should NEVER be exposed as a public action return { success: true, transactionId: "txn_xxx" }; }, }); ``` ### Rate Limiting Audit ```typescript // convex/rateLimit.ts import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; import { ConvexError } from "convex/values"; const RATE_LIMITS = { message: { requests: 10, windowMs: 60000 }, // 10 per minute upload: { requests: 5, windowMs: 300000 }, // 5 per 5 minutes api: { requests: 100, windowMs: 3600000 }, // 100 per hour }; export const checkRateLimit = mutation({ args: { userId: v.string(), action: v.union(v.literal("message"), v.literal("upload"), v.literal("api")), }, returns: v.object({ allowed: v.boolean(), retryAfter: v.optional(v.number()) }), handler: async (ctx, args) => { const limit = RATE_LIMITS[args.action]; const now = Date.now(); const windowStart = now - limit.windowMs; // Count requests in window const requests = await ctx.db .query("rateLimits") .withIndex("by_user_and_action", (q) => q.eq("userId", args.userId).eq("action", args.action) ) .filter((q) => q.gt(q.field("timestamp"), windowStart)) .collect(); if (requests.length >= limit.requests) { const oldestRequest = requests[0]; const retryAfter = oldestRequest.timestamp + limit.windowMs - now; return { allowed: false, retryAfter }; } // Record this request await ctx.db.insert("rateLimits", { userId: args.userId, action: args.action, timestamp: now, }); return { allowed: true }; }, }); // Use in mutations export const sendMessage = mutation({ args: { content: v.string() }, returns: v.id("messages"), handler: async (ctx, args) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) throw new ConvexError("Authentication required"); // Check rate limit const rateCheck = await checkRateLimit(ctx, { userId: identity.tokenIdentifier, action: "message", }); if (!rateCheck.allowed) { throw new ConvexError({ code: "RATE_LIMITED", message: `Too many requests. Try again in ${Math.ceil(rateCheck.retryAfter! / 1000)} seconds`, }); } return await ctx.db.insert("messages", { content: args.content, authorId: identity.tokenIdentifier, createdAt: Date.now(), }); }, }); ``` ### Sensitive Operations Protection ```typescript // convex/admin.ts import { mutation, internalMutation } from "./_generated/server"; import { v } from "convex/values"; import { requireRole, requirePermission } from "./lib/auth"; import { internal } from "./_generated/api"; // Two-factor confirmation for dangerous operations export const deleteAllUserData = mutation({ args: { userId: v.id("users"), confirmationCode: v.string(), }, returns: v.null(), handler: async (ctx, args) => { // Require superadmin const admin = await requireRole(ctx, "superadmin"); // Verify confirmation code const confirmation = await ctx.db .query("confirmations") .withIndex("by_admin_and_code", (q) =>
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub