Files
lexmind/SECURITY-AUDIT.md

213 lines
7.5 KiB
Markdown

# LexMind Security Audit Report
**Date:** 2026-02-01
**Auditor:** Automated Security Audit
**Status:** ✅ COMPLETED
---
## Executive Summary
Comprehensive security audit of the LexMind application (Next.js 16 + Prisma + PostgreSQL + NextAuth + Stripe). Found and fixed **12 vulnerabilities** across 7 categories. Zero npm vulnerabilities remain.
---
## 1. NPM Vulnerabilities
**Status:** ✅ FIXED
| Before | After |
|--------|-------|
| 21 high severity | 0 vulnerabilities |
- **Root cause:** `fast-xml-parser` 5.2.5 (via AWS SDK) had RangeError DoS bug
- **Fix:** Added npm override for `fast-xml-parser@5.3.4` in package.json
---
## 2. SQL Injection / Prisma
**Status:** ✅ CLEAN
- No `$queryRaw` or `$executeRaw` usage found
- All database access uses Prisma's parameterized queries
- No raw SQL anywhere in the codebase
---
## 3. XSS (Cross-Site Scripting)
**Status:** ✅ CLEAN
- No `dangerouslySetInnerHTML` or `innerHTML` usage found
- React's default escaping protects against XSS
- Added Content-Security-Policy header (see Section 9)
---
## 4. CSRF Protection
**Status:** ✅ VERIFIED
- NextAuth CSRF tokens working correctly
- Cookies use `__Host-` prefix with `HttpOnly; Secure; SameSite=Lax`
- All mutating API routes require authenticated session
---
## 5. Authentication & Authorization
**Status:** ✅ FIXED (2 critical issues)
### 🔴 CRITICAL: Unauthenticated Checkout Route
- **File:** `/api/checkout/route.ts`
- **Issue:** No `getServerSession` check — anyone could create Stripe checkout sessions
- **Fix:** Added authentication check, uses session email instead of user-provided email
### 🔴 CRITICAL: Unauthenticated DOCX Export Route
- **File:** `/api/export/docx/route.ts`
- **Issue:** No authentication — anyone could generate DOCX documents
- **Fix:** Added `getServerSession` check
### Auth Coverage (all routes verified):
| Route | Auth | IDOR Protected |
|-------|------|----------------|
| /api/admin/stats | ✅ ADMIN check | N/A |
| /api/analise-processo | ✅ session.user.id | ✅ userId filter |
| /api/analise-processo/[id] | ✅ session.user.id | ✅ userId filter |
| /api/auditoria | ✅ session.user.id | ✅ userId filter |
| /api/auditoria/[id] | ✅ session.user.id | ✅ userId filter |
| /api/chat | ✅ session.user.id | ✅ userId filter |
| /api/chat/[chatId] | ✅ session.user.id | ✅ userId filter |
| /api/checkout | ✅ **FIXED** | N/A |
| /api/documents | ✅ session.user.id | ✅ userId filter |
| /api/documents/[id] | ✅ session.user.id | ✅ userId filter |
| /api/documents/generate | ✅ session.user.id | N/A |
| /api/export/docx | ✅ **FIXED** | N/A |
| /api/jurisprudencia | ✅ session.user.id | N/A (public data) |
| /api/jurisprudencia/search | ✅ session.user.id | N/A (public data) |
| /api/keys | ✅ session.user.id | ✅ userId filter |
| /api/keys/[id] | ✅ session.user.id | ✅ userId filter |
| /api/prazos | ✅ session.user.id | ✅ userId filter |
| /api/prazos/[id] | ✅ session.user.id | ✅ userId filter |
| /api/register | N/A (public) | N/A |
| /api/stripe/checkout | ✅ session.user | ✅ |
| /api/stripe/portal | ✅ session.user | ✅ |
| /api/stripe/webhook | N/A (Stripe sig) | ✅ signature verified |
| /api/templates | ✅ session.user.id | ✅ userId filter |
| /api/uploads | ✅ session.user.id | ✅ userId filter |
| /api/uploads/[id] | ✅ session.user.id | ✅ userId filter |
---
## 6. Rate Limiting
**Status:** ✅ VERIFIED
Nginx rate limiting active:
- Auth routes: 5 req/min (`zone=auth`)
- API routes: 20 req/sec (`zone=api`)
- General: 30 req/sec (`zone=general`)
- Connection limit: 20 per IP (`conn_limit`)
- Scanner/bot blocking via User-Agent filter
---
## 7. Input Validation
**Status:** ✅ FIXED (5 improvements)
- **Created:** `src/lib/validate.ts` with sanitization utilities
- **Register route:** Added input length limits for all fields
- **Chat route:** Added 10,000 char message limit
- **Auditoria route:** Added title (500) and content (100,000) limits
- **Prazos route:** Added title (500) and description (5,000) limits
- **Pagination:** Bounded page/limit params in uploads and jurisprudencia routes
- **Uploads:** Added server-side file extension validation (defense in depth)
---
## 8. Sensitive Data Exposure
**Status:** ✅ FIXED
- **Created `.gitignore`** — `.env` was not being excluded (no `.gitignore` existed!)
- **Stripe error messages:** Stopped leaking `error.message` to client in checkout/portal routes
- **API responses:** Verified no password hashes or internal IDs are exposed
- **API keys:** Properly hashed (SHA-256), only shown once on creation, masked in listings
- **NEXT_PUBLIC vars:** Only publishable Stripe key and app URL (safe)
- **Error handling:** All routes return generic error messages, details logged server-side
---
## 9. Security Headers
**Status:** ✅ FIXED (2 new headers added)
### Headers now active:
| Header | Value | Status |
|--------|-------|--------|
| X-Frame-Options | SAMEORIGIN | ✅ existing |
| X-Content-Type-Options | nosniff | ✅ existing |
| X-XSS-Protection | 1; mode=block | ✅ existing |
| Referrer-Policy | strict-origin-when-cross-origin | ✅ existing |
| Strict-Transport-Security | max-age=31536000; includeSubDomains | ✅ existing |
| Content-Security-Policy | Full CSP policy | ✅ **ADDED** |
| Permissions-Policy | camera=(), microphone=(), geolocation=() | ✅ **ADDED** |
| server_tokens | off | ✅ existing |
---
## 10. Database Security
**Status:** ✅ VERIFIED
- PostgreSQL listens only on localhost (default, `listen_addresses = 'localhost'`)
- `pg_hba.conf` uses `scram-sha-256` for TCP connections
- Local connections use `peer` authentication
- No remote access configured
---
## 11. File Upload Security
**Status:** ✅ VERIFIED + IMPROVED
- MIME type whitelist: PDF, DOC, DOCX, TXT only
- **Added:** File extension validation (defense in depth)
- Max size: 50MB (enforced both in app and nginx `client_max_body_size`)
- Storage limits per plan (1GB-20GB)
- File paths sanitized via `buildKey()` — strips all special chars
- No path traversal possible (`../` becomes `___`)
- Files stored as `private` ACL on DigitalOcean Spaces
- Access via signed URLs (15 min expiry)
---
## 12. Session Security
**Status:** ✅ IMPROVED
- Cookies: `__Host-` prefix, `HttpOnly`, `Secure`, `SameSite=Lax`
- **Reduced session maxAge from 30 days to 7 days** (more appropriate for legal app)
- JWT strategy with strong NEXTAUTH_SECRET (44 chars, base64)
- CSRF token verified on all auth requests
---
## 13. Other Fixes
### Duplicate Webhook Route Removed
- **Deleted:** `/api/webhook/stripe/route.ts` (incomplete, only logged events, no DB updates)
- **Active:** `/api/stripe/webhook/route.ts` (fully functional with DB updates)
### Next.js Middleware Added
- **Created:** `src/middleware.ts` — adds security headers at application level as backup
---
## 14. Remaining Notes (Low Risk)
| Item | Risk | Notes |
|------|------|-------|
| `typescript: { ignoreBuildErrors: true }` in next.config | Low | Could hide type errors; recommend fixing eventually |
| AWS SDK DoS vuln was in XML parsing | Info | Fixed via override, but only exploitable if attacker controls S3 responses |
| File upload MIME check trusts client header | Low | Mitigated by extension whitelist + private storage |
| No email verification on registration | Medium | Users can register with unverified emails |
---
## Deployment Status
- ✅ All fixes applied
-`npm audit`: 0 vulnerabilities
- ✅ Build successful
- ✅ PM2 restarted
- ✅ Nginx reloaded with new headers
- ✅ Application verified working