This document outlines security practices and incident response procedures for the CarerView application.
.envfile is in.gitignore(DO NOT COMMIT)- Current Supabase credentials in
.envare EXPOSED in git history - ACTION REQUIRED: Rotate credentials immediately after deployment
If credentials have been exposed, follow these steps:
-
Log into Supabase Dashboard
- Navigate to: https://supabase.com/dashboard
-
Rotate Anon Key
- Go to: Project Settings → API
- Click "Generate new anon key"
- Update
.envwith newVITE_SUPABASE_ANON_KEY
-
Rotate Service Role Key (if exposed)
- Go to: Project Settings → API
- Click "Generate new service_role key"
- Update Supabase Function secrets
- Update any CI/CD secrets
-
Update All Deployments
- Update environment variables in .env file
- Republish application via Bolt Publishing
- Update Supabase Functions with new keys
-
Verify Rotation
- Test authentication flow
- Test Stripe webhooks
- Monitor error logs for 24 hours
NEVER commit these files:
.env.env.local.env.production- Any file containing API keys, tokens, or secrets
DO commit:
.env.example(with placeholder values)
All user-facing tables have RLS enabled and policies configured:
- profiles - User profile data
- observations - Caregiver observations
- responses - Observation responses
- cv_team - Team management
- cv_team_members - Team membership
- cv_team_invites - Invitation tokens
- cv_team_patient - Patient PHI/PII data
- user_subscriptions - Subscription data
- stripe_customers - Stripe customer mapping
- stripe_subscriptions - Stripe subscription data
- stripe_orders - Stripe order data
- rate_limit_log - Rate limiting audit data (service_role only)
- All policies verify
auth.uid()for user identity - Patient data (cv_team_patient) is restricted to team members only
- Invite tokens (cv_team_invites.token_hash) should NEVER be selected
- Team access requires active membership verification
- Token generated server-side (32 bytes random)
- Token hashed with SHA-256 before storage
- Plaintext token sent via URL (one-time use)
- Token stored in sessionStorage (not localStorage)
- Token cleared after use or failure
- sessionStorage: Cleared when browser tab closes
- localStorage: Persists indefinitely (DO NOT USE for tokens)
- URL parameters: Only for initial transmission
- Tokens expire after 7 days
The invite flow in src/pages/AcceptInvite.tsx has been secured:
- Changed from localStorage to sessionStorage
- Token cleared immediately after use or error
- Better error messages for user feedback
- All webhooks verify Stripe signature
- Supports multiple secrets for rotation
- Invalid signatures return 400 error
- Failed attempts logged to console
Located in: Supabase → Project Settings → Functions → Secrets
STRIPE_WEBHOOK_SECRET=whsec_xxx
Or for rotation:
STRIPE_WEBHOOK_SECRETS=["whsec_old", "whsec_new"]
All Supabase Edge Functions use:
'Access-Control-Allow-Origin': '*'-
Restrict to specific origins:
'Access-Control-Allow-Origin': 'https://carerview.com'
-
Or validate origin dynamically:
const allowedOrigins = ['https://carerview.com', 'https://www.carerview.com']; const origin = req.headers.get('origin'); const corsOrigin = allowedOrigins.includes(origin) ? origin : allowedOrigins[0];
Functions running with elevated privileges:
cv_get_active_team()- Gets user's active teamcv_set_active_team(p_team)- Sets active team (validates membership)cv_create_team_with_patient()- Creates team atomicallycv_list_members(p_team)- Lists team members (validates membership)cv_get_remaining(p_team)- Gets observation quota (validates membership)cv_check_team_seats(p_team)- Checks seat availabilitycv_create_invite(p_team, p_email)- Creates invite tokencv_accept_invite(p_token)- Accepts invite tokencv_apply_plan_to_owner_teams(p_owner, p_plan_id)- Enforces plan limits (service_role only)
All SECURITY DEFINER functions MUST:
- Verify
auth.uid()is not null - Validate user has permission for the operation
- Use explicit permission checks (not RLS bypass)
- Log sensitive operations
- Admin user deletion (
admin_eventstable) - Rate limit tracking (
rate_limit_logtable)
- Team membership changes
- Observation deletion
- Plan enforcement actions
- Failed authentication attempts
- Invite creation/acceptance
INSERT INTO public.admin_events (
admin_id,
event_type,
event_data,
created_at
) VALUES (
auth.uid(),
'team_member_removed',
jsonb_build_object('team_id', team_id, 'user_id', removed_user_id),
now()
);✅ RATE LIMITING IMPLEMENTED
Rate limiting is active on all Edge Functions using the check_rate_limit() database function.
Protected Endpoints:
stripe-checkout: 20 requests per minutestripe-webhook: 100 requests per minutestripe-portal: 20 requests per minutecaregiver-delete-account: 5 requests per minuteadmin-delete-user: 5 requests per minute
Data Storage:
- Rate limit data stored in
rate_limit_logtable - RLS enabled with service_role-only access
- Tracks: identifier (IP), endpoint, request count, time window
- Automatic cleanup recommended (7-day retention)
Security:
- Only service_role can read/write rate limit logs
- Users cannot query or modify rate limit data
- Follows same pattern as
admin_events(audit data)
Enforced by Supabase Auth:
- Minimum 6 characters (Supabase default)
- No complexity requirements by default
- Enable email confirmation (currently disabled)
- Implement MFA for admin accounts
- Add password strength indicator
- Monitor failed login attempts
Handled in accordance with HIPAA guidelines:
cv_team_patient.full_namecv_team_patient.date_of_birthobservations.patient_nameprofiles.email
- PHI accessible only to team members
- RLS policies enforce access restrictions
- Encrypted at rest (Supabase default)
- Encrypted in transit (HTTPS only)
- Identify: Determine scope and affected data
- Contain: Revoke exposed credentials immediately
- Assess: Review audit logs for unauthorized access
- Notify: Contact affected users within 72 hours (HIPAA requirement)
- Document: Record incident details and response actions
- Review: Update security measures to prevent recurrence
- Security Lead: [TO BE ADDED]
- Supabase Support: support@supabase.com
- Stripe Support: https://support.stripe.com
- Rotate all Supabase credentials
- Enable email confirmation for new signups
- Implement rate limiting on auth endpoints
- Restrict CORS to production domains
- Enable Stripe webhook signature verification
- Set up security monitoring/alerting
- Review and test all RLS policies
- Conduct penetration testing
- Set up automated security scanning
- Document incident response procedures
- Enable database backups
- Implement audit logging for sensitive operations
- Add Content Security Policy headers
- Review SECURITY DEFINER functions
- Test data export/deletion workflows
- Review Supabase logs for anomalies
- Check failed authentication attempts
- Monitor Stripe webhook failures
- Review RLS policies for gaps
- Audit user permissions
- Check for unused/stale invites
- Review team membership roster
- Rotate Stripe webhook secrets
- Review and update this document
- Conduct security training
- Test incident response procedures
If you discover a security vulnerability:
- DO NOT open a public GitHub issue
- Email security contact (TO BE ADDED)
- Include:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if known)
Last Updated: 2025-10-22 Next Review: 2026-01-22