Security & Privacy
Earna AI Console implements enterprise-grade security and comprehensive privacy protection. Our security framework protects user data, conversation history, and uploaded files throughout the entire data lifecycle.
Security Architecture
Defense in Depth
Earna AI Console follows Defense in Depth principles:
- Multi-layered Security: Multiple security controls at different layers
- Zero Trust Architecture: Never trust, always verify
- Principle of Least Privilege: Minimal access rights by default
- End-to-End Encryption: Protection throughout data lifecycle
- Security by Design: Built-in security from the ground up
- Continuous Monitoring: Real-time threat detection and response
Data Protection
Encryption Framework
All data is protected with industry-standard encryption:
// lib/security/encryption.ts
import { createCipheriv, createDecipheriv, randomBytes, scrypt } from 'crypto';
import { promisify } from 'util';
const scryptAsync = promisify(scrypt);
export class EncryptionService {
private static instance: EncryptionService;
private readonly algorithm = 'aes-256-gcm';
private readonly keyLength = 32;
static getInstance(): EncryptionService {
if (!EncryptionService.instance) {
EncryptionService.instance = new EncryptionService();
}
return EncryptionService.instance;
}
async encrypt(text: string, password: string): Promise<string> {
const salt = randomBytes(16);
const iv = randomBytes(16);
const key = await this.deriveKey(password, salt);
const cipher = createCipheriv(this.algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// Combine salt, iv, authTag, and encrypted data
const combined = Buffer.concat([
salt,
iv,
authTag,
Buffer.from(encrypted, 'hex')
]);
return combined.toString('base64');
}
async decrypt(encryptedData: string, password: string): Promise<string> {
const combined = Buffer.from(encryptedData, 'base64');
const salt = combined.subarray(0, 16);
const iv = combined.subarray(16, 32);
const authTag = combined.subarray(32, 48);
const encrypted = combined.subarray(48);
const key = await this.deriveKey(password, salt);
const decipher = createDecipheriv(this.algorithm, key, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, undefined, 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
private async deriveKey(password: string, salt: Buffer): Promise<Buffer> {
return (await scryptAsync(password, salt, this.keyLength)) as Buffer;
}
}
Supabase Row Level Security
Database-level access control with RLS policies:
-- Row Level Security Policies
-- Users can only see their own data
CREATE POLICY "Users can view own profile" ON users
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON users
FOR UPDATE USING (auth.uid() = id);
-- Chats are private to their owners
CREATE POLICY "Users can view own chats" ON chats
FOR ALL USING (auth.uid() = user_id);
-- Messages are accessible only through owned chats
CREATE POLICY "Users can view own messages" ON messages
FOR ALL USING (
EXISTS (
SELECT 1 FROM chats
WHERE chats.id = messages.chat_id
AND chats.user_id = auth.uid()
)
);
-- File attachments follow chat ownership
CREATE POLICY "Users can manage own attachments" ON chat_attachments
FOR ALL USING (
EXISTS (
SELECT 1 FROM chats
WHERE chats.id = chat_attachments.chat_id
AND chats.user_id = auth.uid()
)
);
Authentication & Authorization
Multi-Provider Authentication
Secure authentication via Supabase Auth:
// lib/auth/supabase-auth.ts
import { createClient } from '@supabase/supabase-js';
export class AuthService {
private supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// Email/Password authentication
async signUp(email: string, password: string) {
const { data, error } = await this.supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
data: {
subscription_tier: 'free'
}
}
});
if (error) throw error;
return data;
}
// OAuth providers
async signInWithProvider(provider: 'google' | 'github') {
const { data, error } = await this.supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${window.location.origin}/auth/callback`,
scopes: provider === 'google' ? 'email profile' : 'read:user'
}
});
if (error) throw error;
return data;
}
// Anonymous authentication
async signInAnonymously() {
const { data, error } = await this.supabase.auth.signInAnonymously();
if (error) throw error;
return data;
}
// Session management
async getSession() {
const { data: { session } } = await this.supabase.auth.getSession();
return session;
}
// Sign out
async signOut() {
const { error } = await this.supabase.auth.signOut();
if (error) throw error;
}
}
JWT Token Validation
Secure token validation for API access:
// lib/auth/jwt-validation.ts
import jwt from 'jsonwebtoken';
export async function validateToken(token: string): Promise<any> {
try {
// Verify Supabase JWT
const decoded = jwt.verify(token, process.env.SUPABASE_JWT_SECRET!);
// Additional validation
if (!decoded.sub || !decoded.email) {
throw new Error('Invalid token payload');
}
// Check token expiration
const now = Math.floor(Date.now() / 1000);
if (decoded.exp && decoded.exp < now) {
throw new Error('Token expired');
}
return {
userId: decoded.sub,
email: decoded.email,
role: decoded.role || 'user'
};
} catch (error) {
throw new Error('Invalid token');
}
}
API Security
Rate Limiting
Protect against abuse with rate limiting:
// lib/security/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!
});
// Different rate limits for different tiers
const rateLimiters = {
free: new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 requests per minute
analytics: true
}),
pro: new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(100, '1 m'), // 100 requests per minute
analytics: true
}),
api: new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(1000, '1 h'), // 1000 requests per hour
analytics: true
})
};
export async function rateLimit(
identifier: string,
tier: 'free' | 'pro' | 'api' = 'free'
): Promise<{ success: boolean; limit: number; remaining: number; reset: number }> {
const limiter = rateLimiters[tier];
const { success, limit, remaining, reset } = await limiter.limit(identifier);
return { success, limit, remaining, reset };
}
Input Validation
Comprehensive input validation and sanitization:
// lib/security/validation.ts
import { z } from 'zod';
import DOMPurify from 'isomorphic-dompurify';
// Chat message validation
export const chatMessageSchema = z.object({
message: z.string().min(1).max(10000),
chatId: z.string().uuid(),
model: z.enum(['gpt-4o', 'claude-3-opus', 'gemini-1.5-pro']).optional(),
attachments: z.array(z.string().url()).optional()
});
// File upload validation
export const fileUploadSchema = z.object({
fileName: z.string().max(255),
fileType: z.enum(['image/jpeg', 'image/png', 'image/gif', 'application/pdf']),
fileSize: z.number().max(10 * 1024 * 1024) // 10MB max
});
// Sanitize HTML content
export function sanitizeHtml(dirty: string): string {
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'code', 'pre'],
ALLOWED_ATTR: ['href', 'target', 'rel']
});
}
// Sanitize user input
export function sanitizeInput(input: string): string {
return input
.replace(/[<>]/g, '') // Remove angle brackets
.replace(/javascript:/gi, '') // Remove javascript: protocol
.replace(/on\w+\s*=/gi, '') // Remove event handlers
.trim();
}
Privacy Controls
Data Minimization
Collect only necessary information:
// lib/privacy/data-minimization.ts
export class DataMinimization {
// Only store essential user data
async createUserProfile(authUser: any) {
return {
id: authUser.id,
email: authUser.email,
created_at: new Date().toISOString(),
// Don't store unnecessary personal info
subscription_tier: 'free',
daily_message_limit: 10
};
}
// Minimize chat data storage
async saveChatMessage(message: any) {
return {
id: crypto.randomUUID(),
chat_id: message.chatId,
role: message.role,
content: message.content,
model: message.model,
created_at: new Date().toISOString()
// Don't store IP addresses, user agents, etc.
};
}
// Anonymous analytics
async trackEvent(event: string, properties: any) {
// Hash user ID for privacy
const hashedUserId = this.hashUserId(properties.userId);
return {
event,
timestamp: new Date().toISOString(),
properties: {
...properties,
userId: hashedUserId
}
};
}
private hashUserId(userId: string): string {
const crypto = require('crypto');
return crypto
.createHash('sha256')
.update(userId + process.env.HASH_SALT!)
.digest('hex')
.substring(0, 16);
}
}
Data Retention
Automatic data cleanup:
// lib/privacy/data-retention.ts
export class DataRetention {
// Configure retention periods
private retentionPolicies = {
chat_messages: 30, // 30 days for free tier
chat_messages_pro: 365, // 1 year for pro tier
file_uploads: 7, // 7 days
anonymous_users: 30, // 30 days
audit_logs: 90 // 90 days
};
async cleanupOldData() {
const now = new Date();
// Cleanup old chat messages
await this.deleteOldChatMessages(now);
// Cleanup old file uploads
await this.deleteOldFiles(now);
// Cleanup anonymous user data
await this.deleteOldAnonymousUsers(now);
// Cleanup old audit logs
await this.deleteOldAuditLogs(now);
}
private async deleteOldChatMessages(now: Date) {
const cutoffDate = new Date(
now.getTime() - this.retentionPolicies.chat_messages * 24 * 60 * 60 * 1000
);
await supabase
.from('messages')
.delete()
.lt('created_at', cutoffDate.toISOString())
.eq('user_tier', 'free');
}
private async deleteOldFiles(now: Date) {
const cutoffDate = new Date(
now.getTime() - this.retentionPolicies.file_uploads * 24 * 60 * 60 * 1000
);
// List old files
const { data: oldFiles } = await supabase
.from('chat_attachments')
.select('storage_path')
.lt('created_at', cutoffDate.toISOString());
// Delete from storage
if (oldFiles && oldFiles.length > 0) {
const paths = oldFiles.map(f => f.storage_path);
await supabase.storage.from('chat-attachments').remove(paths);
// Delete records
await supabase
.from('chat_attachments')
.delete()
.lt('created_at', cutoffDate.toISOString());
}
}
}
User Rights
GDPR-compliant user rights:
// lib/privacy/user-rights.ts
export class UserRights {
// Right to access
async exportUserData(userId: string) {
const userData = await supabase
.from('users')
.select(`
*,
chats (
*,
messages (*),
chat_attachments (*)
)
`)
.eq('id', userId)
.single();
return {
profile: userData.data,
exportDate: new Date().toISOString(),
format: 'json'
};
}
// Right to deletion
async deleteUserAccount(userId: string) {
// Delete in correct order due to foreign keys
await supabase.rpc('delete_user_data', { user_id: userId });
// Delete auth account
await supabase.auth.admin.deleteUser(userId);
return { deleted: true, timestamp: new Date().toISOString() };
}
// Right to rectification
async updateUserData(userId: string, updates: any) {
const { data, error } = await supabase
.from('users')
.update(updates)
.eq('id', userId);
if (error) throw error;
return data;
}
// Right to portability
async exportDataPortable(userId: string) {
const data = await this.exportUserData(userId);
// Convert to standard format (JSON-LD)
return {
'@context': 'https://schema.org',
'@type': 'Person',
identifier: userId,
email: data.profile.email,
dateCreated: data.profile.created_at,
data: data.profile
};
}
}
Security Monitoring
Threat Detection
Real-time security monitoring:
// lib/security/monitoring.ts
export class SecurityMonitor {
private alertThresholds = {
failedLogins: 5,
rapidRequests: 100,
largeDataExport: 1000,
suspiciousPatterns: ['sql', 'script', 'eval', 'exec']
};
async detectThreats(activity: any) {
// Check for brute force attempts
if (activity.type === 'login_failed') {
await this.checkBruteForce(activity);
}
// Check for rate limit violations
if (activity.type === 'api_request') {
await this.checkRateLimit(activity);
}
// Check for data exfiltration
if (activity.type === 'data_export') {
await this.checkDataExfiltration(activity);
}
// Check for injection attempts
if (activity.type === 'user_input') {
await this.checkInjectionAttempts(activity);
}
}
private async checkBruteForce(activity: any) {
const recentFailures = await this.getRecentFailedLogins(
activity.email,
5 // last 5 minutes
);
if (recentFailures >= this.alertThresholds.failedLogins) {
await this.triggerAlert('brute_force_attempt', {
email: activity.email,
attempts: recentFailures,
ip: activity.ip
});
// Block IP temporarily
await this.blockIP(activity.ip, 3600); // 1 hour
}
}
private async checkInjectionAttempts(activity: any) {
const input = activity.input.toLowerCase();
for (const pattern of this.alertThresholds.suspiciousPatterns) {
if (input.includes(pattern)) {
await this.triggerAlert('possible_injection', {
userId: activity.userId,
input: activity.input.substring(0, 100),
pattern: pattern,
ip: activity.ip
});
break;
}
}
}
private async triggerAlert(type: string, details: any) {
// Log to security audit
await supabase.from('security_alerts').insert({
type,
severity: this.getSeverity(type),
details,
timestamp: new Date().toISOString()
});
// Send notifications for high severity
if (this.getSeverity(type) === 'high') {
await this.notifySecurityTeam(type, details);
}
}
private getSeverity(type: string): 'low' | 'medium' | 'high' {
const highSeverity = ['brute_force_attempt', 'data_breach', 'account_takeover'];
const mediumSeverity = ['possible_injection', 'rate_limit_violation'];
if (highSeverity.includes(type)) return 'high';
if (mediumSeverity.includes(type)) return 'medium';
return 'low';
}
}
Audit Logging
Comprehensive audit trail:
// lib/security/audit.ts
export class AuditLogger {
async log(event: {
userId?: string;
action: string;
resource: string;
details?: any;
ip?: string;
userAgent?: string;
}) {
await supabase.from('audit_logs').insert({
user_id: event.userId,
action: event.action,
resource: event.resource,
details: event.details,
ip_address: event.ip,
user_agent: event.userAgent,
timestamp: new Date().toISOString()
});
}
async logApiAccess(request: Request, userId?: string) {
await this.log({
userId,
action: 'api_access',
resource: new URL(request.url).pathname,
details: {
method: request.method,
headers: this.sanitizeHeaders(request.headers)
},
ip: request.headers.get('x-forwarded-for') || 'unknown',
userAgent: request.headers.get('user-agent') || 'unknown'
});
}
async logDataAccess(userId: string, dataType: string, operation: string) {
await this.log({
userId,
action: `data_${operation}`,
resource: dataType,
details: {
timestamp: new Date().toISOString()
}
});
}
private sanitizeHeaders(headers: Headers): any {
const sanitized: any = {};
const safeHeaders = ['content-type', 'user-agent', 'referer'];
for (const header of safeHeaders) {
if (headers.has(header)) {
sanitized[header] = headers.get(header);
}
}
return sanitized;
}
}
Security Headers
Comprehensive security headers:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Security headers
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
// Content Security Policy
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://vercel.live; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https: blob:; " +
"font-src 'self' data:; " +
"connect-src 'self' https://api.openai.com wss://api.openai.com https://*.supabase.co wss://*.supabase.co https://api.heygen.com; " +
"media-src 'self' blob: https:; " +
"frame-src 'self' https://www.heygen.com;"
);
// Strict Transport Security (HSTS)
if (request.nextUrl.protocol === 'https:') {
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
}
return response;
}
export const config = {
matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
};
Compliance
GDPR Compliance
- Lawful Basis: Explicit consent for data processing
- Data Minimization: Only collect necessary data
- Purpose Limitation: Clear purposes for data collection
- Storage Limitation: Automatic data retention policies
- Integrity & Confidentiality: Encryption and access controls
- Accountability: Audit logs and compliance documentation
CCPA Compliance
- Right to Know: Data export functionality
- Right to Delete: Account deletion options
- Right to Opt-Out: Granular privacy controls
- Non-Discrimination: Equal service regardless of privacy choices
SOC 2 Type II Principles
- Security: Comprehensive security controls
- Availability: 99.9% uptime SLA
- Processing Integrity: Accurate data processing
- Confidentiality: Data encryption and access controls
- Privacy: Privacy by design implementation
Security Best Practices
For Developers
- Never commit secrets: Use environment variables
- Validate all inputs: Use zod schemas
- Sanitize outputs: Prevent XSS attacks
- Use parameterized queries: Prevent SQL injection
- Implement rate limiting: Prevent abuse
- Log security events: Maintain audit trail
- Keep dependencies updated: Regular security patches
For Users
- Use strong passwords: Minimum 12 characters
- Enable 2FA: When available
- Review permissions: Regularly check app permissions
- Report suspicious activity: Contact security team
- Keep software updated: Browser and OS updates
- Use secure networks: Avoid public WiFi
Incident Response
Security Incident Procedure
- Detection: Automated monitoring and alerts
- Containment: Isolate affected systems
- Investigation: Determine scope and impact
- Eradication: Remove threat and vulnerabilities
- Recovery: Restore normal operations
- Lessons Learned: Post-incident review
Contact Information
- Security Team: security@earna.sh
- Privacy Officer: privacy@earna.sh
- Bug Bounty: security.earna.sh
- Status Page: status.earna.sh
Earna AI Console implements comprehensive security measures to protect user data, conversations, and uploaded files. Our security framework is continuously monitored and updated to address emerging threats.
For security concerns or to report vulnerabilities, please contact security@earna.sh.