This document outlines security best practices and guidelines for developing and deploying DiagnosAI. As a medical application handling sensitive patient data, security is paramount.
-
NEVER commit secrets to version control
- API keys, database passwords, JWT secrets
- Supabase service role keys
- Any production credentials
-
Use environment variables for all secrets
# ✅ CORRECT const apiKey = process.env.OPENAI_API_KEY; # ❌ WRONG - Never hardcode const apiKey = 'sk-1234567890abcdef';
-
Separate development and production credentials
- Use different Supabase projects for dev/staging/prod
- Rotate keys regularly
- Never use production keys in development
Store these in .env.local (never commit this file):
# Supabase - Different keys for dev/prod
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc... # Public, client-side
SUPABASE_SERVICE_ROLE_KEY=eyJhbGc... # SECRET - server only!
DATABASE_URL=postgresql://postgres:[password]@...
# AI Providers - Keep private
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GROQ_API_KEY=gsk_...
HUGGINGFACE_TOKEN=hf_...
# Application
NEXT_PUBLIC_APP_URL=http://localhost:3000| Variable | Exposure | Usage | Security Level |
|---|---|---|---|
NEXT_PUBLIC_* |
Public (embedded in client bundle) | Browser-safe data | Low |
*_ANON_KEY |
Public (but restricted by RLS) | Client-side DB access | Medium |
*_SERVICE_ROLE_KEY |
Private (server-only) | Admin operations, bypasses RLS | CRITICAL |
DATABASE_URL |
Private (server-only) | Direct DB connection | CRITICAL |
*_API_KEY |
Private (server-only) | Third-party services | High |
All tables with user data MUST have RLS enabled:
-- Enable RLS
ALTER TABLE diagnostic_sessions ENABLE ROW LEVEL SECURITY;
-- Create policy
CREATE POLICY "Users can only see their own sessions"
ON diagnostic_sessions FOR SELECT
USING (auth.uid() = doctor_id);Tables WITHOUT RLS (public knowledge only):
medical_diseases- Public medical informationmedical_symptoms- Public symptom indexmedical_qa- Public Q&A knowledge base
These are protected by:
- Insert/Update/Delete restricted to service role
- No PHI (Protected Health Information)
- Read-only for application users
The SUPABASE_SERVICE_ROLE_KEY has superuser privileges and bypasses all RLS policies.
Rules:
- ✅ Use ONLY in server-side code (
/app/api/routes) - ✅ Use for ETL scripts and admin operations
- ❌ NEVER send to client-side code
- ❌ NEVER log or display in error messages
- ❌ NEVER commit to GitHub
Usage Example:
// ✅ CORRECT - Server-side API route
// app/api/sessions/route.ts
import { createServerClient } from '@/lib/supabase/client';
export async function POST(req: Request) {
const supabase = createServerClient(); // Uses service role
// ... admin operations
}
// ❌ WRONG - Client component
// components/my-component.tsx
'use client';
const supabase = createClient(url, SERVICE_ROLE_KEY); // EXPOSED!DiagnosAI handles sensitive medical data. Follow these guidelines:
-
Data Minimization
- Only collect necessary patient information
- Don't store unnecessary identifiable data
- Use patient IDs instead of names where possible
-
Encryption
- Data in transit: HTTPS/TLS (enforced by Supabase)
- Data at rest: PostgreSQL encryption (Supabase default)
- Consider column-level encryption for extra sensitive data
-
Audit Trails
- All patient data access logged in
session_audit_log - Append-only audit table (no modifications)
- Include actor, timestamp, IP address
- All patient data access logged in
-
Access Control
- RLS policies enforce data isolation
- Doctor can only access their own sessions
- Admin access logged and monitored
The audit log is tamper-proof using database triggers:
-- Prevents modification/deletion of audit records
CREATE TRIGGER audit_log_immutable
BEFORE UPDATE OR DELETE ON session_audit_log
FOR EACH ROW
EXECUTE FUNCTION prevent_audit_log_modification();This ensures medicolegal compliance and evidence integrity.
Always validate and sanitize user input:
import { z } from 'zod';
const sessionSchema = z.object({
patient_name: z.string().min(1).max(100),
patient_age: z.number().int().min(0).max(150),
chief_complaint: z.string().max(500)
});
// Validate before database insertion
const validated = sessionSchema.parse(req.body);Protect API endpoints from abuse:
// Example rate limiting (implement in middleware)
const rateLimit = {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per window
};Never expose sensitive information in error messages:
// ❌ WRONG - Exposes internal details
throw new Error(`Database connection failed: ${dbPassword}`);
// ✅ CORRECT - Generic message, log details server-side
console.error('DB connection failed:', error);
throw new Error('Internal server error');Restrict API access to known origins:
// next.config.ts
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: 'https://yourdomain.com' },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE' }
]
}
];
}
};# .env.local (development)
NEXT_PUBLIC_SUPABASE_URL=https://dev-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=dev_key_xxx
OPENAI_API_KEY=test_key_xxxDevelopment Practices:
- Use separate Supabase project for dev
- Use test API keys with spending limits
- Never test with real patient data
- Reset database frequently
# Vercel environment variables (production)
NEXT_PUBLIC_SUPABASE_URL=https://prod-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=[stored securely in Vercel]
OPENAI_API_KEY=[stored securely in Vercel]Production Practices:
- Use Vercel's encrypted environment variables
- Enable Supabase production mode
- Set up monitoring and alerting
- Regular security audits
- Backup strategy for patient data
Rotate secrets regularly to minimize breach impact:
| Secret Type | Rotation Frequency | Action |
|---|---|---|
| Supabase Keys | Every 90 days | Generate new keys in dashboard |
| API Keys | Every 90 days | Rotate with providers |
| Database Passwords | Every 180 days | Update via Supabase |
| JWT Secrets | Every 365 days | Update auth configuration |
Before deploying to production:
- All secrets stored in environment variables
- No hardcoded credentials in code
-
.env.localin.gitignore - RLS enabled on all tables with user data
- Audit logging configured and tested
- HTTPS/TLS enforced on all endpoints
- Input validation on all API routes
- Error messages don't leak sensitive info
- Rate limiting configured
- CORS properly restricted
- Separate dev/prod environments
- Backup and disaster recovery plan
- Security monitoring and alerting
- Code review for security issues
- Dependency vulnerability scan
If a security breach occurs:
-
Immediate Actions
- Rotate all compromised credentials immediately
- Revoke exposed API keys
- Lock down affected systems
- Document timeline and impact
-
Investigation
- Review audit logs for unauthorized access
- Identify scope of data breach
- Determine attack vector
-
Remediation
- Patch vulnerabilities
- Update security policies
- Notify affected users (if required by law)
- Post-mortem and lessons learned
-
Contact
- Report to Supabase support
- Contact relevant authorities if PHI exposed
- Inform affected healthcare providers
If you discover a security vulnerability:
- DO NOT open a public GitHub issue
- Email security concerns to: [your-security-email]
- Include:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if known)
Last Updated: January 2026
Version: 1.0
Review Schedule: Quarterly security audits recommended