S
Solo Kit
DocumentationComponentsPricingChangelogRoadmapFAQContact
LoginGet Started
DocumentationComponentsPricing
LoginGet Started
Welcome to Solo Kit DocumentationIntroductionTech StackRoadmapFAQGetting Started
Database OverviewSchema DesignDatabase QueriesDatabase MigrationsDatabase SeedingDatabase PerformanceDatabase SecurityBackup & RestoreDatabase MonitoringAdvanced Database Features
Database

Database Security

Database Security

Learn how to secure your Solo Kit database against common vulnerabilities and implement enterprise-grade security practices. This guide covers input validation, access control, and comprehensive security monitoring with Convex.

Security Overview

Solo Kit Security Architecture

Solo Kit implements defense-in-depth security with multiple layers:

  • Connection Security: Automatic TLS encryption with Convex
  • Authentication: BetterAuth with secure session management
  • Authorization: Role-based access control (RBAC) with function-level constraints
  • Input Validation: TypeScript type safety and Zod validation in Convex functions
  • Data Protection: Encryption at rest and in transit (handled by Convex)
  • Monitoring: Comprehensive security scanning and audit logging

Security Principles

Zero Trust Database Design:

  • Principle of Least Privilege: Minimal required permissions only
  • Defense in Depth: Multiple security layers working together
  • Secure by Default: Safe configurations out-of-the-box
  • Continuous Monitoring: Real-time security health checks

Input Validation & Type Safety

Convex Function Protection

Solo Kit uses Convex functions which provide automatic input validation:

// convex/users.ts
import { v } from 'convex/values';
import { query, mutation } from './_generated/server';

// SAFE: Convex validates all inputs automatically
export const getUserById = query({
  args: { id: v.id('users') },
  handler: async (ctx, args) => {
    // Input is automatically validated by Convex
    const user = await ctx.db.get(args.id);
    return user;
  },
});

// SAFE: All Convex queries use validated arguments
export const getUserByEmail = query({
  args: { email: v.string() },
  handler: async (ctx, args) => {
    // Safe validated query
    const user = await ctx.db
      .query('users')
      .withIndex('by_email', (q) => q.eq('email', args.email))
      .first();
    return user;
  },
});

Why Convex is Secure:

  • Automatic Validation: All arguments are validated against declared schemas
  • Type Safety: TypeScript prevents many injection vectors
  • No SQL: NoSQL queries eliminate SQL injection entirely
  • Sandboxed Execution: Functions run in isolated environments

Input Validation at Schema Level

Solo Kit implements schema-level validation in Convex:

// convex/schema.ts
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';

export default defineSchema({
  users: defineTable({
    email: v.string(),
    name: v.string(),
    role: v.union(v.literal('admin'), v.literal('user')),
    emailVerified: v.optional(v.boolean()),
    // ... other fields
  })
    .index('by_email', ['email'])
    .index('by_role', ['role']),
});

Multiple validation layers:

// convex/verificationTokens.ts
import { v } from 'convex/values';
import { mutation } from './_generated/server';
import { z } from 'zod';

// Additional Zod validation for complex patterns
const emailSchema = z.string().email();

export const createVerificationToken = mutation({
  args: {
    email: v.string(),
    type: v.union(v.literal('email_verification'), v.literal('password_reset')),
    token: v.string(),
    expiresAt: v.number(),
  },
  handler: async (ctx, args) => {
    // Additional email validation
    const validatedEmail = emailSchema.parse(args.email);

    return await ctx.db.insert('verificationTokens', {
      ...args,
      email: validatedEmail,
      createdAt: Date.now(),
    });
  },
});

Authentication & Authorization

Role-Based Access Control

Solo Kit implements comprehensive RBAC with Convex:

// convex/schema.ts
export default defineSchema({
  users: defineTable({
    email: v.string(),
    name: v.string(),
    role: v.union(v.literal('admin'), v.literal('user')), // Type-safe roles
    // ... other fields
  }),
});

Role-based query patterns:

// convex/admin.ts
import { v } from 'convex/values';
import { mutation, query } from './_generated/server';
import { getCurrentUser } from './auth';

// Admin-only operations
export const adminOnly = async <T>(ctx: any, operation: () => Promise<T>): Promise<T> => {
  const user = await getCurrentUser(ctx);

  if (!user || user.role !== 'admin') {
    throw new Error('Access denied: Admin privileges required');
  }

  return await operation();
};

// Usage example
export const deleteAnyUser = mutation({
  args: { targetUserId: v.id('users') },
  handler: async (ctx, args) => {
    return await adminOnly(ctx, async () => {
      await ctx.db.delete(args.targetUserId);
      return true;
    });
  },
});

Secure Session Management

Solo Kit uses BetterAuth for secure session handling:

// apps/web/lib/auth.ts
export const authOptions: BetterAuthOptions = {
  // Session-based authentication (more secure than JWT-only)
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days
    updateAge: 60 * 60 * 24, // 1 day - extends on activity
    freshAge: 60 * 15, // 15 minutes for sensitive operations
    storeSessionInDatabase: true, // Sessions are revocable

    // Performance optimization with security
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // 5 minutes - short cache for security
    },
  },

  // Enhanced cookie security
  advanced: {
    useSecureCookies: process.env.NODE_ENV === 'production',
    cookies: {
      sessionToken: {
        name: 'better-auth.session_token',
        attributes: {
          secure: process.env.NODE_ENV === 'production', // HTTPS only in production
          sameSite: 'lax', // CSRF protection
          path: '/',
          httpOnly: true, // Prevent XSS attacks
        },
      },
    },
  },

  // Trusted origins for CORS protection
  trustedOrigins: [authConfig.baseURL],
};

Security features:

  • HttpOnly Cookies: Prevents XSS access to session tokens
  • Secure Cookies: HTTPS-only in production
  • SameSite Protection: CSRF attack prevention
  • Database Sessions: Revocable sessions for security
  • Session Rotation: Automatic session refresh

Password Security

Argon2 password hashing with legacy support:

// apps/web/lib/utils/password.ts
export async function hashPassword(password: string): Promise<string> {
  // Use Argon2 for new passwords (industry standard)
  return await hash(password, {
    memoryCost: 65536, // 64 MB
    timeCost: 3, // 3 iterations
    parallelism: 4, // 4 parallel threads
    hashLength: 32, // 32 bytes output
  });
}

export async function verifyLegacyPassword(
  password: string,
  hash: string
): Promise<{ isValid: boolean; needsRehash: boolean }> {
  // Support migration from bcrypt/scrypt
  if (hash.startsWith('$argon2')) {
    // Already Argon2
    const isValid = await verify(hash, password);
    return { isValid, needsRehash: false };
  } else if (hash.startsWith('$2b$')) {
    // Legacy bcrypt - verify and mark for rehash
    const isValid = await bcrypt.compare(password, hash);
    return { isValid, needsRehash: isValid };
  }

  // Unknown format
  return { isValid: false, needsRehash: false };
}

Connection Security

Secure Convex Configuration

Environment variable protection:

# .env.local (never committed)
NEXT_PUBLIC_CONVEX_URL="https://your-project.convex.cloud"
CONVEX_DEPLOY_KEY="your-deploy-key"

Convex security features:

  • Automatic TLS: All connections encrypted by default
  • Edge Deployment: Low-latency secure connections
  • Sandboxed Functions: Isolated execution environments
  • Automatic Retries: Built-in connection resilience

Convex Client Configuration

Solo Kit's connection architecture includes security features:

// apps/web/lib/convex-client.ts
import { ConvexReactClient } from 'convex/react';

const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL!;

export const convex = new ConvexReactClient(convexUrl);

// Server-side Convex client with authentication
// apps/web/lib/convex-server.ts
import { ConvexHttpClient } from 'convex/browser';

export function getConvexClient() {
  const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL;

  if (!convexUrl) {
    throw new Error('NEXT_PUBLIC_CONVEX_URL is not configured');
  }

  return new ConvexHttpClient(convexUrl);
}

Data Protection & Cleanup

Automated Token Cleanup

Solo Kit includes automatic security token cleanup:

// convex/cleanup.ts
import { internalMutation } from './_generated/server';

export const cleanupExpiredTokens = internalMutation({
  handler: async (ctx) => {
    const now = Date.now();

    // Find and delete expired verification tokens
    const expiredTokens = await ctx.db
      .query('verificationTokens')
      .filter((q) => q.lt(q.field('expiresAt'), now))
      .collect();

    let deletedCount = 0;
    for (const token of expiredTokens) {
      await ctx.db.delete(token._id);
      deletedCount++;
    }

    if (deletedCount > 0) {
      console.info(`Cleaned up ${deletedCount} expired verification tokens`);
    }

    return deletedCount;
  },
});

// User-specific cleanup for security incidents
export const cleanupUserTokens = internalMutation({
  args: {
    userId: v.id('users'),
    tokenType: v.optional(v.union(v.literal('email_verification'), v.literal('password_reset'))),
  },
  handler: async (ctx, args) => {
    let tokensQuery = ctx.db
      .query('verificationTokens')
      .withIndex('by_userId', (q) => q.eq('userId', args.userId));

    const tokens = await tokensQuery.collect();

    const filteredTokens = args.tokenType
      ? tokens.filter((t) => t.type === args.tokenType)
      : tokens;

    for (const token of filteredTokens) {
      await ctx.db.delete(token._id);
    }

    return filteredTokens.length;
  },
});

Token Replay Attack Prevention

Used token tracking for security:

// convex/schema.ts
export default defineSchema({
  usedTokens: defineTable({
    jti: v.string(), // JWT ID from token
    tokenHash: v.string(), // Hashed token for security
    usedAt: v.number(),
    expiresAt: v.number(),
    purpose: v.string(), // 'purchase-verification', etc.
    email: v.string(), // For audit trail
  })
    .index('by_jti', ['jti'])
    .index('by_expiresAt', ['expiresAt']),
});

// convex/tokens.ts
export const markTokenAsUsed = mutation({
  args: {
    jti: v.string(),
    tokenHash: v.string(),
    purpose: v.string(),
    email: v.string(),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert('usedTokens', {
      ...args,
      usedAt: Date.now(),
      expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
    });
  },
});

export const isTokenUsed = query({
  args: { jti: v.string() },
  handler: async (ctx, args) => {
    const token = await ctx.db
      .query('usedTokens')
      .withIndex('by_jti', (q) => q.eq('jti', args.jti))
      .first();

    return token !== null;
  },
});

Security Monitoring

Comprehensive Security Scanning

Solo Kit includes automated security scanning:

# Run comprehensive security scan
pnpm security-scan

Security scan features (scripts/security-scan.ts):

  • Dependency vulnerability scanning: pnpm audit integration
  • Source code security scanning: Pattern-based vulnerability detection
  • Authentication flow analysis: Session and token validation
  • CORS and CSP policy validation: Header security checks
  • Input sanitization checks: Validation prevention checks
  • Rate limiting verification: DoS protection validation
  • Security headers validation: HTTPS and security header checks

Database Health Monitoring

Built-in database security health checks:

// API route: app/api/health/database/route.ts
import { getConvexClient } from '@/lib/convex-server';
import { api } from '@/convex/_generated/api';

export async function GET() {
  try {
    const startTime = Date.now();
    const convex = getConvexClient();

    // Test basic connectivity
    await convex.query(api.health.check);
    const responseTime = Date.now() - startTime;

    return Response.json({
      status: 'healthy',
      responseTime: `${responseTime}ms`,
      provider: 'convex',
      timestamp: new Date().toISOString(),
    });
  } catch (error) {
    return Response.json(
      {
        status: 'unhealthy',
        error: error.message,
        provider: 'convex',
      },
      { status: 503 }
    );
  }
}

Audit Logging

Security event logging patterns:

// convex/audit.ts
import { v } from 'convex/values';
import { mutation } from './_generated/server';

export const logSecurityEvent = mutation({
  args: {
    eventType: v.union(
      v.literal('login'),
      v.literal('logout'),
      v.literal('admin_action'),
      v.literal('token_cleanup')
    ),
    userId: v.optional(v.id('users')),
    details: v.optional(v.any()),
    ipAddress: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert('auditLogs', {
      ...args,
      timestamp: Date.now(),
    });
  },
});

// Usage examples in application code
// await logSecurityEvent({ eventType: 'login', userId, details: { method: 'email' } });
// await logSecurityEvent({ eventType: 'admin_action', userId: adminId, details: { action: 'user_delete', targetUserId } });

Security Best Practices

1. Environment Security

Secure environment variable management:

# .env.local (never committed)
NEXT_PUBLIC_CONVEX_URL="https://your-project.convex.cloud"
CONVEX_DEPLOY_KEY="your-deploy-key"

# .env (safe defaults, committed)
EMAIL_PROVIDER=console
PAYMENTS_PROVIDER=disabled

# NEVER: Commit real credentials to git

Environment validation:

// Validate critical security settings
function validateSecurityEnvironment() {
  const errors = [];

  if (process.env.NODE_ENV === 'production') {
    if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
      errors.push('Production must have NEXT_PUBLIC_CONVEX_URL configured');
    }

    if (!process.env.BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRET.length < 32) {
      errors.push('Production must have secure auth secret (32+ characters)');
    }
  }

  return errors;
}

2. Access Control Patterns

Implement consistent authorization:

// convex/auth.ts
export async function requireRole(ctx: any, allowedRoles: string[]) {
  const user = await getCurrentUser(ctx);

  if (!user) {
    throw new Error('Authentication required');
  }

  if (!allowedRoles.includes(user.role)) {
    throw new Error(`Access denied. Required roles: ${allowedRoles.join(', ')}`);
  }

  return user;
}

// Usage
export const deleteUser = mutation({
  args: { targetUserId: v.id('users') },
  handler: async (ctx, args) => {
    await requireRole(ctx, ['admin']);

    // Admin-only operation
    await ctx.db.delete(args.targetUserId);
  },
});

3. Input Sanitization

Always validate and sanitize:

import { z } from 'zod';
import { v } from 'convex/values';
import { mutation } from './_generated/server';

// Schema validation for security
const userUpdateSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  bio: z.string().max(500).optional(),
});

export const updateUser = mutation({
  args: {
    userId: v.id('users'),
    updates: v.object({
      name: v.optional(v.string()),
      email: v.optional(v.string()),
      bio: v.optional(v.string()),
    }),
  },
  handler: async (ctx, args) => {
    // Validate input before database operation
    const validatedUpdates = userUpdateSchema.parse(args.updates);

    await ctx.db.patch(args.userId, {
      ...validatedUpdates,
      updatedAt: Date.now(),
    });
  },
});

4. Rate Limiting

Implement rate limiting for security:

// convex/rateLimits.ts
import { v } from 'convex/values';
import { mutation, query } from './_generated/server';

export const checkRateLimit = query({
  args: {
    identifier: v.string(), // IP or user ID
    action: v.string(), // 'login', 'signup', etc.
    maxAttempts: v.optional(v.number()),
    windowMinutes: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    const maxAttempts = args.maxAttempts ?? 5;
    const windowMinutes = args.windowMinutes ?? 15;
    const windowStart = Date.now() - windowMinutes * 60 * 1000;

    // Count recent attempts
    const attempts = await ctx.db
      .query('rateLimits')
      .filter((q) =>
        q.and(
          q.eq(q.field('identifier'), args.identifier),
          q.eq(q.field('action'), args.action),
          q.gte(q.field('windowStart'), windowStart)
        )
      )
      .collect();

    const currentCount = attempts.length;

    return {
      allowed: currentCount < maxAttempts,
      remaining: Math.max(0, maxAttempts - currentCount),
    };
  },
});

export const recordRateLimitAttempt = mutation({
  args: {
    identifier: v.string(),
    action: v.string(),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert('rateLimits', {
      ...args,
      windowStart: Date.now(),
      createdAt: Date.now(),
    });
  },
});

Encryption & Data Protection

Data at Rest

Convex encryption features:

  • Automatic Encryption: All data encrypted at rest by Convex
  • Key Management: Handled by Convex infrastructure
  • Compliance: SOC 2 Type II certified

Data in Transit

Connection encryption:

  • TLS 1.3: All connections use modern TLS
  • Certificate Pinning: Automatic certificate validation
  • Edge Security: Global edge network with security features

Application-level Encryption

Encrypt sensitive data before storage:

import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!; // 32-byte key

export function encryptSensitive(data: string): string {
  const iv = randomBytes(16);
  const cipher = createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
  let encrypted = cipher.update(data, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  return iv.toString('hex') + ':' + encrypted;
}

export function decryptSensitive(encryptedData: string): string {
  const [ivHex, encrypted] = encryptedData.split(':');
  const iv = Buffer.from(ivHex, 'hex');
  const decipher = createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  return decrypted;
}

Next Steps

Strengthen your database security with these additional guides:

  1. Backup & Restore - Secure data protection
  2. Monitoring - Security monitoring
  3. Advanced - Advanced security patterns
  4. Performance - Secure performance optimization

Implementing comprehensive database security ensures your Solo Kit application protects user data and maintains trust at scale.

Database Performance

Optimize database queries, implement effective indexing, and monitor performance in your Solo Kit application with Convex

Backup & Restore

Protect your data with comprehensive backup strategies, disaster recovery plans, and reliable restore procedures using Convex

On this page

Database SecuritySecurity OverviewSolo Kit Security ArchitectureSecurity PrinciplesInput Validation & Type SafetyConvex Function ProtectionInput Validation at Schema LevelAuthentication & AuthorizationRole-Based Access ControlSecure Session ManagementPassword SecurityConnection SecuritySecure Convex ConfigurationConvex Client ConfigurationData Protection & CleanupAutomated Token CleanupToken Replay Attack PreventionSecurity MonitoringComprehensive Security ScanningDatabase Health MonitoringAudit LoggingSecurity Best Practices1. Environment Security2. Access Control Patterns3. Input Sanitization4. Rate LimitingEncryption & Data ProtectionData at RestData in TransitApplication-level EncryptionNext Steps