This document outlines the security architecture, compliance requirements, and best practices for Gear AI CoPilot. Given the sensitive nature of personal vehicle data, financial information, and location tracking, security is a foundational pillar of the platform.
The application implements multiple layers of security:
┌─────────────────────────────────────────────┐
│ Layer 1: Client-Side Security │
│ - Input validation │
│ - XSS prevention │
│ - Secure storage (Keychain/Keystore) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Layer 2: Transport Security │
│ - TLS 1.3 │
│ - Certificate pinning │
│ - Encrypted API requests │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Layer 3: Application Security │
│ - JWT authentication │
│ - API rate limiting │
│ - Input sanitization │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Layer 4: Database Security │
│ - Row Level Security (RLS) │
│ - Encrypted columns │
│ - Audit logging │
└─────────────────────────────────────────────┘
Identity Provider: Firebase Auth serves as the primary identity provider.
Supported Methods:
Implementation:
// services/auth-service.ts
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'firebase/auth';
export async function signUpUser(email: string, password: string) {
const auth = getAuth();
// Validate password strength
if (password.length < 12) {
throw new Error('Password must be at least 12 characters');
}
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
// Send verification email
await sendEmailVerification(userCredential.user);
// Sync to Supabase
await syncUserToSupabase(userCredential.user);
return userCredential.user;
}
All user data in Supabase is protected by RLS policies.
Example Policy:
-- Users can only access their own vehicles
CREATE POLICY "Users can view their own vehicles"
ON public.vehicles FOR SELECT
USING (
auth.uid() = (
SELECT firebase_uid
FROM public.users
WHERE user_id = vehicles.user_id
)
);
Enforcement:
Token Lifecycle:
Token Structure:
interface JWTClaims {
sub: string; // User ID
email: string; // User email
email_verified: boolean;
firebase: {
sign_in_provider: string;
};
iat: number; // Issued at
exp: number; // Expiration
aud: string; // Audience
iss: string; // Issuer
}
Database Encryption:
Encrypted Columns:
-- Financial account numbers
CREATE TABLE public.financial_accounts (
account_id UUID PRIMARY KEY,
-- Encrypted column for account number
account_number_encrypted BYTEA,
encryption_key_id VARCHAR(50)
);
-- Encryption function
CREATE OR REPLACE FUNCTION encrypt_account_number(
account_number VARCHAR,
key TEXT
) RETURNS BYTEA AS $$
BEGIN
RETURN pgp_sym_encrypt(account_number, key);
END;
$$ LANGUAGE plpgsql;
-- Decryption function (restricted access)
CREATE OR REPLACE FUNCTION decrypt_account_number(
encrypted_data BYTEA,
key TEXT
) RETURNS VARCHAR AS $$
BEGIN
RETURN pgp_sym_decrypt(encrypted_data, key);
END;
$$ LANGUAGE plpgsql;
File Storage Encryption:
TLS Configuration:
Implementation:
// Mobile app certificate pinning
// app.json
{
"expo": {
"ios": {
"infoPlist": {
"NSAppTransportSecurity": {
"NSPinnedDomains": {
"api.supabase.co": {
"NSIncludesSubdomains": true,
"NSPinnedLeafIdentities": [
{
"SPKI-SHA256-PIN": "your-certificate-hash"
}
]
}
}
}
}
}
}
}
PII Data Classification:
| Data Type | Classification | Storage | Access Control |
|---|---|---|---|
| PII | Encrypted | User only | |
| VIN | PII | Hashed + Encrypted | User + Support |
| License Plate | PII | Encrypted | User only |
| Location History | Sensitive PII | Anonymized after 90 days | User only |
| Financial Data | Sensitive PII | Encrypted | User only |
| Photos | PII | Encrypted | User only |
Data Minimization:
User Data Rights:
// utils/validators.ts
export function validateVIN(vin: string): boolean {
// VIN must be exactly 17 characters
if (vin.length !== 17) return false;
// VIN cannot contain I, O, or Q
if (/[IOQ]/.test(vin)) return false;
// Validate check digit (position 9)
return validateVINCheckDigit(vin);
}
export function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
export function sanitizeInput(input: string): string {
// Remove HTML tags
return input.replace(/<[^>]*>/g, '');
}
All Edge Functions validate and sanitize inputs:
// supabase/functions/decode-vin/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
serve(async (req) => {
const { vin } = await req.json();
// Validate VIN
if (!vin || typeof vin !== 'string' || vin.length !== 17) {
return new Response(
JSON.stringify({ error: 'Invalid VIN' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
// Sanitize VIN (uppercase, remove spaces)
const sanitizedVIN = vin.toUpperCase().replace(/\s/g, '');
// Process VIN...
});
Supabase Edge Functions:
// Rate limiting middleware
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
const rateLimits = {
free: { requests: 100, window: 3600 }, // 100/hour
pro: { requests: 1000, window: 3600 }, // 1000/hour
dealer: { requests: 10000, window: 3600 }, // 10000/hour
};
async function checkRateLimit(userId: string, tier: string) {
const limit = rateLimits[tier];
const now = Math.floor(Date.now() / 1000);
const windowStart = now - limit.window;
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_KEY')!
);
// Count requests in current window
const { count } = await supabase
.from('api_requests')
.select('*', { count: 'exact', head: true })
.eq('user_id', userId)
.gte('timestamp', windowStart);
if (count && count >= limit.requests) {
throw new Error('Rate limit exceeded');
}
// Log request
await supabase.from('api_requests').insert({
user_id: userId,
timestamp: now,
});
}
// Strict CORS policy
const corsHeaders = {
'Access-Control-Allow-Origin': process.env.NODE_ENV === 'production'
? 'https://gearai.app'
: 'http://localhost:8081',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
};
Always use parameterized queries:
// ❌ NEVER do this
const { data } = await supabase
.from('vehicles')
.select('*')
.eq('vin', userInput); // Safe with Supabase client
// ✅ For raw SQL, use parameterized queries
const { data } = await supabase.rpc('get_vehicle_by_vin', {
vin_param: userInput
});
Automated Tools:
npm audit in CI/CD pipelineProcess:
Static Analysis:
Configuration:
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:security/recommended'
],
plugins: ['security'],
rules: {
'security/detect-object-injection': 'error',
'security/detect-non-literal-regexp': 'warn',
'security/detect-unsafe-regex': 'error',
}
};
Requirements:
Implementation:
// services/gdpr-service.ts
export async function exportUserData(userId: string): Promise<object> {
// Export all user data in JSON format
const [user, vehicles, maintenance, chat] = await Promise.all([
supabase.from('users').select('*').eq('user_id', userId).single(),
supabase.from('vehicles').select('*').eq('user_id', userId),
supabase.from('maintenance_records').select('*').in('vehicle_id', vehicleIds),
supabase.from('chat_sessions').select('*, chat_messages(*)').eq('user_id', userId),
]);
return {
user: user.data,
vehicles: vehicles.data,
maintenance: maintenance.data,
chat: chat.data,
exported_at: new Date().toISOString(),
};
}
export async function deleteUserData(userId: string): Promise<void> {
// Cascade delete via foreign keys
await supabase.from('users').delete().eq('user_id', userId);
// Log deletion for audit
await supabase.from('audit_log').insert({
action: 'user_deletion',
user_id: userId,
timestamp: new Date().toISOString(),
});
}
Requirements:
Privacy Notice: See /legal/privacy-policy.md
Compliance Strategy:
Stripe Integration Security:
// Never log or store card data
export async function createPaymentMethod(
cardElement: any // Stripe Elements card
): Promise<string> {
const stripe = await loadStripe(process.env.STRIPE_PUBLISHABLE_KEY!);
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
});
if (error) {
throw new Error(error.message);
}
// Only return payment method ID, not card details
return paymentMethod.id;
}
Timeline: Year 2 after launch
Requirements:
Phases:
Breach Notification Timeline:
| Level | Description | Response Time | Example |
|---|---|---|---|
| P0 (Critical) | Data breach, system compromise | 15 minutes | Database exposed publicly |
| P1 (High) | Vulnerability actively exploited | 1 hour | XSS attack detected |
| P2 (Medium) | Potential vulnerability discovered | 4 hours | Outdated dependency with CVE |
| P3 (Low) | Security best practice violation | 24 hours | Missing security header |
Internal:
External:
Logged Events:
Log Retention: 1 year for audit, 7 days for operational logs
Implementation:
-- Audit log table
CREATE TABLE public.audit_log (
log_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID,
action VARCHAR(100) NOT NULL,
resource_type VARCHAR(50),
resource_id UUID,
ip_address INET,
user_agent TEXT,
success BOOLEAN DEFAULT true,
error_message TEXT,
timestamp TIMESTAMP DEFAULT NOW(),
metadata JSONB
);
-- Trigger for sensitive table access
CREATE OR REPLACE FUNCTION log_financial_access()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.audit_log (user_id, action, resource_type, resource_id)
VALUES (
(SELECT user_id FROM public.users WHERE firebase_uid = auth.uid()),
TG_OP,
'financial_account',
NEW.account_id
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER audit_financial_access
AFTER SELECT OR UPDATE OR DELETE ON public.financial_accounts
FOR EACH ROW EXECUTE FUNCTION log_financial_access();
Monitored Metrics:
Alerting:
Never commit secrets to version control:
# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key
secrets/
Use environment variables:
// ✅ Correct
const apiKey = process.env.OPENAI_API_KEY;
// ❌ Never do this
const apiKey = 'sk-proj-abc123...';
Secret Rotation:
Security is an ongoing process, not a one-time implementation. This security framework provides a strong foundation, but continuous monitoring, testing, and improvement are essential to protect user data and maintain trust in the Gear AI CoPilot platform.
Security Contact: security@gearai.app
Vulnerability Reporting: See SECURITY.md in repository root