[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CCDDCC0KPM02DRBH2JFN82":3,"skill-same:01M6CCDDCC0KPM02DRBH2JFN82":244},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":32,"linkOnly":33,"markdownContent":34,"markdownBody":35,"frontMatter":36,"refName":41,"commitSha":42,"skillDirectory":43,"forksCount":14,"repositoryHtmlUrl":44,"repositoryHomepageUrl":45,"ownerHtmlUrl":46,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":47,"install":48,"resources":61},"01M6CCDDCC0KPM02DRBH2JFN82","convex-security-audit",null,"Deep security review patterns for authorization logic, data access boundaries, action isolation, rate limiting, and protecting sensitive operations","baisharg\u002FASTN","baisharg","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F204916775?v=4","01M6CC8XHZYVK2DR22FZ1BESCG","BAISH","01M6CC8XP400QZ9DXAWTC8EG3X",2,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-security-audit\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31],"convex","security","audit","security audit","convex security audit","review","security review","data","authorization","rate-limiting","access","operations","protection","2026-11-10 10:12:30",false,"---\nname: convex-security-audit\ndisplayName: Convex Security Audit\ndescription: Deep security review patterns for authorization logic, data access boundaries, action isolation, rate limiting, and protecting sensitive operations\nversion: 1.0.0\nauthor: Convex\ntags: [convex, security, audit, authorization, rate-limiting, protection]\n---\n\n# Convex Security Audit\n\nComprehensive security review patterns for Convex applications including authorization logic, data access boundaries, action isolation, rate limiting, and protecting sensitive operations.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fauth\u002Ffunctions-auth\n- Production Security: https:\u002F\u002Fdocs.convex.dev\u002Fproduction\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### Security Audit Areas\n\n1. **Authorization Logic** - Who can do what\n2. **Data Access Boundaries** - What data users can see\n3. **Action Isolation** - Protecting external API calls\n4. **Rate Limiting** - Preventing abuse\n5. **Sensitive Operations** - Protecting critical functions\n\n### Authorization Logic Audit\n\n#### Role-Based Access Control (RBAC)\n\n```typescript\n\u002F\u002F convex\u002Flib\u002Fauth.ts\nimport { QueryCtx, MutationCtx } from '.\u002F_generated\u002Fserver'\nimport { ConvexError } from 'convex\u002Fvalues'\nimport { Doc } from '.\u002F_generated\u002FdataModel'\n\ntype UserRole = 'user' | 'moderator' | 'admin' | 'superadmin'\n\nconst roleHierarchy: Record\u003CUserRole, number> = {\n  user: 0,\n  moderator: 1,\n  admin: 2,\n  superadmin: 3,\n}\n\nexport async function getUser(\n  ctx: QueryCtx | MutationCtx,\n): Promise\u003CDoc\u003C'users'> | null> {\n  const identity = await ctx.auth.getUserIdentity()\n  if (!identity) return null\n\n  return await ctx.db\n    .query('users')\n    .withIndex('by_tokenIdentifier', (q) =>\n      q.eq('tokenIdentifier', identity.tokenIdentifier),\n    )\n    .unique()\n}\n\nexport async function requireRole(\n  ctx: QueryCtx | MutationCtx,\n  minRole: UserRole,\n): Promise\u003CDoc\u003C'users'>> {\n  const user = await getUser(ctx)\n\n  if (!user) {\n    throw new ConvexError({\n      code: 'UNAUTHENTICATED',\n      message: 'Authentication required',\n    })\n  }\n\n  const userRoleLevel = roleHierarchy[user.role as UserRole] ?? 0\n  const requiredLevel = roleHierarchy[minRole]\n\n  if (userRoleLevel \u003C requiredLevel) {\n    throw new ConvexError({\n      code: 'FORBIDDEN',\n      message: `Role '${minRole}' or higher required`,\n    })\n  }\n\n  return user\n}\n\n\u002F\u002F Permission-based check\ntype Permission = 'read:users' | 'write:users' | 'delete:users' | 'admin:system'\n\nconst rolePermissions: Record\u003CUserRole, Permission[]> = {\n  user: ['read:users'],\n  moderator: ['read:users', 'write:users'],\n  admin: ['read:users', 'write:users', 'delete:users'],\n  superadmin: ['read:users', 'write:users', 'delete:users', 'admin:system'],\n}\n\nexport async function requirePermission(\n  ctx: QueryCtx | MutationCtx,\n  permission: Permission,\n): Promise\u003CDoc\u003C'users'>> {\n  const user = await getUser(ctx)\n\n  if (!user) {\n    throw new ConvexError({\n      code: 'UNAUTHENTICATED',\n      message: 'Authentication required',\n    })\n  }\n\n  const userRole = user.role as UserRole\n  const permissions = rolePermissions[userRole] ?? []\n\n  if (!permissions.includes(permission)) {\n    throw new ConvexError({\n      code: 'FORBIDDEN',\n      message: `Permission '${permission}' required`,\n    })\n  }\n\n  return user\n}\n```\n\n### Data Access Boundaries Audit\n\n```typescript\n\u002F\u002F convex\u002Fdata.ts\nimport { query, mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { getUser, requireRole } from '.\u002Flib\u002Fauth'\nimport { ConvexError } from 'convex\u002Fvalues'\n\n\u002F\u002F Audit: Users can only see their own data\nexport const getMyData = query({\n  args: {},\n  returns: v.array(\n    v.object({\n      _id: v.id('userData'),\n      content: v.string(),\n    }),\n  ),\n  handler: async (ctx) => {\n    const user = await getUser(ctx)\n    if (!user) return []\n\n    \u002F\u002F SECURITY: Filter by userId\n    return await ctx.db\n      .query('userData')\n      .withIndex('by_user', (q) => q.eq('userId', user._id))\n      .collect()\n  },\n})\n\n\u002F\u002F Audit: Verify ownership before returning sensitive data\nexport const getSensitiveItem = query({\n  args: { itemId: v.id('sensitiveItems') },\n  returns: v.union(\n    v.object({\n      _id: v.id('sensitiveItems'),\n      secret: v.string(),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args) => {\n    const user = await getUser(ctx)\n    if (!user) return null\n\n    const item = await ctx.db.get(args.itemId)\n\n    \u002F\u002F SECURITY: Verify ownership\n    if (!item || item.ownerId !== user._id) {\n      return null \u002F\u002F Don't reveal if item exists\n    }\n\n    return item\n  },\n})\n\n\u002F\u002F Audit: Shared resources with access list\nexport const getSharedDocument = query({\n  args: { docId: v.id('documents') },\n  returns: v.union(\n    v.object({\n      _id: v.id('documents'),\n      content: v.string(),\n      accessLevel: v.string(),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args) => {\n    const user = await getUser(ctx)\n    const doc = await ctx.db.get(args.docId)\n\n    if (!doc) return null\n\n    \u002F\u002F Public documents\n    if (doc.visibility === 'public') {\n      return { ...doc, accessLevel: 'public' }\n    }\n\n    \u002F\u002F Must be authenticated for non-public\n    if (!user) return null\n\n    \u002F\u002F Owner has full access\n    if (doc.ownerId === user._id) {\n      return { ...doc, accessLevel: 'owner' }\n    }\n\n    \u002F\u002F Check shared access\n    const access = await ctx.db\n      .query('documentAccess')\n      .withIndex('by_doc_and_user', (q) =>\n        q.eq('documentId', args.docId).eq('userId', user._id),\n      )\n      .unique()\n\n    if (!access) return null\n\n    return { ...doc, accessLevel: access.level }\n  },\n})\n```\n\n### Action Isolation Audit\n\n```typescript\n\u002F\u002F convex\u002Factions.ts\n'use node'\n\nimport { action, internalAction } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { api, internal } from '.\u002F_generated\u002Fapi'\nimport { ConvexError } from 'convex\u002Fvalues'\n\n\u002F\u002F SECURITY: Never expose API keys in responses\nexport const callExternalAPI = action({\n  args: { query: v.string() },\n  returns: v.object({ result: v.string() }),\n  handler: async (ctx, args) => {\n    \u002F\u002F Verify user is authenticated\n    const identity = await ctx.auth.getUserIdentity()\n    if (!identity) {\n      throw new ConvexError('Authentication required')\n    }\n\n    \u002F\u002F Get API key from environment (not hardcoded)\n    const apiKey = process.env.EXTERNAL_API_KEY\n    if (!apiKey) {\n      throw new Error('API key not configured')\n    }\n\n    \u002F\u002F Log usage for audit trail\n    await ctx.runMutation(internal.audit.logAPICall, {\n      userId: identity.tokenIdentifier,\n      endpoint: 'external-api',\n      timestamp: Date.now(),\n    })\n\n    const response = await fetch('https:\u002F\u002Fapi.example.com\u002Fquery', {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${apiKey}`,\n        'Content-Type': 'application\u002Fjson',\n      },\n      body: JSON.stringify({ query: args.query }),\n    })\n\n    if (!response.ok) {\n      \u002F\u002F Don't expose external API error details\n      throw new ConvexError('External service unavailable')\n    }\n\n    const data = await response.json()\n\n    \u002F\u002F Sanitize response before returning\n    return { result: sanitizeResponse(data) }\n  },\n})\n\n\u002F\u002F Internal action - not exposed to clients\nexport const _processPayment = internalAction({\n  args: {\n    userId: v.id('users'),\n    amount: v.number(),\n    paymentMethodId: v.string(),\n  },\n  returns: v.object({\n    success: v.boolean(),\n    transactionId: v.optional(v.string()),\n  }),\n  handler: async (ctx, args) => {\n    const stripeKey = process.env.STRIPE_SECRET_KEY\n\n    \u002F\u002F Process payment with Stripe\n    \u002F\u002F This should NEVER be exposed as a public action\n\n    return { success: true, transactionId: 'txn_xxx' }\n  },\n})\n```\n\n### Rate Limiting Audit\n\n```typescript\n\u002F\u002F convex\u002FrateLimit.ts\nimport { mutation, query } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { ConvexError } from 'convex\u002Fvalues'\n\nconst RATE_LIMITS = {\n  message: { requests: 10, windowMs: 60000 }, \u002F\u002F 10 per minute\n  upload: { requests: 5, windowMs: 300000 }, \u002F\u002F 5 per 5 minutes\n  api: { requests: 100, windowMs: 3600000 }, \u002F\u002F 100 per hour\n}\n\nexport const checkRateLimit = mutation({\n  args: {\n    userId: v.string(),\n    action: v.union(\n      v.literal('message'),\n      v.literal('upload'),\n      v.literal('api'),\n    ),\n  },\n  returns: v.object({\n    allowed: v.boolean(),\n    retryAfter: v.optional(v.number()),\n  }),\n  handler: async (ctx, args) => {\n    const limit = RATE_LIMITS[args.action]\n    const now = Date.now()\n    const windowStart = now - limit.windowMs\n\n    \u002F\u002F Count requests in window\n    const requests = await ctx.db\n      .query('rateLimits')\n      .withIndex('by_user_and_action', (q) =>\n        q.eq('userId', args.userId).eq('action', args.action),\n      )\n      .filter((q) => q.gt(q.field('timestamp'), windowStart))\n      .collect()\n\n    if (requests.length >= limit.requests) {\n      const oldestRequest = requests[0]\n      const retryAfter = oldestRequest.timestamp + limit.windowMs - now\n\n      return { allowed: false, retryAfter }\n    }\n\n    \u002F\u002F Record this request\n    await ctx.db.insert('rateLimits', {\n      userId: args.userId,\n      action: args.action,\n      timestamp: now,\n    })\n\n    return { allowed: true }\n  },\n})\n\n\u002F\u002F Use in mutations\nexport const sendMessage = mutation({\n  args: { content: v.string() },\n  returns: v.id('messages'),\n  handler: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity()\n    if (!identity) throw new ConvexError('Authentication required')\n\n    \u002F\u002F Check rate limit\n    const rateCheck = await checkRateLimit(ctx, {\n      userId: identity.tokenIdentifier,\n      action: 'message',\n    })\n\n    if (!rateCheck.allowed) {\n      throw new ConvexError({\n        code: 'RATE_LIMITED',\n        message: `Too many requests. Try again in ${Math.ceil(rateCheck.retryAfter! \u002F 1000)} seconds`,\n      })\n    }\n\n    return await ctx.db.insert('messages', {\n      content: args.content,\n      authorId: identity.tokenIdentifier,\n      createdAt: Date.now(),\n    })\n  },\n})\n```\n\n### Sensitive Operations Protection\n\n```typescript\n\u002F\u002F convex\u002Fadmin.ts\nimport { mutation, internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { requireRole, requirePermission } from '.\u002Flib\u002Fauth'\nimport { internal } from '.\u002F_generated\u002Fapi'\n\n\u002F\u002F Two-factor confirmation for dangerous operations\nexport const deleteAllUserData = mutation({\n  args: {\n    userId: v.id('users'),\n    confirmationCode: v.string(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Require superadmin\n    const admin = await requireRole(ctx, 'superadmin')\n\n    \u002F\u002F Verify confirmation code\n    const confirmation = await ctx.db\n      .query('confirmations')\n      .withIndex('by_admin_and_code', (q) =>\n        q.eq('adminId', admin._id).eq('code', args.confirmationCode),\n      )\n      .filter((q) => q.gt(q.field('expiresAt'), Date.now()))\n      .unique()\n\n    if (!confirmation || confirmation.action !== 'delete_user_data') {\n      throw new ConvexError('Invalid or expired confirmation code')\n    }\n\n    \u002F\u002F Delete confirmation to prevent reuse\n    await ctx.db.delete(confirmation._id)\n\n    \u002F\u002F Schedule deletion (don't do it inline)\n    await ctx.scheduler.runAfter(0, internal.admin._performDeletion, {\n      userId: args.userId,\n      requestedBy: admin._id,\n    })\n\n    \u002F\u002F Audit log\n    await ctx.db.insert('auditLogs', {\n      action: 'delete_user_data',\n      targetUserId: args.userId,\n      performedBy: admin._id,\n      timestamp: Date.now(),\n    })\n\n    return null\n  },\n})\n\n\u002F\u002F Generate confirmation code for sensitive action\nexport const requestDeletionConfirmation = mutation({\n  args: { userId: v.id('users') },\n  returns: v.string(),\n  handler: async (ctx, args) => {\n    const admin = await requireRole(ctx, 'superadmin')\n\n    const code = generateSecureCode()\n\n    await ctx.db.insert('confirmations', {\n      adminId: admin._id,\n      code,\n      action: 'delete_user_data',\n      targetUserId: args.userId,\n      expiresAt: Date.now() + 5 * 60 * 1000, \u002F\u002F 5 minutes\n    })\n\n    \u002F\u002F In production, send code via secure channel (email, SMS)\n    return code\n  },\n})\n```\n\n## Examples\n\n### Complete Audit Trail System\n\n```typescript\n\u002F\u002F convex\u002Faudit.ts\nimport { mutation, query, internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { getUser, requireRole } from '.\u002Flib\u002Fauth'\n\nconst auditEventValidator = v.object({\n  _id: v.id('auditLogs'),\n  _creationTime: v.number(),\n  action: v.string(),\n  userId: v.optional(v.string()),\n  resourceType: v.string(),\n  resourceId: v.string(),\n  details: v.optional(v.any()),\n  ipAddress: v.optional(v.string()),\n  timestamp: v.number(),\n})\n\n\u002F\u002F Internal: Log audit event\nexport const logEvent = internalMutation({\n  args: {\n    action: v.string(),\n    userId: v.optional(v.string()),\n    resourceType: v.string(),\n    resourceId: v.string(),\n    details: v.optional(v.any()),\n  },\n  returns: v.id('auditLogs'),\n  handler: async (ctx, args) => {\n    return await ctx.db.insert('auditLogs', {\n      ...args,\n      timestamp: Date.now(),\n    })\n  },\n})\n\n\u002F\u002F Admin: View audit logs\nexport const getAuditLogs = query({\n  args: {\n    resourceType: v.optional(v.string()),\n    userId: v.optional(v.string()),\n    limit: v.optional(v.number()),\n  },\n  returns: v.array(auditEventValidator),\n  handler: async (ctx, args) => {\n    await requireRole(ctx, 'admin')\n\n    let query = ctx.db.query('auditLogs')\n\n    if (args.resourceType) {\n      query = query.withIndex('by_resource_type', (q) =>\n        q.eq('resourceType', args.resourceType),\n      )\n    }\n\n    return await query.order('desc').take(args.limit ?? 100)\n  },\n})\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Implement defense in depth (multiple security layers)\n- Log all sensitive operations for audit trails\n- Use confirmation codes for destructive actions\n- Rate limit all user-facing endpoints\n- Never expose internal API keys or errors\n- Review access patterns regularly\n\n## Common Pitfalls\n\n1. **Single point of failure** - Implement multiple auth checks\n2. **Missing audit logs** - Log all sensitive operations\n3. **Trusting client data** - Always validate server-side\n4. **Exposing error details** - Sanitize error messages\n5. **No rate limiting** - Always implement rate limits\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Functions Auth: https:\u002F\u002Fdocs.convex.dev\u002Fauth\u002Ffunctions-auth\n- Production Security: https:\u002F\u002Fdocs.convex.dev\u002Fproduction\n","# Convex Security Audit\n\nComprehensive security review patterns for Convex applications including authorization logic, data access boundaries, action isolation, rate limiting, and protecting sensitive operations.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fauth\u002Ffunctions-auth\n- Production Security: https:\u002F\u002Fdocs.convex.dev\u002Fproduction\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### Security Audit Areas\n\n1. **Authorization Logic** - Who can do what\n2. **Data Access Boundaries** - What data users can see\n3. **Action Isolation** - Protecting external API calls\n4. **Rate Limiting** - Preventing abuse\n5. **Sensitive Operations** - Protecting critical functions\n\n### Authorization Logic Audit\n\n#### Role-Based Access Control (RBAC)\n\n```typescript\n\u002F\u002F convex\u002Flib\u002Fauth.ts\nimport { QueryCtx, MutationCtx } from '.\u002F_generated\u002Fserver'\nimport { ConvexError } from 'convex\u002Fvalues'\nimport { Doc } from '.\u002F_generated\u002FdataModel'\n\ntype UserRole = 'user' | 'moderator' | 'admin' | 'superadmin'\n\nconst roleHierarchy: Record\u003CUserRole, number> = {\n  user: 0,\n  moderator: 1,\n  admin: 2,\n  superadmin: 3,\n}\n\nexport async function getUser(\n  ctx: QueryCtx | MutationCtx,\n): Promise\u003CDoc\u003C'users'> | null> {\n  const identity = await ctx.auth.getUserIdentity()\n  if (!identity) return null\n\n  return await ctx.db\n    .query('users')\n    .withIndex('by_tokenIdentifier', (q) =>\n      q.eq('tokenIdentifier', identity.tokenIdentifier),\n    )\n    .unique()\n}\n\nexport async function requireRole(\n  ctx: QueryCtx | MutationCtx,\n  minRole: UserRole,\n): Promise\u003CDoc\u003C'users'>> {\n  const user = await getUser(ctx)\n\n  if (!user) {\n    throw new ConvexError({\n      code: 'UNAUTHENTICATED',\n      message: 'Authentication required',\n    })\n  }\n\n  const userRoleLevel = roleHierarchy[user.role as UserRole] ?? 0\n  const requiredLevel = roleHierarchy[minRole]\n\n  if (userRoleLevel \u003C requiredLevel) {\n    throw new ConvexError({\n      code: 'FORBIDDEN',\n      message: `Role '${minRole}' or higher required`,\n    })\n  }\n\n  return user\n}\n\n\u002F\u002F Permission-based check\ntype Permission = 'read:users' | 'write:users' | 'delete:users' | 'admin:system'\n\nconst rolePermissions: Record\u003CUserRole, Permission[]> = {\n  user: ['read:users'],\n  moderator: ['read:users', 'write:users'],\n  admin: ['read:users', 'write:users', 'delete:users'],\n  superadmin: ['read:users', 'write:users', 'delete:users', 'admin:system'],\n}\n\nexport async function requirePermission(\n  ctx: QueryCtx | MutationCtx,\n  permission: Permission,\n): Promise\u003CDoc\u003C'users'>> {\n  const user = await getUser(ctx)\n\n  if (!user) {\n    throw new ConvexError({\n      code: 'UNAUTHENTICATED',\n      message: 'Authentication required',\n    })\n  }\n\n  const userRole = user.role as UserRole\n  const permissions = rolePermissions[userRole] ?? []\n\n  if (!permissions.includes(permission)) {\n    throw new ConvexError({\n      code: 'FORBIDDEN',\n      message: `Permission '${permission}' required`,\n    })\n  }\n\n  return user\n}\n```\n\n### Data Access Boundaries Audit\n\n```typescript\n\u002F\u002F convex\u002Fdata.ts\nimport { query, mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { getUser, requireRole } from '.\u002Flib\u002Fauth'\nimport { ConvexError } from 'convex\u002Fvalues'\n\n\u002F\u002F Audit: Users can only see their own data\nexport const getMyData = query({\n  args: {},\n  returns: v.array(\n    v.object({\n      _id: v.id('userData'),\n      content: v.string(),\n    }),\n  ),\n  handler: async (ctx) => {\n    const user = await getUser(ctx)\n    if (!user) return []\n\n    \u002F\u002F SECURITY: Filter by userId\n    return await ctx.db\n      .query('userData')\n      .withIndex('by_user', (q) => q.eq('userId', user._id))\n      .collect()\n  },\n})\n\n\u002F\u002F Audit: Verify ownership before returning sensitive data\nexport const getSensitiveItem = query({\n  args: { itemId: v.id('sensitiveItems') },\n  returns: v.union(\n    v.object({\n      _id: v.id('sensitiveItems'),\n      secret: v.string(),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args) => {\n    const user = await getUser(ctx)\n    if (!user) return null\n\n    const item = await ctx.db.get(args.itemId)\n\n    \u002F\u002F SECURITY: Verify ownership\n    if (!item || item.ownerId !== user._id) {\n      return null \u002F\u002F Don't reveal if item exists\n    }\n\n    return item\n  },\n})\n\n\u002F\u002F Audit: Shared resources with access list\nexport const getSharedDocument = query({\n  args: { docId: v.id('documents') },\n  returns: v.union(\n    v.object({\n      _id: v.id('documents'),\n      content: v.string(),\n      accessLevel: v.string(),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args) => {\n    const user = await getUser(ctx)\n    const doc = await ctx.db.get(args.docId)\n\n    if (!doc) return null\n\n    \u002F\u002F Public documents\n    if (doc.visibility === 'public') {\n      return { ...doc, accessLevel: 'public' }\n    }\n\n    \u002F\u002F Must be authenticated for non-public\n    if (!user) return null\n\n    \u002F\u002F Owner has full access\n    if (doc.ownerId === user._id) {\n      return { ...doc, accessLevel: 'owner' }\n    }\n\n    \u002F\u002F Check shared access\n    const access = await ctx.db\n      .query('documentAccess')\n      .withIndex('by_doc_and_user', (q) =>\n        q.eq('documentId', args.docId).eq('userId', user._id),\n      )\n      .unique()\n\n    if (!access) return null\n\n    return { ...doc, accessLevel: access.level }\n  },\n})\n```\n\n### Action Isolation Audit\n\n```typescript\n\u002F\u002F convex\u002Factions.ts\n'use node'\n\nimport { action, internalAction } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { api, internal } from '.\u002F_generated\u002Fapi'\nimport { ConvexError } from 'convex\u002Fvalues'\n\n\u002F\u002F SECURITY: Never expose API keys in responses\nexport const callExternalAPI = action({\n  args: { query: v.string() },\n  returns: v.object({ result: v.string() }),\n  handler: async (ctx, args) => {\n    \u002F\u002F Verify user is authenticated\n    const identity = await ctx.auth.getUserIdentity()\n    if (!identity) {\n      throw new ConvexError('Authentication required')\n    }\n\n    \u002F\u002F Get API key from environment (not hardcoded)\n    const apiKey = process.env.EXTERNAL_API_KEY\n    if (!apiKey) {\n      throw new Error('API key not configured')\n    }\n\n    \u002F\u002F Log usage for audit trail\n    await ctx.runMutation(internal.audit.logAPICall, {\n      userId: identity.tokenIdentifier,\n      endpoint: 'external-api',\n      timestamp: Date.now(),\n    })\n\n    const response = await fetch('https:\u002F\u002Fapi.example.com\u002Fquery', {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${apiKey}`,\n        'Content-Type': 'application\u002Fjson',\n      },\n      body: JSON.stringify({ query: args.query }),\n    })\n\n    if (!response.ok) {\n      \u002F\u002F Don't expose external API error details\n      throw new ConvexError('External service unavailable')\n    }\n\n    const data = await response.json()\n\n    \u002F\u002F Sanitize response before returning\n    return { result: sanitizeResponse(data) }\n  },\n})\n\n\u002F\u002F Internal action - not exposed to clients\nexport const _processPayment = internalAction({\n  args: {\n    userId: v.id('users'),\n    amount: v.number(),\n    paymentMethodId: v.string(),\n  },\n  returns: v.object({\n    success: v.boolean(),\n    transactionId: v.optional(v.string()),\n  }),\n  handler: async (ctx, args) => {\n    const stripeKey = process.env.STRIPE_SECRET_KEY\n\n    \u002F\u002F Process payment with Stripe\n    \u002F\u002F This should NEVER be exposed as a public action\n\n    return { success: true, transactionId: 'txn_xxx' }\n  },\n})\n```\n\n### Rate Limiting Audit\n\n```typescript\n\u002F\u002F convex\u002FrateLimit.ts\nimport { mutation, query } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { ConvexError } from 'convex\u002Fvalues'\n\nconst RATE_LIMITS = {\n  message: { requests: 10, windowMs: 60000 }, \u002F\u002F 10 per minute\n  upload: { requests: 5, windowMs: 300000 }, \u002F\u002F 5 per 5 minutes\n  api: { requests: 100, windowMs: 3600000 }, \u002F\u002F 100 per hour\n}\n\nexport const checkRateLimit = mutation({\n  args: {\n    userId: v.string(),\n    action: v.union(\n      v.literal('message'),\n      v.literal('upload'),\n      v.literal('api'),\n    ),\n  },\n  returns: v.object({\n    allowed: v.boolean(),\n    retryAfter: v.optional(v.number()),\n  }),\n  handler: async (ctx, args) => {\n    const limit = RATE_LIMITS[args.action]\n    const now = Date.now()\n    const windowStart = now - limit.windowMs\n\n    \u002F\u002F Count requests in window\n    const requests = await ctx.db\n      .query('rateLimits')\n      .withIndex('by_user_and_action', (q) =>\n        q.eq('userId', args.userId).eq('action', args.action),\n      )\n      .filter((q) => q.gt(q.field('timestamp'), windowStart))\n      .collect()\n\n    if (requests.length >= limit.requests) {\n      const oldestRequest = requests[0]\n      const retryAfter = oldestRequest.timestamp + limit.windowMs - now\n\n      return { allowed: false, retryAfter }\n    }\n\n    \u002F\u002F Record this request\n    await ctx.db.insert('rateLimits', {\n      userId: args.userId,\n      action: args.action,\n      timestamp: now,\n    })\n\n    return { allowed: true }\n  },\n})\n\n\u002F\u002F Use in mutations\nexport const sendMessage = mutation({\n  args: { content: v.string() },\n  returns: v.id('messages'),\n  handler: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity()\n    if (!identity) throw new ConvexError('Authentication required')\n\n    \u002F\u002F Check rate limit\n    const rateCheck = await checkRateLimit(ctx, {\n      userId: identity.tokenIdentifier,\n      action: 'message',\n    })\n\n    if (!rateCheck.allowed) {\n      throw new ConvexError({\n        code: 'RATE_LIMITED',\n        message: `Too many requests. Try again in ${Math.ceil(rateCheck.retryAfter! \u002F 1000)} seconds`,\n      })\n    }\n\n    return await ctx.db.insert('messages', {\n      content: args.content,\n      authorId: identity.tokenIdentifier,\n      createdAt: Date.now(),\n    })\n  },\n})\n```\n\n### Sensitive Operations Protection\n\n```typescript\n\u002F\u002F convex\u002Fadmin.ts\nimport { mutation, internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { requireRole, requirePermission } from '.\u002Flib\u002Fauth'\nimport { internal } from '.\u002F_generated\u002Fapi'\n\n\u002F\u002F Two-factor confirmation for dangerous operations\nexport const deleteAllUserData = mutation({\n  args: {\n    userId: v.id('users'),\n    confirmationCode: v.string(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Require superadmin\n    const admin = await requireRole(ctx, 'superadmin')\n\n    \u002F\u002F Verify confirmation code\n    const confirmation = await ctx.db\n      .query('confirmations')\n      .withIndex('by_admin_and_code', (q) =>\n        q.eq('adminId', admin._id).eq('code', args.confirmationCode),\n      )\n      .filter((q) => q.gt(q.field('expiresAt'), Date.now()))\n      .unique()\n\n    if (!confirmation || confirmation.action !== 'delete_user_data') {\n      throw new ConvexError('Invalid or expired confirmation code')\n    }\n\n    \u002F\u002F Delete confirmation to prevent reuse\n    await ctx.db.delete(confirmation._id)\n\n    \u002F\u002F Schedule deletion (don't do it inline)\n    await ctx.scheduler.runAfter(0, internal.admin._performDeletion, {\n      userId: args.userId,\n      requestedBy: admin._id,\n    })\n\n    \u002F\u002F Audit log\n    await ctx.db.insert('auditLogs', {\n      action: 'delete_user_data',\n      targetUserId: args.userId,\n      performedBy: admin._id,\n      timestamp: Date.now(),\n    })\n\n    return null\n  },\n})\n\n\u002F\u002F Generate confirmation code for sensitive action\nexport const requestDeletionConfirmation = mutation({\n  args: { userId: v.id('users') },\n  returns: v.string(),\n  handler: async (ctx, args) => {\n    const admin = await requireRole(ctx, 'superadmin')\n\n    const code = generateSecureCode()\n\n    await ctx.db.insert('confirmations', {\n      adminId: admin._id,\n      code,\n      action: 'delete_user_data',\n      targetUserId: args.userId,\n      expiresAt: Date.now() + 5 * 60 * 1000, \u002F\u002F 5 minutes\n    })\n\n    \u002F\u002F In production, send code via secure channel (email, SMS)\n    return code\n  },\n})\n```\n\n## Examples\n\n### Complete Audit Trail System\n\n```typescript\n\u002F\u002F convex\u002Faudit.ts\nimport { mutation, query, internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { getUser, requireRole } from '.\u002Flib\u002Fauth'\n\nconst auditEventValidator = v.object({\n  _id: v.id('auditLogs'),\n  _creationTime: v.number(),\n  action: v.string(),\n  userId: v.optional(v.string()),\n  resourceType: v.string(),\n  resourceId: v.string(),\n  details: v.optional(v.any()),\n  ipAddress: v.optional(v.string()),\n  timestamp: v.number(),\n})\n\n\u002F\u002F Internal: Log audit event\nexport const logEvent = internalMutation({\n  args: {\n    action: v.string(),\n    userId: v.optional(v.string()),\n    resourceType: v.string(),\n    resourceId: v.string(),\n    details: v.optional(v.any()),\n  },\n  returns: v.id('auditLogs'),\n  handler: async (ctx, args) => {\n    return await ctx.db.insert('auditLogs', {\n      ...args,\n      timestamp: Date.now(),\n    })\n  },\n})\n\n\u002F\u002F Admin: View audit logs\nexport const getAuditLogs = query({\n  args: {\n    resourceType: v.optional(v.string()),\n    userId: v.optional(v.string()),\n    limit: v.optional(v.number()),\n  },\n  returns: v.array(auditEventValidator),\n  handler: async (ctx, args) => {\n    await requireRole(ctx, 'admin')\n\n    let query = ctx.db.query('auditLogs')\n\n    if (args.resourceType) {\n      query = query.withIndex('by_resource_type', (q) =>\n        q.eq('resourceType', args.resourceType),\n      )\n    }\n\n    return await query.order('desc').take(args.limit ?? 100)\n  },\n})\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Implement defense in depth (multiple security layers)\n- Log all sensitive operations for audit trails\n- Use confirmation codes for destructive actions\n- Rate limit all user-facing endpoints\n- Never expose internal API keys or errors\n- Review access patterns regularly\n\n## Common Pitfalls\n\n1. **Single point of failure** - Implement multiple auth checks\n2. **Missing audit logs** - Log all sensitive operations\n3. **Trusting client data** - Always validate server-side\n4. **Exposing error details** - Sanitize error messages\n5. **No rate limiting** - Always implement rate limits\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Functions Auth: https:\u002F\u002Fdocs.convex.dev\u002Fauth\u002Ffunctions-auth\n- Production Security: https:\u002F\u002Fdocs.convex.dev\u002Fproduction\n",{"name":5,"tags":37,"author":38,"version":39,"description":7,"displayName":40},[19,20,21,27,28,31],"Convex","1.0.0","Convex Security Audit","main","83212e6d909b6a0fe9e93fd5a3ff1723300f5b5b",".claude\u002Fskills\u002Fconvex-security-audit","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN","https:\u002F\u002Fsafetytalent.org","https:\u002F\u002Fgithub.com\u002Fbaisharg","2026-11-04 07:01:33",{"source":8,"skillName":5,"commands":49,"prompt":59,"zipAvailable":60},[50,53,56],{"tool":51,"command":52},"npx","npx skills add baisharg\u002FASTN --skill convex-security-audit",{"tool":54,"command":55},"bunx","bunx skills add baisharg\u002FASTN --skill convex-security-audit",{"tool":57,"command":58},"pnpm","pnpm dlx skills add baisharg\u002FASTN --skill convex-security-audit","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-security-audit\u002FSKILL.md",true,[62,68,72,78,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240],{"path":63,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":66,"sourceHtmlUrl":67},"agentmemory-evaluation.md","REFERENCE","参考文档",3509,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fagentmemory-evaluation.md",{"path":69,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":70,"sourceHtmlUrl":71},"anthropic-skills-blog-evaluation.md",5255,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fanthropic-skills-blog-evaluation.md",{"path":73,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":76,"sourceHtmlUrl":77},"architecture\u002Fauthor-delivery.excalidraw","OTHER","其他",7977,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Fauthor-delivery.excalidraw",{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"architecture\u002Fauthor-delivery.png","ASSET","资产文件",79677,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Fauthor-delivery.png",{"path":85,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":86,"sourceHtmlUrl":87},"architecture\u002Fpr-metadata.excalidraw",9023,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Fpr-metadata.excalidraw",{"path":89,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":90,"sourceHtmlUrl":91},"architecture\u002Fpr-metadata.png",163309,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Fpr-metadata.png",{"path":93,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":94,"sourceHtmlUrl":95},"architecture\u002Frea-integration.excalidraw",13921,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Frea-integration.excalidraw",{"path":97,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":98,"sourceHtmlUrl":99},"architecture\u002Frea-integration.png",123564,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Frea-integration.png",{"path":101,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":102,"sourceHtmlUrl":103},"architecture\u002Freact-devtools-routing.excalidraw",16777,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freact-devtools-routing.excalidraw",{"path":105,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":106,"sourceHtmlUrl":107},"architecture\u002Freact-devtools-routing.png",140220,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freact-devtools-routing.png",{"path":109,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":110,"sourceHtmlUrl":111},"architecture\u002Freview-delta-after.excalidraw",2697,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freview-delta-after.excalidraw",{"path":113,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":114,"sourceHtmlUrl":115},"architecture\u002Freview-delta-after.png",33256,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freview-delta-after.png",{"path":117,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":118,"sourceHtmlUrl":119},"architecture\u002Freview-delta-before.excalidraw",2078,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freview-delta-before.excalidraw",{"path":121,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":122,"sourceHtmlUrl":123},"architecture\u002Freview-delta-before.png",29830,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freview-delta-before.png",{"path":125,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":126,"sourceHtmlUrl":127},"architecture\u002Freview-repair.excalidraw",9361,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freview-repair.excalidraw",{"path":129,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":130,"sourceHtmlUrl":131},"architecture\u002Freview-repair.png",91351,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freview-repair.png",{"path":133,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":134,"sourceHtmlUrl":135},"architecture\u002Freview-specialists.excalidraw",20635,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freview-specialists.excalidraw",{"path":137,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":138,"sourceHtmlUrl":139},"architecture\u002Freview-specialists.png",139234,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Freview-specialists.png",{"path":141,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":142,"sourceHtmlUrl":143},"architecture\u002Fsubscription-quota-guard.excalidraw",16589,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Fsubscription-quota-guard.excalidraw",{"path":145,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":146,"sourceHtmlUrl":147},"architecture\u002Fsubscription-quota-guard.png",107358,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Fsubscription-quota-guard.png",{"path":149,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":150,"sourceHtmlUrl":151},"architecture\u002Ft3-harness-watcher.excalidraw",15023,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Ft3-harness-watcher.excalidraw",{"path":153,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":154,"sourceHtmlUrl":155},"architecture\u002Ft3-harness-watcher.png",217396,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Ft3-harness-watcher.png",{"path":157,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":158,"sourceHtmlUrl":159},"architecture\u002Ft3-native-routing.excalidraw",16702,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Ft3-native-routing.excalidraw",{"path":161,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":162,"sourceHtmlUrl":163},"architecture\u002Ft3-native-routing.png",88757,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Farchitecture\u002Ft3-native-routing.png",{"path":165,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":166,"sourceHtmlUrl":167},"bench-2026-07-08.md",6196,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fbench-2026-07-08.md",{"path":169,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":170,"sourceHtmlUrl":171},"claude-code-best-practice-repo-evaluation.md",3911,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fclaude-code-best-practice-repo-evaluation.md",{"path":173,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":174,"sourceHtmlUrl":175},"cocoindex-code-evaluation.md",3644,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fcocoindex-code-evaluation.md",{"path":177,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":178,"sourceHtmlUrl":179},"CODEX-PRESETS.md",3439,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002FCODEX-PRESETS.md",{"path":181,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":182,"sourceHtmlUrl":183},"DEMOS.md",16980,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002FDEMOS.md",{"path":185,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":186,"sourceHtmlUrl":187},"FAQ.md",14198,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002FFAQ.md",{"path":189,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":190,"sourceHtmlUrl":191},"harness-audit-2026-07-07.md",68808,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fharness-audit-2026-07-07.md",{"path":193,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":194,"sourceHtmlUrl":195},"harness-orchestration-learnings.md",5779,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fharness-orchestration-learnings.md",{"path":197,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":198,"sourceHtmlUrl":199},"hayden-writing-harness-upgrade-2026-10-21.md",17307,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fhayden-writing-harness-upgrade-2026-10-21.md",{"path":201,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":202,"sourceHtmlUrl":203},"lightpanda-browser-evaluation.md",2616,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Flightpanda-browser-evaluation.md",{"path":205,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":206,"sourceHtmlUrl":207},"opus-5-5-playbook-evaluation.md",4485,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fopus-5-5-playbook-evaluation.md",{"path":209,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":210,"sourceHtmlUrl":211},"property-based-testing-evaluation.md",7692,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fproperty-based-testing-evaluation.md",{"path":213,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":214,"sourceHtmlUrl":215},"rfc\u002Fbrowser-daemon.md",7386,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Frfc\u002Fbrowser-daemon.md",{"path":217,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":218,"sourceHtmlUrl":219},"session-summary-2026-03-30.md",5673,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fsession-summary-2026-03-30.md",{"path":221,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":222,"sourceHtmlUrl":223},"spa-soak-testing-evaluation.md",10079,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fspa-soak-testing-evaluation.md",{"path":225,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":226,"sourceHtmlUrl":227},"subscription-only-usage.md",6804,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fsubscription-only-usage.md",{"path":229,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":230,"sourceHtmlUrl":231},"t3code-ui-pr-practices-2026-10-26.md",31924,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Ft3code-ui-pr-practices-2026-10-26.md",{"path":233,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":234,"sourceHtmlUrl":235},"tanstack-code-mode-evaluation.md",4940,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Ftanstack-code-mode-evaluation.md",{"path":237,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":238,"sourceHtmlUrl":239},"this-week-in-react-skills-harness-opportunities-2026-07-07.md",8306,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Fthis-week-in-react-skills-harness-opportunities-2026-07-07.md",{"path":241,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":242,"sourceHtmlUrl":243},"toon-format-evaluation.md",3407,"https:\u002F\u002Fgithub.com\u002Fmalinskibeniamin\u002Fskills\u002Fblob\u002Fmain\u002Fdocs\u002Ftoon-format-evaluation.md",[245,267,281,294,308,325],{"skillNo":246,"name":247,"nameZh":6,"description":248,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":249,"categories":250,"keywords":256,"publishedAt":32},"01M6CCE5DVER8DQWJDY51HAJF7","schema-builder","Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fschema-builder\u002FSKILL.md",[251],{"categoryNo":252,"categoryName":253,"taxonomyType":254,"taxonomyTypeDesc":255},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",[257,258,259,260,261,262,263,264,265,19,266],"schema builder","schema","builder","indexes","validation","use","table","database","design","generate",{"skillNo":268,"name":269,"nameZh":6,"description":270,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":271,"categories":272,"keywords":273,"publishedAt":32},"01M6CCE36AA57CG36R4VHKFRXV","integration-tanstack-start","PostHog integration for TanStack Start full-stack applications","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fposthog-integration-tanstack-start\u002FSKILL.md",[],[274,275,276,277,278,279,280],"integration","start","tanstack","integration tanstack start","posthog","applications","stack",{"skillNo":282,"name":283,"nameZh":6,"description":284,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":285,"categories":286,"keywords":287,"publishedAt":32},"01M6CCE0FK6N8XAB093X7TTQ4F","migration-helper","Plan and execute Convex schema migrations safely, including adding fields, creating tables, and data transformations. Use when schema changes affect existing data.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fmigration-helper\u002FSKILL.md",[],[288,289,290,26,291,292,262,258,293,19],"migration","migration helper","schema migrations safely","plan","execute","migrations",{"skillNo":295,"name":296,"nameZh":6,"description":297,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":298,"categories":299,"keywords":300,"publishedAt":32},"01M6CCDY3644G8WXXP0BDNMZPR","gsd:sprint","Autonomous phase execution with Codex validation. Runs plan\u002Fexecute\u002Fverify cycles in a bash loop until done or halted.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-sprint\u002FSKILL.md",[],[296,301,302,261,275,303,304,305,306,291,307,292],"resume","codex","execution","verify","Bash","loop","autonomous",{"skillNo":309,"name":310,"nameZh":6,"description":311,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":312,"categories":313,"keywords":314,"publishedAt":32},"01M6CCDVTZRTFWWGREMV6FF956","gsd:second-opinion","Get a second opinion from OpenAI Codex (GPT-5.2-codex) on code, architecture, or implementation decisions. Use when you want independent verification or an alternative perspective.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-second-opinion\u002FSKILL.md",[],[315,316,317,302,318,319,320,321,322,323,262,324],"gsd:second opinion","second opinion","question","architecture","code","verification","openai","gpt","decisions","implementation",{"skillNo":326,"name":327,"nameZh":6,"description":328,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":329,"categories":330,"keywords":331,"publishedAt":32},"01M6CCDSDPYYNEDERHP01N30PT","gsd:milestone-sprint","Run entire milestone autonomously with Codex validation. Auto-detects current milestone, executes all phases, and runs audit.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-milestone-sprint\u002FSKILL.md",[],[332,333,261,21,334,335,301,302],"gsd:milestone sprint","run","interactive","auto"]