Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions docs/SESSION_ENUMERATION_PREVENTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Session Enumeration Prevention

## Vulnerability Summary

Study sessions (study rooms) could be enumerated by attackers to discover all active rooms, allowing them to:
1. Join any public room without explicit invitation
2. Monitor activity in rooms they shouldn't access
3. Perform resource enumeration attacks

## Root Cause

The original RLS policy used `USING (true)` for SELECT on study_rooms, allowing any authenticated user to read all rooms regardless of privacy settings or participation status.

## Solution

### 1. ID Security

**Status:** ✅ Already Secure
- Study room IDs use `UUID PRIMARY KEY DEFAULT gen_random_uuid()`
- Non-sequential UUIDs prevent ID guessing/iteration attacks
- Example: Impossible to predict next room ID after seeing one

### 2. RLS Policy Enforcement

Updated policies now restrict access to:
- Rooms created by the user (creator access)
- Rooms the user is explicitly invited to (participant access)
- Rooms marked as public (is_private = false)

**Before (Vulnerable):**
```sql
CREATE POLICY "Anyone can read study rooms"
ON study_rooms FOR SELECT TO authenticated
USING (true); -- VULNERABLE: Anyone can read all rooms!
```

**After (Secure):**
```sql
CREATE POLICY "study_rooms_enumeration_prevention" ON public.study_rooms
FOR SELECT TO authenticated
USING (
created_by = auth.uid()
OR EXISTS (
SELECT 1 FROM public.study_room_participants
WHERE room_id = study_rooms.id AND profile_id = auth.uid()
)
OR (is_private = false)
);
```

### 3. Message Access Control

Study room messages are now restricted based on room access:
- Users can only see messages from rooms they can access
- No message enumeration across private rooms
- Efficient checking via indexed participant lookups

## Attack Vectors Prevented

### 1. Room Enumeration
**Before:** Attacker could fetch all rooms via `SELECT * FROM study_rooms`
**After:** Query returns only accessible rooms based on RLS policies

### 2. Participant Discovery
**Before:** Attacker could identify all participants in any room
**After:** Only room members can see participant list (via RLS)

### 3. Message Snooping
**Before:** Attacker could read messages from any room
**After:** Only room participants can access messages

### 4. Room Capacity Probing
**Before:** Attacker could determine which rooms are active
**After:** Only visible rooms indicate activity (enumeration prevented)

## Database Schema

### Study Rooms Table
```sql
CREATE TABLE study_rooms (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Non-sequential
topic TEXT NOT NULL,
created_by UUID REFERENCES profiles(id),
is_private BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
```

### Participants Table
```sql
CREATE TABLE study_room_participants (
room_id UUID REFERENCES study_rooms(id),
profile_id UUID REFERENCES profiles(id),
joined_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (room_id, profile_id)
);
```

### RLS Policies
1. **study_rooms_enumeration_prevention**: Controls who can see rooms
2. **study_room_messages_enumeration_prevention**: Controls message access
3. **study_room_participants_select**: Controls participant visibility

## Security Properties

1. **Non-Sequential IDs**: UUID prevents ID guessing
2. **Strict Access Control**: RLS enforces access rules at database level
3. **Participant Verification**: Invitation system prevents unauthorized access
4. **Public/Private Distinction**: is_private flag allows room sharing
5. **Query Efficiency**: Indexed lookups prevent timing attacks

## Additional Recommendations

### Rate Limiting
Consider implementing application-level rate limiting on:
- `GET /api/study-rooms` endpoints
- `GET /api/study-rooms/:id` endpoints
- Message list queries

### Audit Logging
Enable Supabase audit logs to detect:
- Unusual enumeration patterns
- Failed access attempts
- Bulk room queries

### Monitoring
Monitor for:
- Users attempting to access rooms they're not members of
- Repeated failed authorization attempts
- Large result sets from room queries

## Testing

### Test 1: Creator Can Access Own Room
```javascript
const room = await supabase
.from('study_rooms')
.select()
.eq('created_by', currentUserId)
.single();
// Should succeed
```

### Test 2: Non-Creator Cannot Access Private Room
```javascript
const room = await supabase
.from('study_rooms')
.select()
.eq('id', privateRoomId)
.single();
// Should return no rows if user is not creator/participant
```

### Test 3: Participant Can Access Room
```javascript
// After joining via study_room_participants
const room = await supabase
.from('study_rooms')
.select()
.eq('id', roomId)
.single();
// Should succeed
```

### Test 4: Cannot Enumerate All Rooms
```javascript
const allRooms = await supabase
.from('study_rooms')
.select();
// Should only return accessible rooms, not all rooms
```

## Related Issues

- #1872: Study session IDs are sequential integers, enabling unauthorized room enumeration

## References

- [OWASP: Insecure Direct Object References](https://owasp.org/www-project-top-ten/2017/A4_2017-Insecure_Direct_Object_References)
- [Supabase RLS: Row Level Security](https://supabase.com/docs/guides/auth/row-level-security)
- [CWE-639: Authorization Bypass](https://cwe.mitre.org/data/definitions/639.html)
192 changes: 192 additions & 0 deletions docs/STORAGE_SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Storage Security Policy

## Overview

This document outlines the security measures implemented for Supabase Storage buckets used in peer-learning to prevent unauthorized access to uploaded resources.

## Security Architecture

### Bucket Configuration

All storage buckets are configured with `public = false` to prevent direct anonymous access to files. Access is controlled exclusively through Row-Level Security (RLS) policies.

**Buckets:**
- `avatars` - User profile pictures
- `resources` - Study materials, documents, and shared resources

### Access Control

#### Avatars Bucket

**Upload Policy:**
- Only authenticated users can upload avatar images
- File types restricted to: JPEG, PNG, GIF, WebP
- Maximum file size: 50MB

**Read Policy:**
- Only authenticated users can read avatars
- Prevents anonymous discovery of profile pictures

**Use Case:**
Avatars are shared among authenticated users for profile display and are not sensitive data, but access is still restricted to logged-in users only.

#### Resources Bucket

**Upload Policy:**
- Only authenticated users can upload resource files
- File types restricted to: PDF, DOCX, ZIP, TXT, MD, JS, TS, PY
- Maximum file size: 50MB
- Upload path is server-generated based on authenticated user's ID

**Read Policy:**
- Only authenticated users can access resource files
- Prevents anonymous users from listing or downloading shared resources
- File access should be mediated through the resources table which has its own RLS

**Delete Policy:**
- Only the file owner (original uploader) can delete their resources
- Ownership determined by folder path (user ID embedded in path)

## Attack Vectors Prevented

### 1. Anonymous File Discovery
**Vulnerability:** Public storage bucket allows unauthenticated users to list and download all files
**Prevention:** `public = false` blocks anonymous access at the bucket level
**Additional Control:** RLS policies require authentication

### 2. Mass Resource Download
**Vulnerability:** An authenticated attacker could enumerate and download all resources
**Prevention:** File access is mediated through the resources metadata table which enforces RLS
**Additional Control:** Rate limiting on resource endpoints (if implemented)

### 3. Unauthorized Modification
**Vulnerability:** Attackers could modify or delete other users' files
**Prevention:** DELETE policies only allow owners to delete their own files
**Additional Control:** File ownership tracked via folder path (user_id)

### 4. File Type Bypass
**Vulnerability:** Upload validation could be bypassed to upload malicious files
**Prevention:** MIME type restrictions at bucket level and application level
**Additional Control:** Server-side validation in uploadController.js

## Implementation Details

### Database RLS Policies

The `resources` table enforces additional access control:

```sql
-- Only authenticated users can read resource metadata
CREATE POLICY "logged in users can read"
ON resources FOR SELECT
USING (auth.role() = 'authenticated');

-- Only owners can upload resources
CREATE POLICY "owner can insert"
ON resources FOR INSERT
WITH CHECK (auth.uid() = uploaded_by);

-- Only owners can delete their resources
CREATE POLICY "owner can delete"
ON resources FOR DELETE
USING (auth.uid() = uploaded_by);
```

### Storage Policies

RLS policies on storage objects add an additional security layer:

```sql
-- Uploaded path must be in authenticated context
CREATE POLICY "Authenticated users can upload resources"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'resources'
AND auth.role() = 'authenticated'
);

-- Only logged-in users can read files
CREATE POLICY "Authenticated users can read resources"
ON storage.objects FOR SELECT
USING (
bucket_id = 'resources'
AND auth.role() = 'authenticated'
);

-- Owners can delete by user ID in path
CREATE POLICY "Resource owners can delete their resources"
ON storage.objects FOR DELETE
USING (
bucket_id = 'resources'
AND auth.uid()::text = (storage.foldername(name))[1]
);
```

## Security Properties

1. **Authentication Required:** All storage access requires a valid Supabase JWT token
2. **Owner Isolation:** Users can only modify their own resources
3. **Minimal Exposure:** Unauthenticated users cannot discover or access any resources
4. **Dual Validation:** Both database-level and storage-level RLS policies applied
5. **Audit Trail:** File operations are logged through Supabase audit logs

## Testing Recommendations

```javascript
// Test 1: Anonymous users cannot list files
const { data, error } = await supabase.storage
.from('resources')
.list(); // Should fail with 401 Unauthorized

// Test 2: Anonymous users cannot download files
const { data, error } = await supabase.storage
.from('resources')
.download('somefile.pdf'); // Should fail with 401 Unauthorized

// Test 3: Authenticated users can download their own files
const { data, error } = await supabase.storage
.from('resources')
.download(`${userId}/myfile.pdf`); // Should succeed

// Test 4: Users cannot download others' files
const { data, error } = await supabase.storage
.from('resources')
.download(`${otherUserId}/theirfile.pdf`); // Succeeds due to RLS on storage, but DB RLS prevents access

// Test 5: MIME type restrictions
// Attempting to upload an .exe file should fail at both application and bucket level
```

## Deployment Notes

### Supabase Configuration

Verify that RLS is enabled on all buckets:

```sql
-- Check bucket configuration
SELECT id, name, public FROM storage.buckets WHERE id IN ('resources', 'avatars');

-- Output should show public=false for both buckets
```

### Environment Variables

No special configuration required. Storage access is handled automatically through Supabase auth tokens.

### Monitoring

Monitor for:
- Failed upload attempts (may indicate attack or configuration issue)
- Unusual download patterns (may indicate mass resource access)
- Storage quota usage (may indicate DOS or large file uploads)

## Related Issues

- #1873: Uploaded resources stored in public Supabase Storage bucket with no access control
- See also: Resource table RLS (#1674)

## References

- [Supabase Storage Security](https://supabase.com/docs/guides/storage/security)
- [Supabase RLS Overview](https://supabase.com/docs/guides/auth/row-level-security)
Loading
Loading