# Moderation Queue Security ## Overview The moderation queue implements multiple layers of security to prevent unauthorized access, enforce proper workflows, and maintain a comprehensive audit trail. ## Security Layers ### 1. Role-Based Access Control (RBAC) All moderation actions require one of the following roles: - `moderator`: Can review and approve/reject submissions - `admin`: Full moderation access + user management - `superuser`: All admin privileges + system configuration **Implementation:** - Roles stored in separate `user_roles` table (not on profiles) - `has_role()` function uses `SECURITY DEFINER` to avoid RLS recursion - RLS policies enforce role requirements on all sensitive operations ### 2. Lock Enforcement Submissions can be "claimed" by moderators to prevent concurrent modifications. **Lock Mechanism:** - 15-minute expiry window - Only the claiming moderator can approve/reject/delete - Backend validation via `validate_moderation_action()` function - RLS policies prevent lock bypassing **Lock States:** ```typescript interface LockState { submissionId: string; lockedBy: string; expiresAt: Date; } ``` ### 3. Rate Limiting **Client-Side:** - Debounced filter updates (300ms) - Action buttons disabled during processing - Toast notifications for user feedback **Server-Side:** - Maximum 10 moderation actions per minute per user - Enforced by `validate_moderation_action()` function - Uses `moderation_audit_log` for tracking ### 4. Input Sanitization All user-generated content is sanitized before rendering to prevent XSS attacks. **Sanitization Functions:** ```typescript import { sanitizeURL, sanitizePlainText, sanitizeHTML } from '@/lib/sanitize'; // Sanitize URLs to prevent javascript: and data: protocols const safeUrl = sanitizeURL(userInput); // Escape HTML entities in plain text const safeText = sanitizePlainText(userInput); // Sanitize HTML with whitelist const safeHTML = sanitizeHTML(userInput); ``` **Protected Fields:** - `submission_notes` - Plain text sanitization - `source_url` - URL protocol validation - `reviewer_notes` - Plain text sanitization ### 5. Audit Trail All moderation actions are automatically logged in the `moderation_audit_log` table. **Logged Actions:** - `approve` - Submission approved - `reject` - Submission rejected - `delete` - Submission permanently deleted - `reset` - Submission reset to pending - `claim` - Submission locked by moderator - `release` - Lock released - `extend_lock` - Lock expiry extended - `retry_failed` - Failed items retried **Audit Log Schema:** ```sql CREATE TABLE moderation_audit_log ( id UUID PRIMARY KEY, submission_id UUID REFERENCES content_submissions(id), moderator_id UUID REFERENCES auth.users(id), action TEXT, previous_status TEXT, new_status TEXT, notes TEXT, metadata JSONB, created_at TIMESTAMPTZ ); ``` **Access:** - Read-only for moderators/admins/superusers - Inserted automatically via database trigger - Cannot be modified or deleted (immutable audit trail) ## Validation Function The `validate_moderation_action()` function enforces all security rules: ```sql SELECT validate_moderation_action( _submission_id := '', _user_id := auth.uid(), _action := 'approve' ); ``` **Validation Steps:** 1. Check if user has moderator/admin/superuser role 2. Check if submission is locked by another user 3. Check rate limit (10 actions/minute) 4. Return `true` if valid, raise exception otherwise **Usage in Application:** While the validation function exists, it's primarily enforced through: - RLS policies on `content_submissions` table - Automatic audit logging via triggers - Frontend lock state management The validation function can be called explicitly for additional security checks: ```typescript const { data, error } = await supabase.rpc('validate_moderation_action', { _submission_id: submissionId, _user_id: userId, _action: 'approve' }); if (error) { // Handle validation failure } ``` ## RLS Policies ### content_submissions ```sql -- Update policy with lock enforcement CREATE POLICY "Moderators can update with validation" ON content_submissions FOR UPDATE USING (has_role(auth.uid(), 'moderator')) WITH CHECK ( has_role(auth.uid(), 'moderator') AND ( assigned_to IS NULL OR assigned_to = auth.uid() OR locked_until < NOW() ) ); ``` ### moderation_audit_log ```sql -- Read-only for moderators CREATE POLICY "Moderators can view audit log" ON moderation_audit_log FOR SELECT USING (has_role(auth.uid(), 'moderator')); -- Insert only (via trigger or explicit call) CREATE POLICY "System can insert audit log" ON moderation_audit_log FOR INSERT WITH CHECK (moderator_id = auth.uid()); ``` ## Security Best Practices ### For Developers 1. **Always sanitize user input** before rendering: ```typescript // ❌ NEVER DO THIS
{userInput}
// ✅ ALWAYS DO THIS
{sanitizePlainText(userInput)}
``` 2. **Never bypass validation** for "convenience": ```typescript // ❌ WRONG if (isAdmin) { // Skip lock check for admins await updateSubmission(id, { status: 'approved' }); } // ✅ CORRECT // Let RLS policies handle authorization const { error } = await supabase .from('content_submissions') .update({ status: 'approved' }) .eq('id', id); ``` 3. **Always check lock state** before actions: ```typescript const isLockedByOther = useModerationQueue().isLockedByOther( item.id, item.assigned_to, item.locked_until ); if (isLockedByOther) { toast.error('Submission is locked by another moderator'); return; } ``` 4. **Log all admin actions** for audit trail: ```typescript await supabase.rpc('log_admin_action', { action: 'delete_submission', target_id: submissionId, details: { reason: 'spam' } }); ``` ### For Moderators 1. **Always claim submissions** before reviewing (prevents conflicts) 2. **Release locks** if stepping away (allows others to review) 3. **Provide clear notes** for rejections (improves submitter experience) 4. **Respect rate limits** (prevents accidental mass actions) ## Threat Mitigation ### XSS (Cross-Site Scripting) **Threat:** Malicious users submit content with JavaScript to steal session tokens or modify page behavior. **Mitigation:** - All user input sanitized via `DOMPurify` - URL validation blocks `javascript:` and `data:` protocols - CSP headers (if configured) provide additional layer ### CSRF (Cross-Site Request Forgery) **Threat:** Attacker tricks authenticated user into making unwanted actions. **Mitigation:** - Supabase JWT tokens provide CSRF protection - All API calls require valid session token - SameSite cookie settings (managed by Supabase) ### Privilege Escalation **Threat:** Regular user gains moderator/admin privileges. **Mitigation:** - Roles stored in separate `user_roles` table with RLS - Only superusers can grant roles (enforced by RLS) - `has_role()` function uses `SECURITY DEFINER` safely ### Lock Bypassing **Threat:** User modifies submission while locked by another moderator. **Mitigation:** - RLS policies check lock state on UPDATE - Backend validation in `validate_moderation_action()` - Frontend enforces disabled state on UI ### Rate Limit Abuse **Threat:** User spams approve/reject actions to overwhelm system. **Mitigation:** - Server-side rate limiting (10 actions/minute) - Client-side debouncing on filters - Action buttons disabled during processing ## Testing Security See `tests/integration/moderation-security.test.ts` for comprehensive security tests: - ✅ Role validation - ✅ Lock enforcement - ✅ Rate limiting - ✅ Audit logging - ✅ XSS protection (unit tests in `tests/unit/sanitize.test.ts`) **Run Security Tests:** ```bash npm run test:integration -- moderation-security npm run test:unit -- sanitize ``` ## Monitoring & Alerts **Key Metrics to Monitor:** 1. **Failed validation attempts** - May indicate attack 2. **Rate limit violations** - May indicate abuse 3. **Expired locks** - May indicate abandoned reviews 4. **Audit log anomalies** - Unusual action patterns **Query Audit Log:** ```sql -- Recent moderation actions SELECT * FROM moderation_audit_log ORDER BY created_at DESC LIMIT 100; -- Actions by moderator SELECT action, COUNT(*) as count FROM moderation_audit_log WHERE moderator_id = '' GROUP BY action; -- Rate limit violations (proxy: high action density) SELECT moderator_id, COUNT(*) as action_count FROM moderation_audit_log WHERE created_at > NOW() - INTERVAL '1 minute' GROUP BY moderator_id HAVING COUNT(*) > 10; ``` ## Incident Response If a security issue is detected: 1. **Immediate:** Revoke affected user's role in `user_roles` table 2. **Investigate:** Query `moderation_audit_log` for suspicious activity 3. **Rollback:** Reset affected submissions to pending if needed 4. **Notify:** Alert other moderators via admin panel 5. **Document:** Record incident details for review ## Future Enhancements - [ ] MFA requirement for delete/reverse actions - [ ] IP-based rate limiting (in addition to user-based) - [ ] Anomaly detection on audit log patterns - [ ] Automated lock expiry notifications - [ ] Scheduled security audits via cron jobs