9.1 KiB
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 submissionsadmin: Full moderation access + user managementsuperuser: All admin privileges + system configuration
Implementation:
- Roles stored in separate
user_rolestable (not on profiles) has_role()function usesSECURITY DEFINERto 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:
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_logfor tracking
4. Input Sanitization
All user-generated content is sanitized before rendering to prevent XSS attacks.
Sanitization Functions:
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 sanitizationsource_url- URL protocol validationreviewer_notes- Plain text sanitization
5. Audit Trail
All moderation actions are automatically logged in the moderation_audit_log table.
Logged Actions:
approve- Submission approvedreject- Submission rejecteddelete- Submission permanently deletedreset- Submission reset to pendingclaim- Submission locked by moderatorrelease- Lock releasedextend_lock- Lock expiry extendedretry_failed- Failed items retried
Audit Log Schema:
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:
SELECT validate_moderation_action(
_submission_id := '<uuid>',
_user_id := auth.uid(),
_action := 'approve'
);
Validation Steps:
- Check if user has moderator/admin/superuser role
- Check if submission is locked by another user
- Check rate limit (10 actions/minute)
- Return
trueif valid, raise exception otherwise
Usage in Application:
While the validation function exists, it's primarily enforced through:
- RLS policies on
content_submissionstable - Automatic audit logging via triggers
- Frontend lock state management
The validation function can be called explicitly for additional security checks:
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
-- 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
-- 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
-
Always sanitize user input before rendering:
// ❌ NEVER DO THIS <div>{userInput}</div> // ✅ ALWAYS DO THIS <div>{sanitizePlainText(userInput)}</div> -
Never bypass validation for "convenience":
// ❌ 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); -
Always check lock state before actions:
const isLockedByOther = useModerationQueue().isLockedByOther( item.id, item.assigned_to, item.locked_until ); if (isLockedByOther) { toast.error('Submission is locked by another moderator'); return; } -
Log all admin actions for audit trail:
await supabase.rpc('log_admin_action', { action: 'delete_submission', target_id: submissionId, details: { reason: 'spam' } });
For Moderators
- Always claim submissions before reviewing (prevents conflicts)
- Release locks if stepping away (allows others to review)
- Provide clear notes for rejections (improves submitter experience)
- 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:anddata: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_rolestable with RLS - Only superusers can grant roles (enforced by RLS)
has_role()function usesSECURITY DEFINERsafely
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:
npm run test:integration -- moderation-security
npm run test:unit -- sanitize
Monitoring & Alerts
Key Metrics to Monitor:
- Failed validation attempts - May indicate attack
- Rate limit violations - May indicate abuse
- Expired locks - May indicate abandoned reviews
- Audit log anomalies - Unusual action patterns
Query Audit Log:
-- 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 = '<uuid>'
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:
- Immediate: Revoke affected user's role in
user_rolestable - Investigate: Query
moderation_audit_logfor suspicious activity - Rollback: Reset affected submissions to pending if needed
- Notify: Alert other moderators via admin panel
- 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