# 🔒 Backend Security Audit - Findings & Fixes

**Date:** 2024-10-31  
**Status:** 🔴 CRITICAL ISSUES FOUND

---

## 🚨 Critical Security Issues

### 1. **NO MAX PLANS LIMIT** - CRITICAL
**Location:** `planner.controller.ts:createPlan()`  
**Risk:** Users can create unlimited plans → DoS, resource exhaustion  
**Current:** ✅ Quota check exists for billing service  
**Issue:** ❌ No limit if billing service not configured  

**Impact:**
- Free users can create 1000+ plans
- Database bloat
- Performance degradation

**Fix Required:**
```typescript
// Add max plans check even without billing service
const MAX_FREE_PLANS = 10
const userPlans = await this.service.getUserPlans(userId)
if (userPlans.length >= MAX_FREE_PLANS) {
  return ApiResponse.tooManyRequests(reply, 'Maximum number of plans reached')
}
```

---

### 2. **MISSING OWNERSHIP CHECK IN updateStage()** - CRITICAL
**Location:** `planner.controller.ts:updateStage()`  
**Risk:** Any user can update any stage by knowing the stageId  
**Current:**
```typescript
async updateStage(request, reply) {
  const { id } = request.params
  const updated = await this.service.updateStage(id, request.body)
  return reply.send(updated)  // ❌ NO OWNERSHIP CHECK
}
```

**Attack Scenario:**
1. Attacker gets stageId from public plan
2. Attacker calls `PATCH /api/stages/:id` with malicious data
3. Stage modified without authorization

**Fix Required:**
```typescript
// Fetch stage → check plan ownership
const stage = await this.service.getStage(id)
if (!stage) return ApiResponse.notFound(reply)

const plan = await this.service.getPlan(stage.planId)
if (plan.userId !== request.user?.uid) {
  return ApiResponse.forbidden(reply)
}
```

---

### 3. **MISSING OWNERSHIP CHECK IN deleteStage()** - CRITICAL
**Location:** `planner.controller.ts:deleteStage()`  
**Risk:** Any user can delete any stage  
**Current:**
```typescript
async deleteStage(request, reply) {
  const { id } = request.params
  await this.service.deleteStage(id)  // ❌ NO OWNERSHIP CHECK
  return reply.code(204).send()
}
```

**Fix Required:** Same as updateStage() - check plan ownership first

---

### 4. **NO MAX STAGES PER PLAN FOR FREE USERS** - HIGH
**Location:** `planner.controller.ts:createStage()`  
**Risk:** Free users can add unlimited stages if billing service not configured  
**Current:**
```typescript
const billingService = tryGetBillingService()
if (billingService && request.user?.uid) {
  // Check limit
} else {
  // ❌ NO LIMIT!
}
```

**Fix Required:**
```typescript
const MAX_FREE_STAGES = 20
if (!billingService) {
  const currentCount = Array.isArray(plan.stages) ? plan.stages.length : 0
  if (currentCount >= MAX_FREE_STAGES) {
    return ApiResponse.tooManyRequests(reply, 'Maximum stages reached')
  }
}
```

---

### 5. **NO INPUT SANITIZATION** - HIGH
**Location:** All controllers  
**Risk:** XSS attacks in plan names, descriptions, stage notes  
**Current:** No DOMPurify or sanitization  

**Attack Scenario:**
```json
{
  "name": "<script>alert('XSS')</script>",
  "description": "<img src=x onerror=alert('XSS')>"
}
```

**Fix Required:**
- Add `sanitize-html` package
- Sanitize all user-generated text fields
- Apply in DTOs before saving

---

### 6. **NO RATE LIMITING ON AI ENDPOINTS** - HIGH
**Location:** `planner.controller.ts:generatePlan()`  
**Risk:** Users can spam expensive OpenAI requests  
**Current:** Billing quota check exists BUT not enforced without billing  

**Cost Impact:**
- 1 AI request = $0.10
- 1000 spam requests = $100
- No protection for free users

**Fix Required:**
```typescript
// Add rate limit (10 AI requests per day for free users)
const rateLimitKey = `ai:ratelimit:${userId}`
const count = await redis.incr(rateLimitKey)
if (count === 1) await redis.expire(rateLimitKey, 86400) // 24h
if (count > 10) {
  return ApiResponse.tooManyRequests(reply, 'AI quota exceeded')
}
```

---

### 7. **MISSING SUBSCRIPTION STATUS CHECKS** - MEDIUM
**Location:** Multiple premium features  
**Risk:** Users bypass premium features after subscription expires  

**Features Missing Checks:**
- AI plan generation
- Route optimization
- Map export
- Advanced sharing

**Fix Required:**
```typescript
// Add subscription check decorator
async function requirePremium(userId: string) {
  const billingService = tryGetBillingService()
  if (!billingService) return true // Allow if billing disabled
  
  const plan = await billingService.getUserPlan(userId)
  return plan.tier !== 'free'
}
```

---

### 8. **NO VALIDATION ON STAGE ORDER** - MEDIUM
**Location:** `planner.service.ts:createStage()`  
**Risk:** Negative orders, duplicate orders, order > max int  

**Current:** No validation on `order` field  

**Attack:**
```json
{
  "order": -999999,
  "order": 2147483648  // Integer overflow
}
```

**Fix Required:**
```typescript
if (typeof dto.order !== 'number' || dto.order < 0 || dto.order > 10000) {
  throw new ValidationError('Invalid order', { order: dto.order })
}
```

---

### 9. **GAMIFICATION XP FARMING** - MEDIUM
**Location:** `gamification/season.service.ts:addSeasonXP()`  
**Risk:** Users farm XP by rapid-fire API calls  

**Current Protection:**
- Input validation (xpAmount > 0)
- Warning if xpAmount > 10000

**Missing Protection:**
- No rate limit on XP award frequency
- No cooldown between XP awards
- No check for duplicate event IDs

**Attack:**
```bash
# Spam 1000 requests in 1 second
for i in {1..1000}; do
  curl POST /api/gamification/season/xp -d '{"xpAmount": 100}'
done
# User gains 100,000 XP instantly
```

**Fix Required:**
```typescript
// Add XP rate limiting
const xpKey = `xp:ratelimit:${userId}`
const xpCount = await redis.incr(xpKey)
if (xpCount === 1) await redis.expire(xpKey, 60) // 1 minute window
if (xpCount > 20) {
  throw new RateLimitError('Too many XP awards', { userId })
}
```

---

## 📊 Summary Table

| Issue | Severity | Location | Impact | Fix Time |
|-------|----------|----------|--------|----------|
| No max plans limit | 🔴 CRITICAL | planner.controller | DoS | 5 min |
| Missing ownership check (updateStage) | 🔴 CRITICAL | planner.controller | Unauthorized modification | 10 min |
| Missing ownership check (deleteStage) | 🔴 CRITICAL | planner.controller | Unauthorized deletion | 10 min |
| No max stages for free users | 🟠 HIGH | planner.controller | Resource abuse | 5 min |
| No input sanitization | 🟠 HIGH | All controllers | XSS attacks | 30 min |
| No AI rate limiting | 🟠 HIGH | planner.controller | Cost abuse ($$$) | 10 min |
| Missing subscription checks | 🟡 MEDIUM | Premium features | Feature bypass | 20 min |
| No stage order validation | 🟡 MEDIUM | planner.service | Data corruption | 5 min |
| XP farming | 🟡 MEDIUM | gamification | Game economy abuse | 10 min |

**Total Fix Time:** ~2 hours

---

## 🔧 Fix Priority

### Phase 1: Critical (Must fix before production) - 25 min
1. ✅ Add ownership checks (updateStage, deleteStage)
2. ✅ Add max plans limit (free users)
3. ✅ Add max stages limit (free users)

### Phase 2: High (Fix within 48h) - 50 min
4. ✅ Add input sanitization (DOMPurify)
5. ✅ Add AI rate limiting (per user)
6. ✅ Add subscription status checks

### Phase 3: Medium (Fix within 1 week) - 30 min
7. ✅ Add stage order validation
8. ✅ Add XP rate limiting
9. ✅ Add admin-only route protection

---

## 🎯 Next Steps

1. **Immediate:** Fix critical issues (Phase 1)
2. **Deploy:** Security hotfix
3. **Monitor:** Check for exploitation attempts in logs
4. **Test:** Penetration testing
5. **Document:** Security checklist for new features
