mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 18:51:14 -05:00
Approve tool use
This commit is contained in:
350
docs/moderation/SECURITY.md
Normal file
350
docs/moderation/SECURITY.md
Normal file
@@ -0,0 +1,350 @@
|
||||
# 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 := '<uuid>',
|
||||
_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
|
||||
<div>{userInput}</div>
|
||||
|
||||
// ✅ ALWAYS DO THIS
|
||||
<div>{sanitizePlainText(userInput)}</div>
|
||||
```
|
||||
|
||||
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 = '<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:
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user